diff --git a/core/res/src/main/res/values/strings.xml b/core/res/src/main/res/values/strings.xml
index b54a4ae79a..f0a128eed2 100644
--- a/core/res/src/main/res/values/strings.xml
+++ b/core/res/src/main/res/values/strings.xml
@@ -2688,6 +2688,8 @@
%1$s withdrawn from Aave
Yield Mode initialized
Yield Mode reactivated
+ Returned
+ Supplied
Supply to Aave
%1$s supplied to Aave
Withdraw from Aave
diff --git a/features/txhistory/impl/src/main/kotlin/com/tangem/features/txhistory/converter/OnChainTxToDetailsUMConverter.kt b/features/txhistory/impl/src/main/kotlin/com/tangem/features/txhistory/converter/OnChainTxToDetailsUMConverter.kt
index 4b4d4f1c14..708a11ce0a 100644
--- a/features/txhistory/impl/src/main/kotlin/com/tangem/features/txhistory/converter/OnChainTxToDetailsUMConverter.kt
+++ b/features/txhistory/impl/src/main/kotlin/com/tangem/features/txhistory/converter/OnChainTxToDetailsUMConverter.kt
@@ -27,6 +27,9 @@ import com.tangem.utils.toBriefAddressFormat
import kotlinx.collections.immutable.ImmutableList
import kotlinx.collections.immutable.toImmutableList
+/** Website the yield-supply protocol row links to — the hard-wired Aave integration's home page. */
+private const val AAVE_WEBSITE = "https://aave.com/"
+
/**
* Converts an on-chain [TxInfo] to the [TxHistoryDetailsUM.SingleAsset] details card (Receive / Send / Transfer /
* staking / yield-supply). A two-asset swap always surfaces as an [com.tangem.domain.txhistory.model.ExpressTx.Swap]
@@ -62,14 +65,17 @@ internal class OnChainTxToDetailsUMConverter(
/**
* Protocol row of a yield-supply tx: the DeFi protocol the funds are supplied to. The yield-supply product is a
* single hard-wired integration across the app (Aave — the [yield_module_provider][R.string.yield_module_provider]
- * name), so the value is that constant provider rather than a per-tx resolved name. Mutually exclusive with
- * [validatorRow] — a tx is either staking or yield-supply, never both.
+ * name), so the value is that constant provider rather than a per-tx resolved name, and the tap opens the
+ * constant [AAVE_WEBSITE]. Mutually exclusive with [validatorRow] — a tx is either staking or yield-supply,
+ * never both.
*/
private fun TxInfo.protocolRow(): TxHistoryDetailsUM.InfoRowUM? {
if (type !is TransactionType.YieldSupply) return null
return TxHistoryDetailsUM.InfoRowUM(
label = resourceReference(R.string.staking_validator),
value = resourceReference(R.string.yield_module_provider),
+ trailingIconRes = R.drawable.ic_arrow_top_right_24,
+ onClick = { onOpenValidator(AAVE_WEBSITE) },
)
}
@@ -125,11 +131,37 @@ internal class OnChainTxToDetailsUMConverter(
}
private fun TxInfo.toAmountBlockUM(): TxHistoryDetailsUM.AmountBlockUM = TxHistoryDetailsUM.AmountBlockUM(
- currencyIcon = iconStateConverter.convert(currency),
+ icon = amountIcon(),
amount = stringReference(signedAmount(currency)),
+ label = amountLabel(),
isFailed = status is TxInfo.TransactionStatus.Failed,
)
+ /**
+ * Amount icon. Yield-supply enter/exit shows the asset paired with the hard-wired Aave protocol icon, ordered by
+ * direction (Aave leads on "Supplied"/enter, the asset leads on "Returned"/exit — mirroring the two states in the
+ * design); every other type shows the single token avatar. Only enter/exit are paired for now — the remaining
+ * yield-supply variants (topup / withdraw) can adopt the same pair later.
+ */
+ private fun TxInfo.amountIcon(): TxHistoryDetailsUM.AmountIconUM {
+ val asset = TxHistoryDetailsUM.AmountIconUM.Item.Currency(iconStateConverter.convert(currency))
+ val aave = TxHistoryDetailsUM.AmountIconUM.Item.Resource(R.drawable.img_aave_22)
+ return when (type) {
+ is TransactionType.YieldSupply.Enter ->
+ TxHistoryDetailsUM.AmountIconUM.OverlappingPair(leading = aave, trailing = asset)
+ is TransactionType.YieldSupply.Exit ->
+ TxHistoryDetailsUM.AmountIconUM.OverlappingPair(leading = asset, trailing = aave)
+ else -> TxHistoryDetailsUM.AmountIconUM.Single(iconStateConverter.convert(currency))
+ }
+ }
+
+ /** "Supplied"/"Returned" label above the amount for yield-supply enter/exit; `null` (no label) for other types. */
+ private fun TxInfo.amountLabel(): TextReference? = when (type) {
+ is TransactionType.YieldSupply.Enter -> resourceReference(R.string.yield_module_transaction_supplied)
+ is TransactionType.YieldSupply.Exit -> resourceReference(R.string.yield_module_transaction_returned)
+ else -> null
+ }
+
/**
* Counterparty card ("Recipient" / "From"). Only the external-address avatar is produced — built from the `User`
* interaction address (the same source the history list uses for its external-address subtitle); a counterparty that
@@ -140,6 +172,12 @@ internal class OnChainTxToDetailsUMConverter(
* now — a follow-up.
*/
private fun TxInfo.toCounterpartyUM(): TxHistoryDetailsUM.CounterpartyUM? {
+ // Contract interactions (yield-supply / staking / approve) talk to a protocol/validator, not a real recipient —
+ // no copyable counterparty card.
+ val isContractInteraction = type is TransactionType.YieldSupply ||
+ type is TransactionType.Staking ||
+ type is TransactionType.Approve
+ if (isContractInteraction) return null
val address = (interactionAddressType as? TxInfo.InteractionAddressType.User)?.address ?: return null
return TxHistoryDetailsUM.CounterpartyUM(
label = counterpartyLabel(),
@@ -158,13 +196,15 @@ internal class OnChainTxToDetailsUMConverter(
/**
* Signed crypto amount with inline symbol, e.g. `+ 350.31 USDT` / `- 350.31 USDT`. The sign is `-` for outgoing, `+`
- * otherwise, and is dropped for zero amounts and for the failed state (a failed tx moved nothing) — the UI then only
- * strikes the amount through and dims it via [TxHistoryDetailsUM.AmountBlockUM.isFailed].
+ * otherwise, and is dropped for zero amounts, for the failed state (a failed tx moved nothing) and for yield-supply
+ * enter/exit (which reads "Supplied"/"Returned" via the label instead of a signed transfer) — the UI then only strikes
+ * the amount through and dims it via [TxHistoryDetailsUM.AmountBlockUM.isFailed].
*/
private fun TxInfo.signedAmount(currency: CryptoCurrency): String {
val formatted = amount.format { crypto(cryptoCurrency = currency, ignoreSymbolPosition = true) }
val prefix = when {
status is TxInfo.TransactionStatus.Failed -> ""
+ type is TransactionType.YieldSupply.Enter || type is TransactionType.YieldSupply.Exit -> ""
amount.isZero() -> ""
isOutgoing -> "${StringsSigns.MINUS} "
else -> "${StringsSigns.PLUS} "
diff --git a/features/txhistory/impl/src/main/kotlin/com/tangem/features/txhistory/entity/TxHistoryDetailsUM.kt b/features/txhistory/impl/src/main/kotlin/com/tangem/features/txhistory/entity/TxHistoryDetailsUM.kt
index f158ee4ea3..dabb48ccb8 100644
--- a/features/txhistory/impl/src/main/kotlin/com/tangem/features/txhistory/entity/TxHistoryDetailsUM.kt
+++ b/features/txhistory/impl/src/main/kotlin/com/tangem/features/txhistory/entity/TxHistoryDetailsUM.kt
@@ -137,22 +137,51 @@ internal sealed interface TxHistoryDetailsUM : TangemBottomSheetConfigContent {
}
/**
- * Centered amount block of the single-asset card: token avatar (with network badge), the big signed crypto
- * [amount] and the secondary [fiatAmount].
+ * Centered amount block of the single-asset card: the [icon] (a single token avatar, or a yield-supply asset+Aave
+ * pair), an optional [label] above the amount ("Supplied" / "Returned" for yield supply), the big [amount] and the
+ * secondary [fiatAmount].
*
- * [fiatAmount] is `null` while no fiat value is available (`TxInfo` has no fiat field yet) — the fiat line is then
- * omitted entirely rather than shown as a placeholder.
+ * [label] is `null` for the plain single-asset types (Send / Receive / Transfer / staking) — the line is then
+ * omitted. [fiatAmount] is `null` while no fiat value is available (`TxInfo` has no fiat field yet) — the fiat line
+ * is then omitted entirely rather than shown as a placeholder.
*
* [isFailed] drives the failed visual state — the amount is struck through, recolored to tertiary and carries no
* `+`/`−` sign (mirrors the status-driven recolor in the shared header).
*/
data class AmountBlockUM(
- val currencyIcon: CurrencyIconState,
+ val icon: AmountIconUM,
val amount: TextReference,
+ val label: TextReference? = null,
val fiatAmount: TextReference? = null,
val isFailed: Boolean,
)
+ /** Icon shown above the amount of the single-asset card. */
+ @Immutable
+ sealed interface AmountIconUM {
+
+ /** A single token avatar — Send / Receive / Transfer / staking. */
+ data class Single(val currencyIcon: CurrencyIconState) : AmountIconUM
+
+ /**
+ * Two overlapping avatars: [leading] is drawn on top (left, with a background-colored ring), [trailing] behind
+ * it (right). Used by yield supply — the asset and the hard-wired Aave protocol icon — with the order set by the
+ * transaction direction: Aave leads on "Supplied" (enter), the asset leads on "Returned" (exit).
+ */
+ data class OverlappingPair(val leading: Item, val trailing: Item) : AmountIconUM
+
+ /** One avatar of an [OverlappingPair]. */
+ @Immutable
+ sealed interface Item {
+
+ /** A token avatar backed by its [state]. */
+ data class Currency(val state: CurrencyIconState) : Item
+
+ /** A hard-wired drawable (e.g. the Aave protocol icon). */
+ data class Resource(@DrawableRes val resId: Int) : Item
+ }
+ }
+
/**
* A single info row of the details card: a [label] on the leading side and its [value] on the trailing side
* (e.g. `Network fee` → `0.00056 ETH`, `Rate` → `1 POL ≈ 0.36 USDT`). Rendered by [TxHistoryDetailsInfoRows].
diff --git a/features/txhistory/impl/src/main/kotlin/com/tangem/features/txhistory/ui/TxHistoryDetailsAmountBlock.kt b/features/txhistory/impl/src/main/kotlin/com/tangem/features/txhistory/ui/TxHistoryDetailsAmountBlock.kt
index e5c7804469..2c81c2d3bf 100644
--- a/features/txhistory/impl/src/main/kotlin/com/tangem/features/txhistory/ui/TxHistoryDetailsAmountBlock.kt
+++ b/features/txhistory/impl/src/main/kotlin/com/tangem/features/txhistory/ui/TxHistoryDetailsAmountBlock.kt
@@ -1,15 +1,22 @@
package com.tangem.features.txhistory.ui
import android.content.res.Configuration.UI_MODE_NIGHT_YES
+import androidx.compose.foundation.Image
import androidx.compose.foundation.background
+import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
+import androidx.compose.foundation.layout.width
+import androidx.compose.foundation.shape.CircleShape
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
+import androidx.compose.ui.draw.clip
+import androidx.compose.ui.res.painterResource
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.text.style.TextDecoration
import androidx.compose.ui.tooling.preview.Preview
@@ -25,13 +32,18 @@ import com.tangem.core.ui.res.TangemTheme
import com.tangem.core.ui.res.TangemThemePreviewRedesign
import com.tangem.features.txhistory.entity.TxHistoryDetailsUM
+private val IconSize = 72.dp
+private val PairOverlap = 20.dp
+private val PairRing = 4.dp
+private val LeadingSize = IconSize + PairRing * 2
+
/**
- * Centered amount block of the single-asset card: token avatar (with network badge) over the big signed amount and the
- * secondary fiat line.
+ * Centered amount block of the single-asset card: the token avatar (single, or a yield-supply asset+Aave pair), an
+ * optional label ("Supplied"/"Returned"), the big amount and the secondary fiat line.
*
* The failed state ([TxHistoryDetailsUM.AmountBlockUM.isFailed]) strikes the amount through and dims it (primary ->
- * secondary) — matching the status-driven recolor of the shared header. The `+`/`−` sign is already dropped upstream
- * by the converter for failed transactions (a failed tx moved nothing), so the [amount] text arrives unsigned here.
+ * secondary) — matching the status-driven recolor of the shared header. The `+`/`−` sign is resolved upstream by the
+ * converter (dropped for failed and yield-supply transactions), so the [amount] text arrives ready to render here.
*/
@Composable
internal fun TxHistoryDetailsAmountBlock(amountBlock: TxHistoryDetailsUM.AmountBlockUM, modifier: Modifier = Modifier) {
@@ -41,11 +53,17 @@ internal fun TxHistoryDetailsAmountBlock(amountBlock: TxHistoryDetailsUM.AmountB
.padding(vertical = 48.dp),
horizontalAlignment = Alignment.CenterHorizontally,
) {
- TangemCurrencyIcon(
- state = amountBlock.currencyIcon,
- modifier = Modifier.size(72.dp),
- )
+ AmountIcon(amountBlock.icon)
SpacerH(24.dp)
+ amountBlock.label?.let { label ->
+ Text(
+ text = label.resolveReference(),
+ color = TangemTheme.colors3.text.secondary,
+ style = TangemTheme.typography3.subheading.medium,
+ textAlign = TextAlign.Center,
+ )
+ SpacerH(4.dp)
+ }
Text(
text = amountBlock.amount.resolveReference(),
color = if (amountBlock.isFailed) {
@@ -73,6 +91,60 @@ internal fun TxHistoryDetailsAmountBlock(amountBlock: TxHistoryDetailsUM.AmountB
}
}
+@Composable
+private fun AmountIcon(icon: TxHistoryDetailsUM.AmountIconUM, modifier: Modifier = Modifier) {
+ when (icon) {
+ is TxHistoryDetailsUM.AmountIconUM.Single -> AmountIconItem(
+ item = TxHistoryDetailsUM.AmountIconUM.Item.Currency(icon.currencyIcon),
+ modifier = modifier,
+ )
+ is TxHistoryDetailsUM.AmountIconUM.OverlappingPair -> Box(
+ modifier = modifier
+ .height(LeadingSize)
+ .width(PairRing + IconSize * 2 - PairOverlap),
+ ) {
+ // Trailing behind, right; the pair drops the network badge so the two token arts read as equal-size icons.
+ AmountIconItem(
+ item = icon.trailing,
+ modifier = Modifier.align(Alignment.CenterEnd),
+ shouldDisplayNetwork = false,
+ )
+ // Leading on top, left, wrapped in a background-colored ring that separates it from the trailing icon.
+ Box(
+ modifier = Modifier
+ .align(Alignment.CenterStart)
+ .size(LeadingSize)
+ .background(TangemTheme.colors3.bg.secondary, CircleShape)
+ .padding(PairRing),
+ ) {
+ AmountIconItem(item = icon.leading, shouldDisplayNetwork = false)
+ }
+ }
+ }
+}
+
+@Composable
+private fun AmountIconItem(
+ item: TxHistoryDetailsUM.AmountIconUM.Item,
+ modifier: Modifier = Modifier,
+ shouldDisplayNetwork: Boolean = true,
+) {
+ when (item) {
+ is TxHistoryDetailsUM.AmountIconUM.Item.Currency -> TangemCurrencyIcon(
+ state = item.state,
+ modifier = modifier.size(IconSize),
+ shouldDisplayNetwork = shouldDisplayNetwork,
+ )
+ is TxHistoryDetailsUM.AmountIconUM.Item.Resource -> Image(
+ painter = painterResource(item.resId),
+ contentDescription = null,
+ modifier = modifier
+ .size(IconSize)
+ .clip(CircleShape),
+ )
+ }
+}
+
// region Preview
@Preview(name = "Light", showBackground = true, widthDp = 360)
@@ -87,21 +159,42 @@ private fun TxHistoryDetailsAmountBlockPreview() {
TxHistoryDetailsAmountBlock(amountBlock = previewAmountBlock(isFailed = true))
// No fiat — the fiat line is omitted entirely.
TxHistoryDetailsAmountBlock(amountBlock = previewAmountBlock(isFailed = false, fiatAmount = null))
+ // Yield supply — "Supplied" (Aave leads) and "Returned" (asset leads), unsigned amount.
+ TxHistoryDetailsAmountBlock(
+ amountBlock = previewAmountBlock(
+ isFailed = false,
+ fiatAmount = null,
+ icon = TxHistoryDetailsUM.AmountIconUM.OverlappingPair(
+ leading = TxHistoryDetailsUM.AmountIconUM.Item.Resource(R.drawable.img_aave_22),
+ trailing = TxHistoryDetailsUM.AmountIconUM.Item.Currency(previewCurrencyIcon()),
+ ),
+ label = stringReference("Supplied"),
+ amount = stringReference("1,294.23 USDT"),
+ ),
+ )
}
}
}
-private fun previewAmountBlock(isFailed: Boolean, fiatAmount: TextReference? = stringReference("$350.31")) =
- TxHistoryDetailsUM.AmountBlockUM(
- currencyIcon = CurrencyIconState.CoinIcon(
- url = null,
- fallbackResId = R.drawable.img_eth_22,
- isGrayscale = false,
- shouldShowCustomBadge = false,
- ),
- amount = stringReference("+ 350.31 USDT"),
- fiatAmount = fiatAmount,
- isFailed = isFailed,
- )
+private fun previewCurrencyIcon() = CurrencyIconState.CoinIcon(
+ url = null,
+ fallbackResId = R.drawable.img_eth_22,
+ isGrayscale = false,
+ shouldShowCustomBadge = false,
+)
+
+private fun previewAmountBlock(
+ isFailed: Boolean,
+ fiatAmount: TextReference? = stringReference("$350.31"),
+ icon: TxHistoryDetailsUM.AmountIconUM = TxHistoryDetailsUM.AmountIconUM.Single(previewCurrencyIcon()),
+ label: TextReference? = null,
+ amount: TextReference = stringReference("+ 350.31 USDT"),
+) = TxHistoryDetailsUM.AmountBlockUM(
+ icon = icon,
+ amount = amount,
+ label = label,
+ fiatAmount = fiatAmount,
+ isFailed = isFailed,
+)
// endregion
\ No newline at end of file
diff --git a/features/txhistory/impl/src/main/kotlin/com/tangem/features/txhistory/ui/TxHistoryDetailsModalBottomSheetContent.kt b/features/txhistory/impl/src/main/kotlin/com/tangem/features/txhistory/ui/TxHistoryDetailsModalBottomSheetContent.kt
index 9261a5ec0f..355be8711f 100644
--- a/features/txhistory/impl/src/main/kotlin/com/tangem/features/txhistory/ui/TxHistoryDetailsModalBottomSheetContent.kt
+++ b/features/txhistory/impl/src/main/kotlin/com/tangem/features/txhistory/ui/TxHistoryDetailsModalBottomSheetContent.kt
@@ -78,11 +78,13 @@ private fun previewSingleAsset() = TxHistoryDetailsUM.SingleAsset(
menu = previewMenu(),
),
amountBlock = TxHistoryDetailsUM.AmountBlockUM(
- currencyIcon = CurrencyIconState.CoinIcon(
- url = null,
- fallbackResId = R.drawable.img_eth_22,
- isGrayscale = false,
- shouldShowCustomBadge = false,
+ icon = TxHistoryDetailsUM.AmountIconUM.Single(
+ CurrencyIconState.CoinIcon(
+ url = null,
+ fallbackResId = R.drawable.img_eth_22,
+ isGrayscale = false,
+ shouldShowCustomBadge = false,
+ ),
),
amount = stringReference("- 350.31 USDT"),
fiatAmount = stringReference("$350.31"),
diff --git a/features/txhistory/impl/src/test/kotlin/com/tangem/features/txhistory/converter/OnChainTxToDetailsUMConverterTest.kt b/features/txhistory/impl/src/test/kotlin/com/tangem/features/txhistory/converter/OnChainTxToDetailsUMConverterTest.kt
index ba0412e862..dca56d6ec5 100644
--- a/features/txhistory/impl/src/test/kotlin/com/tangem/features/txhistory/converter/OnChainTxToDetailsUMConverterTest.kt
+++ b/features/txhistory/impl/src/test/kotlin/com/tangem/features/txhistory/converter/OnChainTxToDetailsUMConverterTest.kt
@@ -17,6 +17,7 @@ import kotlinx.collections.immutable.persistentListOf
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.TestInstance
import org.junit.jupiter.params.ParameterizedTest
+import org.junit.jupiter.params.provider.MethodSource
import java.math.BigDecimal
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
@@ -207,6 +208,55 @@ internal class OnChainTxToDetailsUMConverterTest : TxDetailsConverterTestBase()
assertThat(amount).doesNotContain("-")
}
+ @Test
+ fun `GIVEN non-yield Transfer WHEN convert THEN single icon and no label`() {
+ // Arrange
+ val tx = txInfo(type = TransactionType.Transfer)
+
+ // Act
+ val amountBlock = converter.convert(tx).amountBlock
+
+ // Assert
+ assertThat(amountBlock.icon).isInstanceOf(TxHistoryDetailsUM.AmountIconUM.Single::class.java)
+ assertThat(amountBlock.label).isNull()
+ }
+
+ @Test
+ fun `GIVEN yield-supply Enter WHEN convert THEN Supplied label, Aave-leading pair and unsigned amount`() {
+ // Arrange — enter (funds outgoing to Aave): the amount must still be unsigned.
+ val tx = txInfo(type = TransactionType.YieldSupply.Enter(address = USER_ADDRESS), isOutgoing = true)
+
+ // Act
+ val amountBlock = converter.convert(tx).amountBlock
+
+ // Assert
+ assertThat(amountBlock.label).isEqualTo(resourceReference(R.string.yield_module_transaction_supplied))
+ val pair = amountBlock.icon as TxHistoryDetailsUM.AmountIconUM.OverlappingPair
+ assertThat(pair.leading).isEqualTo(TxHistoryDetailsUM.AmountIconUM.Item.Resource(R.drawable.img_aave_22))
+ assertThat(pair.trailing).isInstanceOf(TxHistoryDetailsUM.AmountIconUM.Item.Currency::class.java)
+ val amount = amountBlock.amount.resolveString()
+ assertThat(amount).doesNotContain("+")
+ assertThat(amount).doesNotContain("-")
+ }
+
+ @Test
+ fun `GIVEN yield-supply Exit WHEN convert THEN Returned label, asset-leading pair and unsigned amount`() {
+ // Arrange
+ val tx = txInfo(type = TransactionType.YieldSupply.Exit(address = USER_ADDRESS), isOutgoing = false)
+
+ // Act
+ val amountBlock = converter.convert(tx).amountBlock
+
+ // Assert
+ assertThat(amountBlock.label).isEqualTo(resourceReference(R.string.yield_module_transaction_returned))
+ val pair = amountBlock.icon as TxHistoryDetailsUM.AmountIconUM.OverlappingPair
+ assertThat(pair.leading).isInstanceOf(TxHistoryDetailsUM.AmountIconUM.Item.Currency::class.java)
+ assertThat(pair.trailing).isEqualTo(TxHistoryDetailsUM.AmountIconUM.Item.Resource(R.drawable.img_aave_22))
+ val amount = amountBlock.amount.resolveString()
+ assertThat(amount).doesNotContain("+")
+ assertThat(amount).doesNotContain("-")
+ }
+
// endregion
// region Counterparty
@@ -223,6 +273,29 @@ internal class OnChainTxToDetailsUMConverterTest : TxDetailsConverterTestBase()
assertThat(counterparty).isNull()
}
+ @ParameterizedTest
+ @MethodSource("provideContractInteractionTypes")
+ fun `GIVEN contract-interaction tx with User interaction address WHEN convert THEN no counterparty card`(
+ type: TransactionType,
+ ) {
+ // Arrange — yield-supply / staking / approve talk to a protocol/validator, not a copyable recipient.
+ val tx = txInfo(type = type, interactionAddressType = TxInfo.InteractionAddressType.User(USER_ADDRESS))
+
+ // Act
+ val counterparty = converter.convert(tx).counterparty
+
+ // Assert
+ assertThat(counterparty).isNull()
+ }
+
+ private fun provideContractInteractionTypes() = listOf(
+ TransactionType.YieldSupply.Enter(address = USER_ADDRESS),
+ TransactionType.YieldSupply.Exit(address = USER_ADDRESS),
+ TransactionType.Staking.Stake,
+ TransactionType.Staking.Vote(validatorAddress = VALIDATOR_ADDRESS),
+ TransactionType.Approve,
+ )
+
@Test
fun `GIVEN incoming Transfer with User address WHEN convert THEN address-avatar counterparty with From label`() {
// Arrange
@@ -435,8 +508,8 @@ internal class OnChainTxToDetailsUMConverterTest : TxDetailsConverterTestBase()
// region Protocol row (yield-supply)
@Test
- fun `GIVEN yield-supply tx WHEN convert THEN protocol row shows the hard-wired Aave protocol`() {
- // Arrange — yield-supply is a single hard-wired integration (Aave), so the value is constant, not resolved.
+ fun `GIVEN yield-supply tx WHEN convert THEN protocol row shows the hard-wired Aave protocol with its link`() {
+ // Arrange — yield-supply is a single hard-wired integration (Aave), so the value and link are constant.
val tx = txInfo(type = TransactionType.YieldSupply.Enter(address = USER_ADDRESS))
// Act
@@ -445,8 +518,9 @@ internal class OnChainTxToDetailsUMConverterTest : TxDetailsConverterTestBase()
// Assert
assertThat(row.label).isEqualTo(resourceReference(R.string.staking_validator))
assertThat(row.value).isEqualTo(resourceReference(R.string.yield_module_provider))
- assertThat(row.trailingIconRes).isNull()
- assertThat(row.onClick).isNull()
+ assertThat(row.trailingIconRes).isEqualTo(R.drawable.ic_arrow_top_right_24)
+ row.onClick?.invoke()
+ assertThat(openedUrls).containsExactly("https://aave.com/")
}
@Test