diff --git a/core/res/src/main/res/values/strings.xml b/core/res/src/main/res/values/strings.xml
index 8277d57104..91a906d6b8 100644
--- a/core/res/src/main/res/values/strings.xml
+++ b/core/res/src/main/res/values/strings.xml
@@ -2051,6 +2051,8 @@
Tangem Twin
This action is irreversible. You will not have access to the old wallet.
Tap the twin card with number %s and do not remove until the end of the operation
+ Top up
+ Topped up
Please try again later. If the issue persists, please contact support.
Something went wrong!
We\'ve encountered an error. Error code: %s. Please contact our support.
diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/transactions/TransactionItem.kt b/core/ui/src/main/java/com/tangem/core/ui/components/transactions/TransactionItem.kt
index a0c9c33daa..0cacb2de38 100644
--- a/core/ui/src/main/java/com/tangem/core/ui/components/transactions/TransactionItem.kt
+++ b/core/ui/src/main/java/com/tangem/core/ui/components/transactions/TransactionItem.kt
@@ -7,6 +7,7 @@ import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.PaddingValues
+import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
@@ -32,6 +33,8 @@ import androidx.compose.ui.text.style.TextDecoration
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.tooling.preview.Preview
import com.tangem.core.ui.R
+import com.tangem.core.ui.components.currency.icon.CurrencyIcon
+import com.tangem.core.ui.components.currency.icon.CurrencyIconState
import com.tangem.core.ui.components.icons.identicon.IdentIcon
import com.tangem.core.ui.components.transactions.state.TransactionItemUM
import com.tangem.core.ui.components.transactions.state.TransactionItemUM.Content.Direction
@@ -70,55 +73,92 @@ fun TransactionItem(state: TransactionItemUM, isBalanceHidden: Boolean, modifier
@Composable
private fun ContentItem(state: TransactionItemUM.Content, isBalanceHidden: Boolean, modifier: Modifier = Modifier) {
- val rowModifier = modifier
- .fillMaxWidth()
- .clickable(onClick = state.onClick)
- .testTag(TransactionHistoryItemTestTags.ITEM)
-
- TangemRowContainer(
- modifier = rowModifier,
- contentPadding = PaddingValues(
- horizontal = TangemTheme.dimens2.x4,
- vertical = TangemTheme.dimens2.x3,
- ),
+ Column(
+ modifier = modifier
+ .fillMaxWidth()
+ .clickable(onClick = state.onClick)
+ .testTag(TransactionHistoryItemTestTags.ITEM),
) {
- StatusCircle(
- iconRes = state.iconRes,
- status = state.status,
- modifier = Modifier
- .layoutId(TangemRowLayoutId.HEAD)
- .padding(end = TangemTheme.dimens2.x3)
- .size(TangemTheme.dimens2.x10)
- .testTag(TransactionHistoryItemTestTags.STATUS_PREFIX + state.status.testTagSuffix),
+ TangemRowContainer(
+ contentPadding = PaddingValues(
+ horizontal = TangemTheme.dimens2.x4,
+ vertical = TangemTheme.dimens2.x3,
+ ),
+ ) {
+ StatusCircle(
+ iconRes = state.iconRes,
+ status = state.status,
+ modifier = Modifier
+ .layoutId(TangemRowLayoutId.HEAD)
+ .padding(end = TangemTheme.dimens2.x3)
+ .size(TangemTheme.dimens2.x10)
+ .testTag(TransactionHistoryItemTestTags.STATUS_PREFIX + state.status.testTagSuffix),
+ )
+ TitleText(
+ title = state.title,
+ status = state.status,
+ modifier = Modifier
+ .layoutId(TangemRowLayoutId.START_TOP)
+ .testTag(TransactionHistoryItemTestTags.TITLE),
+ )
+ SubtitleText(
+ subtitle = state.subtitle,
+ status = state.status,
+ modifier = Modifier
+ .layoutId(TangemRowLayoutId.START_BOTTOM)
+ .padding(top = TangemTheme.dimens2.x0_5),
+ )
+ state.amount?.let { amount ->
+ AmountText(
+ amount = amount,
+ status = state.status,
+ isBalanceHidden = isBalanceHidden,
+ modifier = Modifier
+ .layoutId(TangemRowLayoutId.END_TOP)
+ .testTag(TransactionHistoryItemTestTags.AMOUNT),
+ )
+ }
+ CurrencyText(
+ symbol = state.currencySymbol,
+ modifier = Modifier
+ .layoutId(TangemRowLayoutId.END_BOTTOM)
+ .padding(top = TangemTheme.dimens2.x0_5)
+ .testTag(TransactionHistoryItemTestTags.CURRENCY),
+ )
+ }
+ state.warning?.let { warning ->
+ WarningLine(
+ warning = warning,
+ modifier = Modifier.padding(
+ start = TangemTheme.dimens2.x4,
+ end = TangemTheme.dimens2.x4,
+ bottom = TangemTheme.dimens2.x3,
+ ),
+ )
+ }
+ }
+}
+
+@Composable
+private fun WarningLine(warning: TextReference, modifier: Modifier = Modifier) {
+ val attention = TangemTheme.colors2.text.status.attention
+ Row(
+ modifier = modifier,
+ verticalAlignment = Alignment.CenterVertically,
+ horizontalArrangement = Arrangement.spacedBy(TangemTheme.dimens2.x2),
+ ) {
+ Icon(
+ painter = painterResource(R.drawable.ic_alert_triangle_20),
+ contentDescription = null,
+ tint = attention,
+ modifier = Modifier.size(TangemTheme.dimens2.x5),
)
- TitleText(
- title = state.title,
- status = state.status,
- modifier = Modifier
- .layoutId(TangemRowLayoutId.START_TOP)
- .testTag(TransactionHistoryItemTestTags.TITLE),
- )
- SubtitleText(
- subtitle = state.subtitle,
- status = state.status,
- modifier = Modifier
- .layoutId(TangemRowLayoutId.START_BOTTOM)
- .padding(top = TangemTheme.dimens2.x0_5),
- )
- AmountText(
- amount = state.amount,
- status = state.status,
- isBalanceHidden = isBalanceHidden,
- modifier = Modifier
- .layoutId(TangemRowLayoutId.END_TOP)
- .testTag(TransactionHistoryItemTestTags.AMOUNT),
- )
- CurrencyText(
- symbol = state.currencySymbol,
- modifier = Modifier
- .layoutId(TangemRowLayoutId.END_BOTTOM)
- .padding(top = TangemTheme.dimens2.x0_5)
- .testTag(TransactionHistoryItemTestTags.CURRENCY),
+ Text(
+ text = warning.resolveReference(),
+ color = attention,
+ style = TangemTheme.typography2.captionMedium12,
+ maxLines = 2,
+ overflow = TextOverflow.Ellipsis,
)
}
}
@@ -262,6 +302,21 @@ private fun SubtitleText(subtitle: ContentSubtitle, status: Status, modifier: Mo
modifier = Modifier.fillMaxSize(),
)
}
+ is ContentSubtitle.Asset -> InlineImageSubtitle(
+ template = stringResourceSafe(subtitle.direction.templateResId(), subtitle.symbol),
+ color = tertiary,
+ afterIconColor = if (isFailed) tertiary else primary,
+ modifier = modifier,
+ ) {
+ subtitle.icon?.let { iconState ->
+ CurrencyIcon(
+ state = iconState,
+ shouldDisplayNetwork = false,
+ withFixedSize = false,
+ modifier = Modifier.fillMaxSize(),
+ )
+ }
+ }
}
}
@@ -491,4 +546,73 @@ private fun Preview_TransactionItem_Swap() {
}
}
+@Preview(showBackground = true, widthDp = 360)
+@Preview(showBackground = true, widthDp = 360, uiMode = Configuration.UI_MODE_NIGHT_YES)
+@Composable
+private fun Preview_TransactionItem_Express() {
+ TangemThemePreviewRedesign {
+ PreviewColumn(
+ items = listOf(
+ TransactionItemUM.Content(
+ txHash = "exp-swap-u",
+ amount = "-390.00",
+ currencySymbol = "USDT",
+ time = "",
+ status = Status.Unconfirmed,
+ direction = Direction.OUTGOING,
+ onClick = {},
+ iconRes = R.drawable.ic_exchange_vertical_24,
+ title = stringReference("Swapping"),
+ subtitle = ContentSubtitle.Asset(
+ direction = ContentSubtitle.Direction.TO,
+ symbol = "POL",
+ icon = CurrencyIconState.CoinIcon(
+ url = null,
+ fallbackResId = R.drawable.ic_custom_token_44,
+ isGrayscale = false,
+ shouldShowCustomBadge = false,
+ ),
+ ),
+ timestamp = 0L,
+ warning = stringReference("KYC verification required by provider"),
+ ),
+ TransactionItemUM.Content(
+ txHash = "exp-onramp-c",
+ amount = "+0.006339",
+ currencySymbol = "BTC",
+ time = "",
+ status = Status.Confirmed,
+ direction = Direction.INCOMING,
+ onClick = {},
+ iconRes = R.drawable.ic_tangem_card_24,
+ title = stringReference("Topped up"),
+ subtitle = ContentSubtitle.Asset(
+ direction = ContentSubtitle.Direction.FROM,
+ symbol = "SEK",
+ icon = null,
+ ),
+ timestamp = 0L,
+ ),
+ TransactionItemUM.Content(
+ txHash = "exp-onramp-f",
+ amount = "0.006339",
+ currencySymbol = "BTC",
+ time = "",
+ status = Status.Failed,
+ direction = Direction.INCOMING,
+ onClick = {},
+ iconRes = R.drawable.ic_tangem_card_24,
+ title = stringReference("Top up failed"),
+ subtitle = ContentSubtitle.Asset(
+ direction = ContentSubtitle.Direction.FROM,
+ symbol = "SEK",
+ icon = null,
+ ),
+ timestamp = 0L,
+ ),
+ ),
+ )
+ }
+}
+
// endregion
\ No newline at end of file
diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/transactions/state/TransactionItemUM.kt b/core/ui/src/main/java/com/tangem/core/ui/components/transactions/state/TransactionItemUM.kt
index 1c3adfcd0e..7bce8ba673 100644
--- a/core/ui/src/main/java/com/tangem/core/ui/components/transactions/state/TransactionItemUM.kt
+++ b/core/ui/src/main/java/com/tangem/core/ui/components/transactions/state/TransactionItemUM.kt
@@ -3,6 +3,7 @@ package com.tangem.core.ui.components.transactions.state
import androidx.annotation.DrawableRes
import androidx.compose.runtime.Immutable
import androidx.compose.ui.graphics.Color
+import com.tangem.core.ui.components.currency.icon.CurrencyIconState
import com.tangem.core.ui.ds.image.DeviceIconUM
import com.tangem.core.ui.extensions.TextReference
@@ -22,12 +23,13 @@ sealed interface TransactionItemUM {
/**
* Content state.
*
- * @property amount signed numeric value, e.g. "+0.500913" / "-350.31"; no currency symbol embedded
+ * @property amount signed numeric value, e.g. "+0.500913" / "-350.31"; no currency symbol embedded.
+ * `null` hides the numeric value while [currencySymbol] still shows.
* @property currencySymbol currency symbol shown alongside [amount], e.g. "BTC", "USDT"
*/
data class Content(
override val txHash: String,
- val amount: String,
+ val amount: String?,
val currencySymbol: String,
val time: String,
val status: Status,
@@ -37,6 +39,7 @@ sealed interface TransactionItemUM {
val title: TextReference,
val subtitle: ContentSubtitle,
val timestamp: Long,
+ val warning: TextReference? = null,
) : TransactionItemUM {
@Immutable
@@ -95,6 +98,19 @@ sealed interface TransactionItemUM {
val deviceIconUM: DeviceIconUM,
) : ContentSubtitle
+ /**
+ * Counterparty asset ticker β renders as "to/from: ". Used for express rows
+ * (swap counterparty currency / onramp fiat), e.g. "to: β POL" or "from: πΈπͺ SEK".
+ *
+ * @property icon resolved counterparty currency icon, rendered via `CurrencyIcon`. `null` when no icon
+ * is available (e.g. onramp fiat carries no `CryptoCurrency`) β the ticker then renders without a leading icon.
+ */
+ data class Asset(
+ val direction: Direction,
+ val symbol: String,
+ val icon: CurrencyIconState?,
+ ) : ContentSubtitle
+
enum class Direction { TO, FROM }
}
diff --git a/data/txhistory/src/main/kotlin/com/tangem/data/txhistory/repository/converter/ExpressTxHistoryConverter.kt b/data/txhistory/src/main/kotlin/com/tangem/data/txhistory/repository/converter/ExpressTxHistoryConverter.kt
index 8e30176f4a..d911aa525b 100644
--- a/data/txhistory/src/main/kotlin/com/tangem/data/txhistory/repository/converter/ExpressTxHistoryConverter.kt
+++ b/data/txhistory/src/main/kotlin/com/tangem/data/txhistory/repository/converter/ExpressTxHistoryConverter.kt
@@ -55,13 +55,13 @@ internal class ExpressOnrampConverter : Converter it.activeStatus.isHidden
- else -> false
- }
- }.toPersistentList()
+
+ val expressTxsToDisplay = if (txHistoryFeatureToggles.isNewTxHistoryEnabled) {
+ persistentListOf()
+ } else {
+ expressTxs.filterNot {
+ when (it) {
+ is ExpressTransactionStateUM.OnrampUM -> it.activeStatus.isHidden
+ else -> false
+ }
+ }.toPersistentList()
+ }
return state.copy(
transactions = expressTxs,
transactionsToDisplay = expressTxsToDisplay,
diff --git a/features/txhistory/impl/src/main/kotlin/com/tangem/features/txhistory/converter/ExpressTxToTransactionItemUMConverter.kt b/features/txhistory/impl/src/main/kotlin/com/tangem/features/txhistory/converter/ExpressTxToTransactionItemUMConverter.kt
new file mode 100644
index 0000000000..46ade74b07
--- /dev/null
+++ b/features/txhistory/impl/src/main/kotlin/com/tangem/features/txhistory/converter/ExpressTxToTransactionItemUMConverter.kt
@@ -0,0 +1,191 @@
+package com.tangem.features.txhistory.converter
+
+import com.tangem.core.ui.components.transactions.state.TransactionItemUM
+import com.tangem.core.ui.components.transactions.state.TransactionItemUM.Content.Direction as RowDirection
+import com.tangem.core.ui.components.transactions.state.TransactionItemUM.Content.Status
+import com.tangem.common.ui.components.currency.icon.converter.CryptoCurrencyToIconStateConverter
+import com.tangem.core.ui.components.transactions.state.TransactionItemUM.ContentSubtitle
+import com.tangem.core.ui.components.transactions.state.TransactionItemUM.ContentSubtitle.Direction as SubtitleDirection
+import com.tangem.core.ui.extensions.TextReference
+import com.tangem.core.ui.extensions.resourceReference
+import com.tangem.core.ui.extensions.wrappedList
+import com.tangem.core.ui.format.bigdecimal.crypto
+import com.tangem.core.ui.format.bigdecimal.format
+import com.tangem.core.ui.utils.toTimeFormat
+import com.tangem.domain.express.models.ExpressExchangeStatus
+import com.tangem.domain.express.models.ExpressOnrampStatus
+import com.tangem.domain.models.currency.CryptoCurrency
+import com.tangem.domain.txhistory.model.ExpressTx
+import com.tangem.features.txhistory.impl.R
+import com.tangem.features.txhistory.utils.TxHistoryUiActions
+import com.tangem.utils.StringsSigns
+import com.tangem.utils.converter.Converter
+import java.math.BigDecimal
+
+/**
+ * Maps an [ExpressTx] (swap / onramp) row directly to [TransactionItemUM.Content].
+ *
+ * The viewed leg is [CryptoCurrency] ([currency], the token-details currency): outgoing swap shows the pay-in
+ * (`from`) amount with a minus, incoming swap / onramp shows the received (`to`) amount with a plus. The 26 typed
+ * express statuses collapse into the three [Status] buckets (those drive title/icon/amount colors in the row UI).
+ *
+ * The counterparty ticker symbol+icon come from the resolved [ExpressTransactionAsset.cryptoCurrency] (swap);
+ * onramp shows the real fiat code with no icon yet (fiat carries no `CryptoCurrency`). The row click opens the
+ * explorer.
+ */
+internal class ExpressTxToTransactionItemUMConverter(
+ private val currency: CryptoCurrency,
+ private val txHistoryUiActions: TxHistoryUiActions,
+) : Converter {
+
+ private val iconStateConverter = CryptoCurrencyToIconStateConverter()
+
+ override fun convert(value: ExpressTx): TransactionItemUM = when (value) {
+ is ExpressTx.Swap -> swapContent(value)
+ is ExpressTx.Onramp -> onrampContent(value)
+ }
+
+ private fun swapContent(swap: ExpressTx.Swap): TransactionItemUM.Content {
+ val status = swap.tx.status.toUiStatus()
+ val viewedAmount = if (swap.isOutgoing) swap.tx.fromAsset.amount else swap.tx.toAsset.amount
+ val counterparty = if (swap.isOutgoing) swap.tx.toAsset else swap.tx.fromAsset
+ val prefix = when {
+ status is Status.Failed -> ""
+ swap.isOutgoing -> StringsSigns.MINUS
+ else -> StringsSigns.PLUS
+ }
+ return buildContent(
+ tx = swap,
+ status = status,
+ amount = formatAmount(viewedAmount, prefix),
+ direction = if (swap.isOutgoing) RowDirection.OUTGOING else RowDirection.INCOMING,
+ iconRes = R.drawable.ic_exchange_vertical_24,
+ title = swapTitle(status),
+ subtitle = ContentSubtitle.Asset(
+ direction = if (swap.isOutgoing) SubtitleDirection.TO else SubtitleDirection.FROM,
+ symbol = counterparty.cryptoCurrency?.symbol ?: counterparty.id.networkId,
+ icon = counterparty.cryptoCurrency?.let(iconStateConverter::convert),
+ ),
+ // TODO: replace null to warning logic.
+ warning = null,
+ )
+ }
+
+ private fun onrampContent(onramp: ExpressTx.Onramp): TransactionItemUM.Content {
+ val status = onramp.tx.status.toUiStatus()
+ val prefix = when {
+ status is Status.Failed -> ""
+ status is Status.Confirmed -> StringsSigns.PLUS
+ else -> StringsSigns.TILDE_SIGN
+ }
+ return buildContent(
+ tx = onramp,
+ status = status,
+ amount = formatAmount(onramp.tx.toAsset.amount, prefix),
+ direction = RowDirection.INCOMING,
+ iconRes = R.drawable.ic_tangem_card_24,
+ title = onrampTitle(status),
+ subtitle = ContentSubtitle.Asset(
+ direction = SubtitleDirection.FROM,
+ symbol = onramp.tx.fromFiat.currencySymbol,
+ // TODO: fiat carries no OnrampCurrency, so no icon yet β render with a fiat country flag once available.
+ icon = null,
+ ),
+ // TODO: replace null to warning logic.
+ warning = null,
+ )
+ }
+
+ @Suppress("LongParameterList")
+ private fun buildContent(
+ tx: ExpressTx,
+ status: Status,
+ amount: String?,
+ direction: RowDirection,
+ iconRes: Int,
+ title: TextReference,
+ subtitle: ContentSubtitle,
+ warning: TextReference?,
+ ): TransactionItemUM.Content {
+ val explorerHash = tx.matchHash ?: tx.txId
+ return TransactionItemUM.Content(
+ txHash = explorerHash,
+ amount = amount,
+ currencySymbol = currency.symbol,
+ time = tx.timestampMillis.toTimeFormat(),
+ status = status,
+ direction = direction,
+ onClick = { txHistoryUiActions.openTxInExplorer(explorerHash) },
+ iconRes = iconRes,
+ title = title,
+ subtitle = subtitle,
+ timestamp = tx.timestampMillis,
+ warning = warning,
+ )
+ }
+
+ private fun formatAmount(amount: BigDecimal?, prefix: String): String? =
+ amount?.let { prefix + it.format { crypto(symbol = "", decimals = currency.decimals) }.trim() }
+
+ private fun swapTitle(status: Status): TextReference = when (status) {
+ is Status.Confirmed -> resourceReference(R.string.common_swapped)
+ is Status.Unconfirmed -> resourceReference(R.string.common_swapping)
+ is Status.Failed ->
+ resourceReference(R.string.common_action_failed, wrappedList(resourceReference(R.string.common_swapping)))
+ }
+
+ private fun onrampTitle(status: Status): TextReference = when (status) {
+ is Status.Confirmed -> resourceReference(R.string.tx_history_onramp_topped_up)
+ is Status.Unconfirmed -> resourceReference(R.string.tx_history_onramp_top_up)
+ is Status.Failed -> resourceReference(
+ R.string.common_action_failed,
+ wrappedList(resourceReference(R.string.tx_history_onramp_top_up)),
+ )
+ }
+}
+
+// region Status mapping
+
+/**
+ * Collapses the typed swap status into a UI [Status] bucket: the single success state ([Finished][Confirmed]),
+ * the failure/return states ([Failed]/[TxFailed]/[Refunded]/[Expired]/[Unknown]) β Failed, everything in flight
+ * (incl. [Verifying] and [Paused]) β Unconfirmed.
+ */
+private fun ExpressExchangeStatus.toUiStatus(): Status = when (this) {
+ ExpressExchangeStatus.Finished -> Status.Confirmed
+ ExpressExchangeStatus.Failed,
+ ExpressExchangeStatus.TxFailed,
+ ExpressExchangeStatus.Refunded,
+ ExpressExchangeStatus.Expired,
+ ExpressExchangeStatus.Unknown,
+ -> Status.Failed
+ ExpressExchangeStatus.Preview,
+ ExpressExchangeStatus.Created,
+ ExpressExchangeStatus.ExchangeTxSent,
+ ExpressExchangeStatus.Waiting,
+ ExpressExchangeStatus.WaitingTxHash,
+ ExpressExchangeStatus.Confirming,
+ ExpressExchangeStatus.Exchanging,
+ ExpressExchangeStatus.Sending,
+ ExpressExchangeStatus.Verifying,
+ ExpressExchangeStatus.Paused,
+ -> Status.Unconfirmed
+}
+
+private fun ExpressOnrampStatus.toUiStatus(): Status = when (this) {
+ ExpressOnrampStatus.Finished -> Status.Confirmed
+ ExpressOnrampStatus.Failed,
+ ExpressOnrampStatus.Expired,
+ ExpressOnrampStatus.Unknown,
+ -> Status.Failed
+ ExpressOnrampStatus.Created,
+ ExpressOnrampStatus.WaitingForPayment,
+ ExpressOnrampStatus.PaymentProcessing,
+ ExpressOnrampStatus.Verifying,
+ ExpressOnrampStatus.Paid,
+ ExpressOnrampStatus.Sending,
+ ExpressOnrampStatus.Paused,
+ -> Status.Unconfirmed
+}
+
+// endregion
\ No newline at end of file
diff --git a/features/txhistory/impl/src/main/kotlin/com/tangem/features/txhistory/converter/TxHistoryInfoToTransactionItemUMConverter.kt b/features/txhistory/impl/src/main/kotlin/com/tangem/features/txhistory/converter/TxHistoryInfoToTransactionItemUMConverter.kt
index 574f6658c0..a52b20b829 100644
--- a/features/txhistory/impl/src/main/kotlin/com/tangem/features/txhistory/converter/TxHistoryInfoToTransactionItemUMConverter.kt
+++ b/features/txhistory/impl/src/main/kotlin/com/tangem/features/txhistory/converter/TxHistoryInfoToTransactionItemUMConverter.kt
@@ -4,21 +4,20 @@ import com.tangem.core.ui.components.transactions.state.TransactionItemUM
import com.tangem.domain.txhistory.model.ExpressTx
import com.tangem.domain.txhistory.model.OnChainTx
import com.tangem.domain.txhistory.model.TxHistoryInfo
-import com.tangem.features.txhistory.utils.toSyntheticTxInfo
import com.tangem.utils.converter.Converter
/**
- * Converts a merged [TxHistoryInfo] row to [TransactionItemUM], delegating to the on-chain
- * [TxHistoryItemToTransactionItemUMConverter]: on-chain rows convert their `TxInfo` directly, express
- * rows convert a synthesized `TxInfo` view (see [toSyntheticTxInfo]).
+ * Converts a merged [TxHistoryInfo] row to [TransactionItemUM]: on-chain rows convert their `TxInfo` via
+ * [TxHistoryItemToTransactionItemUMConverter]; express rows map directly via [ExpressTxToTransactionItemUMConverter].
*/
internal class TxHistoryInfoToTransactionItemUMConverter(
private val txInfoConverter: TxHistoryItemToTransactionItemUMConverter,
+ private val expressConverter: ExpressTxToTransactionItemUMConverter,
) : Converter {
override fun convert(value: TxHistoryInfo): TransactionItemUM = when (value) {
is OnChainTx -> convertOnChain(value)
- is ExpressTx -> txInfoConverter.convert(value.toSyntheticTxInfo())
+ is ExpressTx -> expressConverter.convert(value)
}
private fun convertOnChain(value: OnChainTx): TransactionItemUM = when (value) {
diff --git a/features/txhistory/impl/src/main/kotlin/com/tangem/features/txhistory/model/TxHistoryModel.kt b/features/txhistory/impl/src/main/kotlin/com/tangem/features/txhistory/model/TxHistoryModel.kt
index b64a5d10d9..9826f149c0 100644
--- a/features/txhistory/impl/src/main/kotlin/com/tangem/features/txhistory/model/TxHistoryModel.kt
+++ b/features/txhistory/impl/src/main/kotlin/com/tangem/features/txhistory/model/TxHistoryModel.kt
@@ -28,6 +28,7 @@ import com.tangem.domain.txhistory.usecase.GetTxHistoryItemsCountUseCase
import com.tangem.domain.wallets.usecase.GetWalletIconUseCase
import com.tangem.domain.txhistory.TxHistoryFeatureToggles
import com.tangem.features.txhistory.component.TxHistoryComponent
+import com.tangem.features.txhistory.converter.ExpressTxToTransactionItemUMConverter
import com.tangem.features.txhistory.converter.TxHistoryInfoToTransactionItemUMConverter
import com.tangem.features.txhistory.converter.TxHistoryItemToTransactionItemUMConverter
import com.tangem.features.txhistory.converter.TxHistoryItemToTransactionStateConverter
@@ -39,6 +40,7 @@ import com.tangem.features.txhistory.utils.HistoryTxListManager
import com.tangem.features.txhistory.utils.TxHistoryListManager
import com.tangem.features.txhistory.utils.TxHistoryUiActions
import com.tangem.pagination.PaginationStatus
+import com.tangem.utils.annotations.RemoveWithToggle
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import com.tangem.utils.logging.TangemLogger
import kotlinx.collections.immutable.ImmutableList
@@ -100,9 +102,11 @@ internal class TxHistoryModel @Inject constructor(
emptyFlow()
}
+ @RemoveWithToggle("APP_REDESIGN_ENABLED")
private val legacyTxHistoryItemConverter =
TxHistoryItemToTransactionStateConverter(currency = params.currency, txHistoryUiActions = this)
+ @RemoveWithToggle("AND_15767_NEW_TX_HISTORY_ENABLED")
private val txHistoryListManager: TxHistoryListManager? = if (!txHistoryFeatureToggle.isNewTxHistoryEnabled) {
TxHistoryListManager(
repository = repository,
@@ -193,7 +197,6 @@ internal class TxHistoryModel @Inject constructor(
}
}
- // Temporary: express rows are mapped to UI via a synthesized TxInfo (see ExpressTx.toSyntheticTxInfo).
private fun buildUiItems(
merged: List,
lookup: TxHistoryLookupContext,
@@ -204,6 +207,10 @@ internal class TxHistoryModel @Inject constructor(
txHistoryUiActions = this,
lookupContext = lookup,
),
+ expressConverter = ExpressTxToTransactionItemUMConverter(
+ currency = params.currency,
+ txHistoryUiActions = this,
+ ),
)
val items = mutableListOf()
diff --git a/features/txhistory/impl/src/main/kotlin/com/tangem/features/txhistory/utils/TxHistoryInfoMerger.kt b/features/txhistory/impl/src/main/kotlin/com/tangem/features/txhistory/utils/TxHistoryInfoMerger.kt
index e310aeca94..1830b1be74 100644
--- a/features/txhistory/impl/src/main/kotlin/com/tangem/features/txhistory/utils/TxHistoryInfoMerger.kt
+++ b/features/txhistory/impl/src/main/kotlin/com/tangem/features/txhistory/utils/TxHistoryInfoMerger.kt
@@ -1,7 +1,5 @@
package com.tangem.features.txhistory.utils
-import com.tangem.domain.express.models.ExpressExchangeStatus
-import com.tangem.domain.express.models.ExpressOnrampStatus
import com.tangem.domain.models.network.TxInfo
import com.tangem.domain.txhistory.model.ExpressTx
import com.tangem.domain.txhistory.model.OnChainTx
@@ -50,48 +48,4 @@ private fun ExpressTx.withMatchedTxInfo(txInfo: TxInfo): ExpressTx {
is ExpressTx.Swap -> copy(txInfo = matched)
is ExpressTx.Onramp -> copy(txInfo = matched)
}
-}
-
-/**
- * Synthesizes a [TxInfo] view of an express op so it can be rendered by the existing
- * [com.tangem.features.txhistory.converter.TxHistoryItemToTransactionItemUMConverter]. Rendered as a
- * [TxInfo.TransactionType.Swap] for now (onramp included). The amount is the viewed-currency leg.
- */
-internal fun ExpressTx.toSyntheticTxInfo(): TxInfo {
- val viewedAmount = when (this) {
- is ExpressTx.Swap -> if (isOutgoing) tx.fromAsset.amount else tx.toAsset.amount
- is ExpressTx.Onramp -> tx.toAsset.amount
- }
- val isOutgoing = when (this) {
- is ExpressTx.Swap -> this.isOutgoing
- is ExpressTx.Onramp -> false
- }
- return TxInfo(
- // matchHash is the on-chain hash (== the matched leg's hash, enables the explorer link); else txId.
- txHash = matchHash ?: txId,
- timestampInMillis = timestampMillis,
- isOutgoing = isOutgoing,
- destinationType = TxInfo.DestinationType.Single(TxInfo.AddressType.User(address = "")),
- sourceType = TxInfo.SourceType.Single(address = ""),
- interactionAddressType = null,
- status = toTransactionStatus(),
- type = TxInfo.TransactionType.Swap,
- amount = viewedAmount,
- )
-}
-
-/**
- * Maps the typed express status to the on-chain-shaped [TxInfo.TransactionStatus] used by the UI:
- * the single success state (`Finished`) β Confirmed, any other terminal state β Failed, in-progress β Unconfirmed.
- */
-private fun ExpressTx.toTransactionStatus(): TxInfo.TransactionStatus {
- val isFinished = when (this) {
- is ExpressTx.Swap -> tx.status == ExpressExchangeStatus.Finished
- is ExpressTx.Onramp -> tx.status == ExpressOnrampStatus.Finished
- }
- return when {
- isFinished -> TxInfo.TransactionStatus.Confirmed
- isTerminal -> TxInfo.TransactionStatus.Failed
- else -> TxInfo.TransactionStatus.Unconfirmed
- }
}
\ No newline at end of file
diff --git a/features/txhistory/impl/src/test/kotlin/com/tangem/features/txhistory/converter/ExpressTxToTransactionItemUMConverterTest.kt b/features/txhistory/impl/src/test/kotlin/com/tangem/features/txhistory/converter/ExpressTxToTransactionItemUMConverterTest.kt
new file mode 100644
index 0000000000..ee4a06fb0f
--- /dev/null
+++ b/features/txhistory/impl/src/test/kotlin/com/tangem/features/txhistory/converter/ExpressTxToTransactionItemUMConverterTest.kt
@@ -0,0 +1,289 @@
+package com.tangem.features.txhistory.converter
+
+import com.google.common.truth.Truth.assertThat
+import com.google.common.truth.Truth.assertWithMessage
+import com.tangem.core.ui.components.transactions.state.TransactionItemUM
+import com.tangem.core.ui.components.transactions.state.TransactionItemUM.Content.Status
+import com.tangem.core.ui.components.transactions.state.TransactionItemUM.ContentSubtitle
+import com.tangem.core.ui.extensions.resourceReference
+import com.tangem.domain.express.models.ExchangeTransaction
+import com.tangem.domain.express.models.ExpressAsset.ID as ExpressAssetId
+import com.tangem.domain.express.models.ExpressExchangeStatus
+import com.tangem.domain.express.models.ExpressOnrampStatus
+import com.tangem.domain.express.models.ExpressTransactionAsset
+import com.tangem.domain.express.models.OnrampTransaction
+import com.tangem.domain.models.currency.CryptoCurrency
+import com.tangem.domain.models.network.Network
+import com.tangem.domain.tokens.model.Amount
+import com.tangem.domain.tokens.model.AmountType
+import com.tangem.domain.txhistory.model.ExpressTx
+import com.tangem.features.txhistory.impl.R
+import com.tangem.features.txhistory.utils.TxHistoryUiActions
+import io.mockk.mockk
+import io.mockk.verify
+import org.junit.jupiter.api.Test
+import org.junit.jupiter.api.TestInstance
+import java.math.BigDecimal
+
+@TestInstance(TestInstance.Lifecycle.PER_CLASS)
+internal class ExpressTxToTransactionItemUMConverterTest {
+
+ private val txHistoryUiActions: TxHistoryUiActions = mockk(relaxed = true)
+ private val coin: CryptoCurrency.Coin = createCoin(symbol = "ETH", decimals = 18)
+
+ private val converter = ExpressTxToTransactionItemUMConverter(
+ currency = coin,
+ txHistoryUiActions = txHistoryUiActions,
+ )
+
+ // region Status β bucket
+
+ @Test
+ fun `GIVEN every swap status WHEN convert THEN mapped to expected status bucket`() {
+ val cases = mapOf(
+ ExpressExchangeStatus.Finished to Status.Confirmed,
+ ExpressExchangeStatus.Failed to Status.Failed,
+ ExpressExchangeStatus.TxFailed to Status.Failed,
+ ExpressExchangeStatus.Refunded to Status.Failed,
+ ExpressExchangeStatus.Expired to Status.Failed,
+ ExpressExchangeStatus.Unknown to Status.Failed,
+ ExpressExchangeStatus.Preview to Status.Unconfirmed,
+ ExpressExchangeStatus.Created to Status.Unconfirmed,
+ ExpressExchangeStatus.ExchangeTxSent to Status.Unconfirmed,
+ ExpressExchangeStatus.Waiting to Status.Unconfirmed,
+ ExpressExchangeStatus.WaitingTxHash to Status.Unconfirmed,
+ ExpressExchangeStatus.Confirming to Status.Unconfirmed,
+ ExpressExchangeStatus.Exchanging to Status.Unconfirmed,
+ ExpressExchangeStatus.Sending to Status.Unconfirmed,
+ ExpressExchangeStatus.Verifying to Status.Unconfirmed,
+ ExpressExchangeStatus.Paused to Status.Unconfirmed,
+ )
+ // every enum entry is covered (guards against new statuses silently falling through)
+ assertThat(cases.keys).containsExactlyElementsIn(ExpressExchangeStatus.entries)
+
+ cases.forEach { (status, expected) ->
+ val result = converter.convert(createSwap(status = status)) as TransactionItemUM.Content
+ assertWithMessage(status.name).that(result.status).isEqualTo(expected)
+ }
+ }
+
+ @Test
+ fun `GIVEN every onramp status WHEN convert THEN mapped to expected status bucket`() {
+ val cases = mapOf(
+ ExpressOnrampStatus.Finished to Status.Confirmed,
+ ExpressOnrampStatus.Failed to Status.Failed,
+ ExpressOnrampStatus.Expired to Status.Failed,
+ ExpressOnrampStatus.Unknown to Status.Failed,
+ ExpressOnrampStatus.Created to Status.Unconfirmed,
+ ExpressOnrampStatus.WaitingForPayment to Status.Unconfirmed,
+ ExpressOnrampStatus.PaymentProcessing to Status.Unconfirmed,
+ ExpressOnrampStatus.Verifying to Status.Unconfirmed,
+ ExpressOnrampStatus.Paid to Status.Unconfirmed,
+ ExpressOnrampStatus.Sending to Status.Unconfirmed,
+ ExpressOnrampStatus.Paused to Status.Unconfirmed,
+ )
+ assertThat(cases.keys).containsExactlyElementsIn(ExpressOnrampStatus.entries)
+
+ cases.forEach { (status, expected) ->
+ val result = converter.convert(createOnramp(status = status)) as TransactionItemUM.Content
+ assertWithMessage(status.name).that(result.status).isEqualTo(expected)
+ }
+ }
+
+ // endregion
+
+ // region Amount sign / prefix
+
+ @Test
+ fun `GIVEN outgoing swap WHEN convert THEN amount is negative from-leg`() {
+ val result = converter.convert(
+ createSwap(status = ExpressExchangeStatus.Waiting, isOutgoing = true),
+ ) as TransactionItemUM.Content
+
+ assertThat(result.direction).isEqualTo(TransactionItemUM.Content.Direction.OUTGOING)
+ assertThat(result.amount).startsWith("-")
+ assertThat(result.amount).contains("1.5")
+ }
+
+ @Test
+ fun `GIVEN incoming swap WHEN convert THEN amount is positive to-leg`() {
+ val result = converter.convert(
+ createSwap(status = ExpressExchangeStatus.Waiting, isOutgoing = false),
+ ) as TransactionItemUM.Content
+
+ assertThat(result.direction).isEqualTo(TransactionItemUM.Content.Direction.INCOMING)
+ assertThat(result.amount).startsWith("+")
+ assertThat(result.amount).contains("0.001")
+ }
+
+ @Test
+ fun `GIVEN finished onramp WHEN convert THEN amount prefixed with plus`() {
+ val result = converter.convert(createOnramp(status = ExpressOnrampStatus.Finished)) as TransactionItemUM.Content
+ assertThat(result.amount).startsWith("+")
+ }
+
+ @Test
+ fun `GIVEN in-progress onramp WHEN convert THEN amount prefixed with tilde`() {
+ val result = converter.convert(createOnramp(status = ExpressOnrampStatus.Sending)) as TransactionItemUM.Content
+ assertThat(result.amount).startsWith("~")
+ }
+
+ @Test
+ fun `GIVEN failed onramp WHEN convert THEN amount has no sign prefix`() {
+ val result = converter.convert(createOnramp(status = ExpressOnrampStatus.Failed)) as TransactionItemUM.Content
+ assertThat(requireNotNull(result.amount).first())
+ .isIn(listOf('0', '1', '2', '3', '4', '5', '6', '7', '8', '9'))
+ }
+
+ @Test
+ fun `GIVEN swap with null viewed amount WHEN convert THEN amount is null`() {
+ val result = converter.convert(
+ createSwap(status = ExpressExchangeStatus.Waiting, isOutgoing = true, fromAmount = null),
+ ) as TransactionItemUM.Content
+
+ assertThat(result.amount).isNull()
+ }
+
+ @Test
+ fun `GIVEN onramp with null amount WHEN convert THEN amount is null`() {
+ val result = converter.convert(
+ createOnramp(status = ExpressOnrampStatus.Sending, toAmount = null),
+ ) as TransactionItemUM.Content
+
+ assertThat(result.amount).isNull()
+ }
+
+ // endregion
+
+ // region Title / subtitle / warning / click
+
+ @Test
+ fun `GIVEN swap statuses WHEN convert THEN status-aware title`() {
+ val swapping = converter.convert(createSwap(status = ExpressExchangeStatus.Waiting)) as TransactionItemUM.Content
+ val swapped = converter.convert(createSwap(status = ExpressExchangeStatus.Finished)) as TransactionItemUM.Content
+
+ assertThat(swapping.title).isEqualTo(resourceReference(R.string.common_swapping))
+ assertThat(swapped.title).isEqualTo(resourceReference(R.string.common_swapped))
+ }
+
+ @Test
+ fun `GIVEN onramp statuses WHEN convert THEN status-aware title`() {
+ val topUp = converter.convert(createOnramp(status = ExpressOnrampStatus.Sending)) as TransactionItemUM.Content
+ val toppedUp = converter.convert(createOnramp(status = ExpressOnrampStatus.Finished)) as TransactionItemUM.Content
+
+ assertThat(topUp.title).isEqualTo(resourceReference(R.string.tx_history_onramp_top_up))
+ assertThat(toppedUp.title).isEqualTo(resourceReference(R.string.tx_history_onramp_topped_up))
+ }
+
+ @Test
+ fun `GIVEN outgoing swap WHEN convert THEN subtitle shows TO counterparty ticker`() {
+ val result = converter.convert(
+ createSwap(status = ExpressExchangeStatus.Waiting, isOutgoing = true),
+ ) as TransactionItemUM.Content
+
+ val subtitle = result.subtitle as ContentSubtitle.Asset
+ assertThat(subtitle.direction).isEqualTo(ContentSubtitle.Direction.TO)
+ assertThat(subtitle.symbol).isEqualTo("btc") // mock: counterparty (to-leg) networkId
+ }
+
+ @Test
+ fun `GIVEN onramp WHEN convert THEN subtitle shows FROM fiat code`() {
+ val result = converter.convert(createOnramp(status = ExpressOnrampStatus.Sending)) as TransactionItemUM.Content
+
+ val subtitle = result.subtitle as ContentSubtitle.Asset
+ assertThat(subtitle.direction).isEqualTo(ContentSubtitle.Direction.FROM)
+ assertThat(subtitle.symbol).isEqualTo("SEK")
+ }
+
+ @Test
+ fun `GIVEN matched on-chain leg WHEN row clicked THEN opens explorer by match hash`() {
+ val result = converter.convert(
+ createSwap(status = ExpressExchangeStatus.Waiting, matchHash = "0xhash", isOutgoing = true),
+ ) as TransactionItemUM.Content
+
+ result.onClick()
+
+ verify { txHistoryUiActions.openTxInExplorer("0xhash") }
+ }
+
+ // endregion
+
+ private fun createSwap(
+ status: ExpressExchangeStatus,
+ matchHash: String? = null,
+ isOutgoing: Boolean = true,
+ fromAmount: BigDecimal? = BigDecimal("1.5"),
+ toAmount: BigDecimal? = BigDecimal("0.001"),
+ ) = ExpressTx.Swap(
+ tx = ExchangeTransaction(
+ txId = "tx-1",
+ status = status,
+ createdAtMillis = 100,
+ provider = null,
+ payinHash = matchHash.takeIf { isOutgoing },
+ payoutHash = matchHash.takeUnless { isOutgoing },
+ fromAsset = ExpressTransactionAsset(
+ id = ExpressAssetId(networkId = "eth", contractAddress = "0"),
+ amount = fromAmount,
+ decimals = 18,
+ ),
+ toAsset = ExpressTransactionAsset(
+ id = ExpressAssetId(networkId = "btc", contractAddress = "0xt"),
+ amount = toAmount,
+ decimals = 8,
+ ),
+ ),
+ isOutgoing = isOutgoing,
+ txInfo = null,
+ )
+
+ private fun createOnramp(
+ status: ExpressOnrampStatus,
+ toAmount: BigDecimal? = BigDecimal("0.006339"),
+ ) = ExpressTx.Onramp(
+ tx = OnrampTransaction(
+ txId = "tx-2",
+ status = status,
+ createdAtMillis = 100,
+ provider = null,
+ payoutHash = null,
+ fromFiat = Amount(
+ currencySymbol = "SEK",
+ value = BigDecimal("100"),
+ decimals = 2,
+ type = AmountType.FiatType(code = "SEK"),
+ ),
+ toAsset = ExpressTransactionAsset(
+ id = ExpressAssetId(networkId = "btc", contractAddress = "0"),
+ amount = toAmount,
+ decimals = 8,
+ ),
+ ),
+ txInfo = null,
+ )
+
+ private fun createCoin(symbol: String, decimals: Int): CryptoCurrency.Coin = CryptoCurrency.Coin(
+ id = CryptoCurrency.ID(
+ prefix = CryptoCurrency.ID.Prefix.COIN_PREFIX,
+ body = CryptoCurrency.ID.Body.NetworkId(rawId = "ethereum"),
+ suffix = CryptoCurrency.ID.Suffix.RawID(rawId = "ethereum"),
+ ),
+ network = Network(
+ id = Network.ID(value = "ethereum", derivationPath = Network.DerivationPath.None),
+ name = "Ethereum",
+ currencySymbol = symbol,
+ derivationPath = Network.DerivationPath.None,
+ isTestnet = false,
+ standardType = Network.StandardType.ERC20,
+ hasFiatFeeRate = true,
+ canHandleTokens = true,
+ transactionExtrasType = Network.TransactionExtrasType.NONE,
+ nameResolvingType = Network.NameResolvingType.NONE,
+ ),
+ name = "Ethereum",
+ symbol = symbol,
+ decimals = decimals,
+ iconUrl = null,
+ isCustom = false,
+ )
+}
\ No newline at end of file
diff --git a/features/txhistory/impl/src/test/kotlin/com/tangem/features/txhistory/converter/TxHistoryItemToTransactionItemUMConverterTest.kt b/features/txhistory/impl/src/test/kotlin/com/tangem/features/txhistory/converter/TxHistoryItemToTransactionItemUMConverterTest.kt
index ae76d9a485..ed577e236b 100644
--- a/features/txhistory/impl/src/test/kotlin/com/tangem/features/txhistory/converter/TxHistoryItemToTransactionItemUMConverterTest.kt
+++ b/features/txhistory/impl/src/test/kotlin/com/tangem/features/txhistory/converter/TxHistoryItemToTransactionItemUMConverterTest.kt
@@ -161,8 +161,8 @@ internal class TxHistoryItemToTransactionItemUMConverterTest {
assertThat(result.subtitle).isEqualTo(
ContentSubtitle.Plain(resRef(R.string.transaction_history_earned_from_stake)),
)
- assertThat(result.amount.startsWith(StringsSigns.PLUS)).isFalse()
- assertThat(result.amount.startsWith(StringsSigns.MINUS)).isFalse()
+ assertThat(result.amount!!.startsWith(StringsSigns.PLUS)).isFalse()
+ assertThat(result.amount!!.startsWith(StringsSigns.MINUS)).isFalse()
}
@Test
@@ -514,7 +514,7 @@ internal class TxHistoryItemToTransactionItemUMConverterTest {
val result = coinConverter.convert(tx) as TransactionItemUM.Content
- assertThat(result.amount.startsWith(StringsSigns.MINUS)).isTrue()
+ assertThat(result.amount!!.startsWith(StringsSigns.MINUS)).isTrue()
}
@Test
@@ -528,7 +528,7 @@ internal class TxHistoryItemToTransactionItemUMConverterTest {
val result = coinConverter.convert(tx) as TransactionItemUM.Content
- assertThat(result.amount.startsWith(StringsSigns.PLUS)).isTrue()
+ assertThat(result.amount!!.startsWith(StringsSigns.PLUS)).isTrue()
}
@Test
@@ -543,8 +543,8 @@ internal class TxHistoryItemToTransactionItemUMConverterTest {
val result = coinConverter.convert(tx) as TransactionItemUM.Content
- assertThat(result.amount.startsWith(StringsSigns.MINUS)).isFalse()
- assertThat(result.amount.startsWith(StringsSigns.PLUS)).isFalse()
+ assertThat(result.amount!!.startsWith(StringsSigns.MINUS)).isFalse()
+ assertThat(result.amount!!.startsWith(StringsSigns.PLUS)).isFalse()
}
@Test
@@ -558,8 +558,8 @@ internal class TxHistoryItemToTransactionItemUMConverterTest {
val result = coinConverter.convert(tx) as TransactionItemUM.Content
- assertThat(result.amount.startsWith(StringsSigns.MINUS)).isFalse()
- assertThat(result.amount.startsWith(StringsSigns.PLUS)).isFalse()
+ assertThat(result.amount!!.startsWith(StringsSigns.MINUS)).isFalse()
+ assertThat(result.amount!!.startsWith(StringsSigns.PLUS)).isFalse()
}
// endregion
diff --git a/features/txhistory/impl/src/test/kotlin/com/tangem/features/txhistory/utils/TxHistoryInfoMergerTest.kt b/features/txhistory/impl/src/test/kotlin/com/tangem/features/txhistory/utils/TxHistoryInfoMergerTest.kt
index 7413a5ae9f..f4a022d2c1 100644
--- a/features/txhistory/impl/src/test/kotlin/com/tangem/features/txhistory/utils/TxHistoryInfoMergerTest.kt
+++ b/features/txhistory/impl/src/test/kotlin/com/tangem/features/txhistory/utils/TxHistoryInfoMergerTest.kt
@@ -85,21 +85,6 @@ internal class TxHistoryInfoMergerTest {
assertThat(result.map { it.timestampMillis }).containsExactly(200L, 100L).inOrder()
}
- @Test
- fun `GIVEN outgoing swap WHEN toSyntheticTxInfo THEN viewed from-leg amount and swap type`() {
- // Arrange
- val swap = createSwap(matchHash = "missing", status = ExpressExchangeStatus.Waiting, isOutgoing = true)
-
- // Act
- val txInfo = swap.toSyntheticTxInfo()
-
- // Assert
- assertThat(txInfo.isOutgoing).isTrue()
- assertThat(txInfo.amount).isEqualTo(BigDecimal("1.5"))
- assertThat(txInfo.type).isEqualTo(TxInfo.TransactionType.Swap)
- assertThat(txInfo.status).isEqualTo(TxInfo.TransactionStatus.Unconfirmed)
- }
-
private fun createTxInfo(txHash: String, timestamp: Long) = TxInfo(
txHash = txHash,
timestampInMillis = timestamp,