Updated on 2026-08-14

This commit is contained in:
Tangem 2022-09-15 23:00:33 +03:00
commit 300b2edecb
16 changed files with 128 additions and 94 deletions

View file

@ -3,8 +3,19 @@ package com.tangem.tap.features.send.redux.reducers
import com.tangem.blockchain.blockchains.stellar.StellarMemo
import com.tangem.blockchain.common.Blockchain
import com.tangem.tap.features.send.redux.SendScreenAction
import com.tangem.tap.features.send.redux.TransactionExtrasAction.*
import com.tangem.tap.features.send.redux.states.*
import com.tangem.tap.features.send.redux.TransactionExtrasAction.BinanceMemo
import com.tangem.tap.features.send.redux.TransactionExtrasAction.Prepare
import com.tangem.tap.features.send.redux.TransactionExtrasAction.Release
import com.tangem.tap.features.send.redux.TransactionExtrasAction.XlmMemo
import com.tangem.tap.features.send.redux.TransactionExtrasAction.XrpDestinationTag
import com.tangem.tap.features.send.redux.states.BinanceMemoState
import com.tangem.tap.features.send.redux.states.InputViewValue
import com.tangem.tap.features.send.redux.states.SendState
import com.tangem.tap.features.send.redux.states.TransactionExtraError
import com.tangem.tap.features.send.redux.states.TransactionExtrasState
import com.tangem.tap.features.send.redux.states.XlmMemoState
import com.tangem.tap.features.send.redux.states.XlmMemoType
import com.tangem.tap.features.send.redux.states.XrpDestinationTagState
/**
[REDACTED_AUTHOR]
@ -53,42 +64,33 @@ class TransactionExtrasReducer : SendInternalReducer {
}
private fun handleXlmMemo(
action: XlmMemo,
sendState: SendState,
infoState: TransactionExtrasState,
action: XlmMemo,
sendState: SendState,
infoState: TransactionExtrasState,
): SendState {
fun clearMemo(memo: XlmMemoState): XlmMemoState = memo.copy(text = null, id = null, error = null)
val result = when (action) {
// is XlmMemo.ChangeSelectedMemo -> {
// val inputViewValue = InputViewValue("", false)
// val memo = infoState.xlmMemo?.copy(
// viewFieldValue = inputViewValue,
// selectedMemoType = action.memoType,
// ) ?: XlmMemoState(inputViewValue, action.memoType)
//
// infoState.copy(xlmMemo = clearMemo(memo))
// }
is XlmMemo.HandleUserInput -> {
val inputViewValue = InputViewValue(action.data, true)
var memo = infoState.xlmMemo?.copy(viewFieldValue = inputViewValue)
?: XlmMemoState(inputViewValue)
?: XlmMemoState(inputViewValue)
memo = clearMemo(memo)
memo = when (infoState.xlmMemo?.selectedMemoType) {
XlmMemoType.TEXT -> memo.copy(text = StellarMemo.Text(action.data))
XlmMemoType.ID -> {
val id = action.data.toBigIntegerOrNull()
if (id != null) {
if (id > XlmMemoState.MAX_NUMBER) {
memo.copy(error = TransactionExtraError.INVALID_XLM_MEMO)
} else {
memo.copy(id = StellarMemo.Id(id))
}
memo = when (memo.selectedMemoType) {
XlmMemoType.TEXT -> {
if (XlmMemoState.isAssignableValue(action.data)) {
memo.copy(text = StellarMemo.Text(action.data))
} else {
memo
memo.copy(error = TransactionExtraError.INVALID_XLM_MEMO)
}
}
XlmMemoType.ID -> {
if (XlmMemoState.isAssignableValue(action.data)) {
memo.copy(id = StellarMemo.Id(action.data.toBigInteger()))
} else {
memo.copy(error = TransactionExtraError.INVALID_XLM_MEMO)
}
}
null -> memo
}
infoState.copy(xlmMemo = memo)
}
@ -117,16 +119,16 @@ class TransactionExtrasReducer : SendInternalReducer {
}
private fun handleXrpTag(
action: XrpDestinationTag,
sendState: SendState,
infoState: TransactionExtrasState,
action: XrpDestinationTag,
sendState: SendState,
infoState: TransactionExtrasState,
): SendState {
val result = when (action) {
is XrpDestinationTag.HandleUserInput -> {
val tag = action.data.toLongOrNull()
if (tag != null) {
val input = InputViewValue(action.data, true)
val tagState = if (tag <= XrpDestinationTagState.MAX_NUMBER){
val tagState = if (tag <= XrpDestinationTagState.MAX_NUMBER) {
XrpDestinationTagState(input, tag)
} else {
XrpDestinationTagState(input, error = TransactionExtraError.INVALID_DESTINATION_TAG)

View file

@ -1,19 +1,20 @@
package com.tangem.tap.features.send.redux.states
import androidx.core.text.isDigitsOnly
import com.tangem.blockchain.blockchains.stellar.StellarMemo
import com.tangem.tap.features.send.redux.AddressPayIdVerifyAction
import java.math.BigInteger
data class AddressPayIdState(
val viewFieldValue: InputViewValue = InputViewValue(""),
val normalFieldValue: String? = null,
val truncatedFieldValue: String? = null,
val destinationWalletAddress: String? = null,
val error: AddressPayIdVerifyAction.Error? = null,
val truncateHandler: ((String) -> String)? = null,
val sendingToPayIdEnabled: Boolean = false,
val pasteIsEnabled: Boolean = false,
val inputIsEnabled: Boolean = true
val viewFieldValue: InputViewValue = InputViewValue(""),
val normalFieldValue: String? = null,
val truncatedFieldValue: String? = null,
val destinationWalletAddress: String? = null,
val error: AddressPayIdVerifyAction.Error? = null,
val truncateHandler: ((String) -> String)? = null,
val sendingToPayIdEnabled: Boolean = false,
val pasteIsEnabled: Boolean = false,
val inputIsEnabled: Boolean = true,
) : SendScreenState {
override val stateId: StateId = StateId.ADDRESS_PAY_ID
@ -26,9 +27,9 @@ data class AddressPayIdState(
}
data class TransactionExtrasState(
val xlmMemo: XlmMemoState? = null,
val binanceMemo: BinanceMemoState? = null,
val xrpDestinationTag: XrpDestinationTagState? = null
val xlmMemo: XlmMemoState? = null,
val binanceMemo: BinanceMemoState? = null,
val xrpDestinationTag: XrpDestinationTagState? = null,
) : IdStateHolder {
override val stateId: StateId = StateId.TRANSACTION_EXTRAS
}
@ -38,11 +39,10 @@ enum class XlmMemoType {
}
data class XlmMemoState(
val viewFieldValue: InputViewValue = InputViewValue(""),
val selectedMemoType: XlmMemoType = XlmMemoType.ID,
val text: StellarMemo.Text? = null,
val id: StellarMemo.Id? = null,
val error: TransactionExtraError? = null,
val viewFieldValue: InputViewValue = InputViewValue(""),
val text: StellarMemo.Text? = null,
val id: StellarMemo.Id? = null,
val error: TransactionExtraError? = null,
) {
val memo: StellarMemo?
get() = when (selectedMemoType) {
@ -50,16 +50,37 @@ data class XlmMemoState(
XlmMemoType.ID -> id
}
val selectedMemoType: XlmMemoType
get() = determineMemoType(viewFieldValue.value)
companion object {
val MAX_NUMBER: BigInteger = BigInteger("FFFFFFFFFFFFFFFF", 16)
fun determineMemoType(value: String): XlmMemoType = when {
value.isNotEmpty() && value.isDigitsOnly() -> XlmMemoType.ID
else -> XlmMemoType.TEXT
}
fun isAssignableValue(value: String): Boolean = when (determineMemoType(value)) {
XlmMemoType.TEXT -> {
// from org.stellar.sdk.MemoText
value.toByteArray().size <= 28
}
XlmMemoType.ID -> {
try {
// from com.tangem.blockchain.blockchains.stellar.StellarMemo.toStellarSdkMemo
value.toBigInteger() in BigInteger.ZERO..(Long.MAX_VALUE.toBigInteger() * 2.toBigInteger())
} catch (ex: NumberFormatException) {
false
}
}
}
}
}
data class BinanceMemoState(
val viewFieldValue: InputViewValue = InputViewValue(""),
val memo: BigInteger? = null,
val error: TransactionExtraError? = null
) {
val error: TransactionExtraError? = null,
) {
companion object {
val MAX_NUMBER: BigInteger = BigInteger("FFFFFFFFFFFFFFFF", 16)
}
@ -67,9 +88,9 @@ data class BinanceMemoState(
// tag must contains only digits
data class XrpDestinationTagState(
val viewFieldValue: InputViewValue = InputViewValue(""),
val tag: Long? = null,
val error: TransactionExtraError? = null
val viewFieldValue: InputViewValue = InputViewValue(""),
val tag: Long? = null,
val error: TransactionExtraError? = null,
) {
companion object {
const val MAX_NUMBER: Long = 4294967295

View file

@ -2,7 +2,6 @@ package com.tangem.tap.features.send.ui.stateSubscribers
import android.app.Dialog
import android.content.Context
import android.text.InputType
import android.text.SpannableStringBuilder
import android.view.View
import android.view.ViewGroup
@ -31,7 +30,6 @@ import com.tangem.tap.features.send.redux.states.SendState
import com.tangem.tap.features.send.redux.states.StateId
import com.tangem.tap.features.send.redux.states.TransactionExtraError
import com.tangem.tap.features.send.redux.states.TransactionExtrasState
import com.tangem.tap.features.send.redux.states.XlmMemoType
import com.tangem.tap.features.send.ui.FeeUiHelper
import com.tangem.tap.features.send.ui.SendFragment
import com.tangem.tap.features.send.ui.dialogs.RequestFeeErrorDialog
@ -80,14 +78,10 @@ class SendStateSubscriber(fragment: BaseStoreFragment) :
showView(binanceMemoContainer, infoState.binanceMemo)
infoState.xlmMemo?.let {
etXlmMemo.inputType = when (it.selectedMemoType) {
XlmMemoType.TEXT -> InputType.TYPE_CLASS_TEXT or InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS
XlmMemoType.ID -> InputType.TYPE_CLASS_NUMBER
}
if (!it.viewFieldValue.isFromUserInput) etXlmMemo.setText(it.viewFieldValue.value)
if (it.error != null) {
if (it.error == TransactionExtraError.INVALID_XLM_MEMO) {
tilXlmMemo.error = fg.getText(R.string.send_error_invalid_memo_id)
tilXlmMemo.error = fg.getText(R.string.send_error_invalid_memo)
}
} else {
tilXlmMemo.error = null
@ -109,7 +103,7 @@ class SendStateSubscriber(fragment: BaseStoreFragment) :
infoState.binanceMemo?.let {
if (infoState.binanceMemo.error != null) {
if (infoState.binanceMemo.error == TransactionExtraError.INVALID_BINANCE_MEMO) {
tilBinanceMemo.error = fg.getText(R.string.send_error_invalid_memo_id)
tilBinanceMemo.error = fg.getText(R.string.send_error_invalid_memo)
}
} else {
tilBinanceMemo.error = null

View file

@ -153,6 +153,7 @@
android:layout_height="wrap_content"
android:background="@color/backgroundLightGray"
android:ellipsize="middle"
android:inputType="text|textNoSuggestions"
android:paddingStart="0dp"
android:paddingEnd="0dp"
android:singleLine="true"

View file

@ -86,5 +86,9 @@
<string name="card_settings_reset_card_to_factory">Reset to Factory Settings</string>
<string name="card_settings_reset_card_to_factory_footer">Factory Reset will completely delete the wallet from the selected card. You will not be able to restore the current wallet or use the card to recover the access code.</string>
<string name="wallet_connect_select_network">Select network</string>
<string name="send_extras_hint_memo">Memo</string>
<string name="send_extras_hint_destination_tag">Tag</string>
<string name="send_error_invalid_memo">Invalid Memo. It won\'t be added to the transaction</string>
<string name="send_error_invalid_destination_tag">Invalid Tag. It won\'t be added to the transaction</string>
<string name="warning_existential_deposit_message">%s network has a concept of Existential Deposit. If your account drops below %s %s it will be deactivated and any remaining funds will be destroyed.</string>
</resources>

View file

@ -86,5 +86,9 @@
<string name="card_settings_reset_card_to_factory">Reset to Factory Settings</string>
<string name="card_settings_reset_card_to_factory_footer">Factory Reset will completely delete the wallet from the selected card. You will not be able to restore the current wallet or use the card to recover the access code.</string>
<string name="wallet_connect_select_network">Select network</string>
<string name="send_extras_hint_memo">Memo</string>
<string name="send_extras_hint_destination_tag">Tag</string>
<string name="send_error_invalid_memo">Invalid Memo. It won\'t be added to the transaction</string>
<string name="send_error_invalid_destination_tag">Invalid Tag. It won\'t be added to the transaction</string>
<string name="warning_existential_deposit_message">%s network has a concept of Existential Deposit. If your account drops below %s %s it will be deactivated and any remaining funds will be destroyed.</string>
</resources>

View file

@ -86,5 +86,9 @@
<string name="card_settings_reset_card_to_factory">Reset to Factory Settings</string>
<string name="card_settings_reset_card_to_factory_footer">Factory Reset will completely delete the wallet from the selected card. You will not be able to restore the current wallet or use the card to recover the access code.</string>
<string name="wallet_connect_select_network">Select network</string>
<string name="send_extras_hint_memo">Memo</string>
<string name="send_extras_hint_destination_tag">Tag</string>
<string name="send_error_invalid_memo">Invalid Memo. It won\'t be added to the transaction</string>
<string name="send_error_invalid_destination_tag">Invalid Tag. It won\'t be added to the transaction</string>
<string name="warning_existential_deposit_message">%s network has a concept of Existential Deposit. If your account drops below %s %s it will be deactivated and any remaining funds will be destroyed.</string>
</resources>

View file

@ -147,10 +147,6 @@
<string name="common_add">Добавить</string>
<string name="common_remove">Удалить</string>
<string name="common_search">Поиск</string>
<string name="send_extras_hint_memo">Памятка</string>
<string name="send_extras_hint_destination_tag">Тег назначения</string>
<string name="send_error_invalid_destination_tag">Недопустимый тег назначения. Он не будет добавлен в транзакцию.</string>
<string name="send_error_invalid_memo_id">Недопустимый идентификатор памятки. Он не будет добавлен в транзакцию.</string>
<string name="details_section_title_app">Приложение</string>
<string name="details_row_title_send_feedback">Отправить отзыв</string>
<string name="alert_app_feedback_sent_title">Успешно отправлено</string>

View file

@ -97,5 +97,9 @@
<string name="card_settings_reset_card_to_factory">Сброс к заводским настройкам</string>
<string name="card_settings_reset_card_to_factory_footer">Сброс к заводским настройкам приведет к полному удалению кошелька с выбранной карты. Вы не сможете восстановить текущий кошелек или использовать эту карту для восстановления кода доступа.</string>
<string name="wallet_connect_select_network">Выберите сеть</string>
<string name="send_extras_hint_memo">Memo</string>
<string name="send_extras_hint_destination_tag">Tag</string>
<string name="send_error_invalid_memo">Недопустимый Memo. Он не будет добавлен в транзакцию.</string>
<string name="send_error_invalid_destination_tag">Недопустимый Tag. Он не будет добавлен в транзакцию.</string>
<string name="warning_existential_deposit_message">Сеть %s использует концепцию экзистенциального депозита. Если баланс вашего счета опустится ниже %s %s, он будет деактивирован, а все оставшиеся средства будут уничтожены.</string>
</resources>

View file

@ -99,5 +99,9 @@
<string name="card_settings_reset_card_to_factory">Reset to Factory Settings</string>
<string name="card_settings_reset_card_to_factory_footer">Factory Reset will completely delete the wallet from the selected card. You will not be able to restore the current wallet or use the card to recover the access code.</string>
<string name="wallet_connect_select_network">Select network</string>
<string name="send_extras_hint_memo">Memo</string>
<string name="send_extras_hint_destination_tag">Tag</string>
<string name="send_error_invalid_memo">Invalid Memo. It won\'t be added to the transaction</string>
<string name="send_error_invalid_destination_tag">Invalid Tag. It won\'t be added to the transaction</string>
<string name="warning_existential_deposit_message">%s network has a concept of Existential Deposit. If your account drops below %s %s it will be deactivated and any remaining funds will be destroyed.</string>
</resources>

View file

@ -40,12 +40,6 @@
<string name="common_remove" translatable="false">Remove</string>
<string name="common_search" translatable="false">Search</string>
<string name="send_extras_hint_memo" translatable="false">Memo</string>
<string name="send_extras_hint_destination_tag" translatable="false">Destination tag</string>
<string name="send_error_invalid_destination_tag" translatable="false">Invalid destination tag. It won\'t be added to the transaction</string>
<string name="send_error_invalid_memo_id" translatable="false">Invalid Memo ID. It won\'t be added to the transaction</string>
<string name="details_section_title_app" translatable="false">App</string>
<string name="details_row_title_send_feedback" translatable="false">Send feedback</string>
<string name="alert_app_feedback_sent_title" translatable="false">Sent successfully</string>

View file

@ -2,7 +2,7 @@ ext.versions = [
kotlin : '1.6.10',
build_gradle : '7.1.3',
tangem_card_sdk : 'develop-159',
tangem_blockchain_sdk: 'develop-107',
tangem_blockchain_sdk: 'develop-111',
// tangem_blockchain_sdk: '0.0.1',
]

View file

@ -5,7 +5,7 @@ import com.tangem.blockchain.common.Blockchain
fun Blockchain.Companion.fromNetworkId(networkId: String): Blockchain? {
return when (networkId) {
"arbitrum-one" -> Blockchain.Arbitrum
"arbitrum-one/test" -> Blockchain.ArbitrumTestnet
"arbitrum/test" -> Blockchain.ArbitrumTestnet
"avalanche", "avalanche-2" -> Blockchain.Avalanche
"avalanche/test", "avalanche-2/test" -> Blockchain.AvalancheTestnet
"binancecoin" -> Blockchain.Binance
@ -40,6 +40,7 @@ fun Blockchain.Companion.fromNetworkId(networkId: String): Blockchain? {
"xdai" -> Blockchain.Gnosis
"polkadot" -> Blockchain.Polkadot
"polkadot/test" -> Blockchain.PolkadotTestnet
"kusama" -> Blockchain.Kusama
else -> null
}
}
@ -84,6 +85,7 @@ fun Blockchain.toNetworkId(): String {
Blockchain.Gnosis -> "xdai"
Blockchain.Polkadot -> "polkadot"
Blockchain.PolkadotTestnet -> "polkadot/test"
Blockchain.Kusama -> "kusama"
}
}
@ -101,15 +103,16 @@ fun Blockchain.toCoinId(): String {
Blockchain.Avalanche, Blockchain.AvalancheTestnet -> "avalanche-2"
Blockchain.Solana, Blockchain.SolanaTestnet -> "solana"
Blockchain.Fantom, Blockchain.FantomTestnet -> "fantom"
Blockchain.Tron, Blockchain.TronTestnet -> "tron"
Blockchain.Polkadot, Blockchain.PolkadotTestnet -> "polkadot"
Blockchain.Ducatus -> "ducatus"
Blockchain.Litecoin -> "litecoin"
Blockchain.RSK -> "rootstock"
Blockchain.Tezos -> "tezos"
Blockchain.XRP -> "ripple"
Blockchain.Dogecoin -> "dogecoin"
Blockchain.Tron, Blockchain.TronTestnet -> "tron"
Blockchain.Gnosis -> "xdai"
Blockchain.Polkadot, Blockchain.PolkadotTestnet -> "polkadot"
Blockchain.Kusama -> "kusama"
Blockchain.Unknown -> "unknown"
}
}

View file

@ -18,8 +18,7 @@ fun Card.supportedBlockchains(): List<Blockchain> {
Blockchain.fromCurve(EllipticCurve.Secp256k1)
}
else -> {
Blockchain.fromCurve(EllipticCurve.Secp256k1) +
Blockchain.fromCurve(EllipticCurve.Ed25519)
(Blockchain.fromCurve(EllipticCurve.Secp256k1) + Blockchain.fromCurve(EllipticCurve.Ed25519)).distinct()
}
}
val filtered = supportedBlockchains.filter { isTestCard == it.isTestnet() }

View file

@ -0,0 +1,20 @@
package com.tangem.domain.features
import com.google.common.truth.Truth
import com.tangem.blockchain.common.Blockchain
import com.tangem.domain.common.extensions.fromNetworkId
import com.tangem.domain.common.extensions.toNetworkId
import org.junit.Test
class BlockchainTests {
@Test
fun allNetworkIdsAreImplemented() {
val unimplementedIds = Blockchain.values()
.toMutableList()
.apply { remove(Blockchain.Unknown) }
.map { it to Blockchain.fromNetworkId(it.toNetworkId()) }
.mapNotNull { if(it.second == null) it.first else null }
Truth.assertThat(unimplementedIds).isEmpty()
}
}

View file

@ -1,16 +0,0 @@
package com.tangem.domain.features
import org.junit.Assert.assertEquals
import org.junit.Test
/**
* Example local unit test, which will execute on the development machine (host).
*
* See [testing documentation](http://d.android.com/tools/testing).
*/
class ExampleUnitTest {
@Test
fun addition_isCorrect() {
assertEquals(4, 2 + 2)
}
}