tangem-app-android-audited/app/src/main/java/com/tangem/tap/proxy/TransactionManagerImpl.kt
2022-12-26 16:28:20 +03:00

202 lines
No EOL
7.5 KiB
Kotlin

package com.tangem.tap.proxy
import com.google.firebase.crashlytics.FirebaseCrashlytics
import com.tangem.Message
import com.tangem.blockchain.blockchains.ethereum.EthereumTransactionExtras
import com.tangem.blockchain.blockchains.ethereum.EthereumWalletManager
import com.tangem.blockchain.common.Amount
import com.tangem.blockchain.common.Blockchain
import com.tangem.blockchain.common.BlockchainSdkError
import com.tangem.blockchain.common.Token
import com.tangem.blockchain.common.TransactionExtras
import com.tangem.blockchain.common.TransactionSender
import com.tangem.blockchain.common.TransactionSigner
import com.tangem.blockchain.common.WalletManager
import com.tangem.blockchain.extensions.Result
import com.tangem.blockchain.extensions.SimpleResult
import com.tangem.common.core.TangemSdkError
import com.tangem.common.extensions.hexToBytes
import com.tangem.domain.common.extensions.fromNetworkId
import com.tangem.lib.crypto.TransactionManager
import com.tangem.lib.crypto.models.Currency
import com.tangem.lib.crypto.models.ProxyAmount
import com.tangem.lib.crypto.models.transactions.SendTxResult
import com.tangem.tap.common.redux.global.GlobalAction
import com.tangem.tap.domain.TangemSigner
import com.tangem.tap.domain.tokens.models.BlockchainNetwork
import com.tangem.tap.store
import com.tangem.tap.tangemSdk
import java.math.BigDecimal
import java.math.BigInteger
class TransactionManagerImpl(
private val appStateHolder: AppStateHolder,
) : TransactionManager {
@Throws(IllegalStateException::class)
override suspend fun sendTransaction(
networkId: String,
amountToSend: BigDecimal,
currencyToSend: Currency,
feeAmount: BigDecimal,
destinationAddress: String,
dataToSign: String,
): SendTxResult {
val blockchain = requireNotNull(Blockchain.fromNetworkId(networkId)) { "blockchain not found" }
val walletManager = getActualWalletManager(blockchain)
val amount = createAmount(amountToSend, currencyToSend, blockchain)
val txData = walletManager.createTransaction(
amount = amount,
fee = Amount(value = feeAmount, blockchain = blockchain),
destination = destinationAddress,
).copy(hash = dataToSign, extras = createExtras(walletManager, feeAmount, dataToSign))
val signer = transactionSigner(walletManager)
val sendResult = try {
(walletManager as TransactionSender).send(txData, signer)
} catch (ex: Exception) {
FirebaseCrashlytics.getInstance().recordException(ex)
return SendTxResult.UnknownError(ex)
}
return handleSendResult(sendResult)
}
@Throws(IllegalStateException::class)
override suspend fun getFee(
networkId: String,
amountToSend: BigDecimal,
currencyToSend: Currency,
destinationAddress: String,
): ProxyAmount {
val blockchain = requireNotNull(Blockchain.fromNetworkId(networkId)) { "blockchain not found" }
val walletManager = getActualWalletManager(blockchain)
val fee = (walletManager as TransactionSender).getFee(
amount = createAmount(amountToSend, currencyToSend, blockchain),
destination = destinationAddress,
)
when (fee) {
is Result.Success -> {
return convertToProxyAmount(fee.data.firstOrNull() ?: error("no fee found"))
}
is Result.Failure -> {
error(fee.error.message ?: fee.error.customMessage)
}
}
}
override fun getNativeAddress(networkId: String): String {
return "" //todo implement
}
private fun handleSendResult(result: SimpleResult): SendTxResult {
when (result) {
is SimpleResult.Success -> {
return SendTxResult.Success
}
is SimpleResult.Failure -> {
val error = (result.error as? BlockchainSdkError) ?: return SendTxResult.UnknownError()
when (error) {
is BlockchainSdkError.WrappedTangemError -> {
val tangemSdkError = (error.tangemError as? TangemSdkError)
?: return SendTxResult.UnknownError()
if (tangemSdkError is TangemSdkError.UserCancelled) return SendTxResult.UserCancelledError
return SendTxResult.TangemSdkError(tangemSdkError.code, tangemSdkError.cause)
}
else -> {
return SendTxResult.TangemSdkError(error.code, error.cause)
}
}
}
}
}
private fun transactionSigner(walletManager: WalletManager): TransactionSigner {
val actualCard = requireNotNull(appStateHolder.getActualCard()) { "no card found" }
return TangemSigner(
card = actualCard,
tangemSdk = tangemSdk,
initialMessage = Message("test transaction title", "test description"),
) { signResponse ->
store.dispatch(
GlobalAction.UpdateWalletSignedHashes(
walletSignedHashes = signResponse.totalSignedHashes,
walletPublicKey = walletManager.wallet.publicKey.seedKey,
remainingSignatures = signResponse.remainingSignatures,
),
)
}
}
private fun getActualWalletManager(blockchain: Blockchain): WalletManager {
val card = appStateHolder.getActualCard()
if (card != null) {
val blockchainNetwork = BlockchainNetwork(blockchain, card)
val walletManager = appStateHolder.walletState?.getWalletManager(blockchainNetwork)
if (walletManager != null) {
return walletManager
} else {
error("no wallet manager found")
}
} else {
error("card not found")
}
}
private fun createExtras(
walletManager: WalletManager,
feeAmount: BigDecimal,
transactionHash: String,
): TransactionExtras? {
return when (walletManager) {
is EthereumWalletManager -> {
return EthereumTransactionExtras(
data = transactionHash.removePrefix(HEX_PREFIX).hexToBytes(),
gasLimit = BigInteger.valueOf(DEFAULT_GAS_LIMIT),
)
}
else -> {
null
}
}
}
private fun createAmount(
amount: BigDecimal,
currency: Currency,
blockchain: Blockchain,
): Amount {
return when (currency) {
is Currency.NativeToken -> {
Amount(value = amount, blockchain = blockchain)
}
is Currency.NonNativeToken -> {
Amount(convertNonNativeToken(currency), amount)
}
}
}
private fun convertNonNativeToken(token: Currency.NonNativeToken): Token {
return Token(
name = token.name,
symbol = token.symbol,
contractAddress = token.contractAddress,
decimals = token.decimalCount,
id = token.id,
)
}
private fun convertToProxyAmount(amount: Amount): ProxyAmount {
return ProxyAmount(
currencySymbol = amount.currencySymbol,
value = amount.value ?: BigDecimal.ZERO,
decimals = amount.decimals,
)
}
companion object {
private const val HEX_PREFIX = "0x"
private const val DEFAULT_GAS_LIMIT = 300000L
}
}