feat: dApp signing — function-name title + collapsible details (Phase 1)#4054
Conversation
Add functionSignature/functionInputs to TransactionDetailsUiModel and display them on the done screen using existing OtherField components. Co-signers can now see what contract function was called after signing dApp transactions instead of a blank done screen. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add a ContractCallExtractor strategy registry that recognises Aave/Spark/Radiant lending, Compound V3, EigenLayer, Across V3, and ERC20 methods, extracting (token address, amount) pairs from decoded calldata. Co-signers now see "supplyWithPermit 3 USDC" on both the verify and done screens instead of a raw function argument dump. The extractor guards against malformed inputs: nesting-aware comma splitter for tuple params, trimmed function names, non-decimal raw amount rejection, and BigInteger parsing wrapped in runCatching. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Mirror the extension behavior: if the token being acted on (Aave withdraw, Compound supply, etc.) isn't in the vault yet, look it up in the built-in tokens registry so co-signers still see the proper symbol and amount instead of nothing. Add TokenRepository.getBuiltInTokenByContract(chain, address) and wire it as a fallback in JoinKeysignViewModel.resolveTokenDisplay. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Replace the misleading "Send 0 ETH" hero on verify and done screens with the decoded function name + resolved token when available: - Aave withdraw(MAX_UINT256) → "Withdraw / Max USDC" - Aave supplyWithPermit(1e6) → "SupplyWithPermit / 1 USDC" - ERC20 approve(MAX_UINT256) → "Approve / Unlimited USDC" Fixes: - AbiToJson: emit uint256 as JSON string (not BigDecimal→scientific notation) - Sentinel-aware resolver with contextual "Max" vs "Unlimited" labels - Reuses existing TokenValueToDecimalUiStringMapper for amount formatting - Moves sentinel registry into ContractCallExtractor (domain co-location) - Wraps getTransactionFunctionInfo in Dispatchers.IO (potential ANR fix) - Removes duplicated fields from VerifyTransactionUiModel (reads from nested tx) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Hero card with VsOverviewToken on verify + done screens - Collapsible transaction details section with AnimatedVisibility - Sentinel policy: "Unlimited" only for approvals, null for withdraw/repay - VsOverviewToken withContainer param for styling flexibility - Remove no-op padding modifiers Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
|
Note Reviews pausedIt looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
📝 WalkthroughWalkthroughRelocates EVM function metadata into transaction models, adds function-name extraction/formatting, conditions UI hero and detail rendering on function metadata, refactors holdable-button interactions, makes VsOverviewToken container optional, and serializes ABI big integers as JSON strings. ChangesTransaction + ABI + UI Flow
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
|
@coderabbitai review |
✅ Actions performedReview triggered.
|
|
@coderabbitai review |
✅ Actions performedReview triggered.
|
There was a problem hiding this comment.
Actionable comments posted: 5
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (2)
app/src/main/java/com/vultisig/wallet/ui/components/buttons/VsHoldableButton.kt (2)
6-16:⚠️ Potential issue | 🔴 CriticalAdd the missing
matchParentSizeimport.Line 96 calls
Modifier.matchParentSize(), but the required importandroidx.compose.foundation.layout.matchParentSizeis missing from the import block. This will prevent the code from compiling.Fix
import androidx.compose.foundation.layout.fillMaxHeight import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.matchParentSize import androidx.compose.foundation.layout.padding🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@app/src/main/java/com/vultisig/wallet/ui/components/buttons/VsHoldableButton.kt` around lines 6 - 16, The file is missing the import for Modifier.matchParentSize, causing a compile error where matchParentSize() is called in VsHoldableButton (look for Modifier.matchParentSize() usage); fix it by adding the import androidx.compose.foundation.layout.matchParentSize to the import block so the call resolves correctly.
38-38:⚠️ Potential issue | 🟠 MajorThe progress bar animation and onLongClick callback use different, independently configured timeouts.
holdDurationdrives the fill animation (line 71), butcombinedClickable.onLongClicktriggers on the platform's fixed long-press timeout (~500ms). This causes the action to fire before the progress bar reaches 100%, breaking the hold-to-confirm UX pattern. The parameter appears configurable for animation purposes but has no effect on when the actual callback fires.Either synchronize both by deriving the animation from the system long-press timeout, or remove the
holdDurationparameter and drive both from a fixed constraint.Code locations
- Line 38:
holdDurationparameter definition- Lines 61–82: Progress animation keyed to
holdDuration- Lines 87–93:
combinedClickablewith platform-governedonLongClick🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@app/src/main/java/com/vultisig/wallet/ui/components/buttons/VsHoldableButton.kt` at line 38, The progress animation duration (holdDuration) must be synchronized with the platform long-press timeout; update VsHoldableButton so the animation uses the system long-press timeout instead of an independent default: remove or ignore the externally supplied holdDuration parameter and derive duration via ViewConfiguration.getLongPressTimeout() (or compute a single derived value) and feed that into the progress animation logic (the code currently keyed to holdDuration around the progress animation). Keep using combinedClickable.onLongClick for the action, so the visual fill completes at the same system timeout; ensure any remembered/animated state (the progress animate key) uses the derived duration value so the UI and onLongClick fire in sync.
🧹 Nitpick comments (1)
data/src/main/kotlin/com/vultisig/wallet/data/utils/AbiToJson.kt (1)
81-86: Narrow the fallback toNumberFormatException.
BigInteger(value)only needs a parse-error fallback here. CatchingExceptionalso hides unrelated failures in this conversion path.Based on learnings: Do not use a broad catch (`catch (Exception)`) to swallow errors in Kotlin.♻️ Proposed change
private fun parseBigIntAsJsonString(value: String): JsonElement { return try { val big = BigInteger(value) JsonPrimitive(big.toString()) - } catch (_: Exception) { + } catch (_: NumberFormatException) { JsonPrimitive(value) } }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@data/src/main/kotlin/com/vultisig/wallet/data/utils/AbiToJson.kt` around lines 81 - 86, The parseBigIntAsJsonString function currently catches all Exceptions which can mask unrelated errors; change the catch to specifically catch NumberFormatException when calling BigInteger(value) and keep the fallback JsonPrimitive(value) behavior so only parse errors are handled; update the catch clause in parseBigIntAsJsonString and leave other logic (creating BigInteger and returning JsonPrimitive(big.toString())) unchanged.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In
`@app/src/main/java/com/vultisig/wallet/ui/components/buttons/VsHoldableButton.kt`:
- Around line 57-82: The LaunchedEffect in VsHoldableButton currently spawns
concurrent scope.launch jobs causing race conditions and doesn't react to
holdDuration changes; change the effect to key on both interactionSource and
holdDuration, use interactionSource.interactions.collectLatest { ... } so
previous interaction handling is cancelled, run the progress animation directly
inside the collector as a suspend call (call progress.animateTo(...) instead of
scope.launch) and on Release/Cancel call progress.snapTo(0f) within the
collector to ensure proper cancellation and reset.
In `@app/src/main/java/com/vultisig/wallet/ui/screens/send/VerifySendScreen.kt`:
- Around line 196-208: The current hero selection sets heroToken =
tx.resolvedToken ?: tx.token and heroHeader = tx.functionName ?: ..., which
causes a native/zero-amount hero to be shown when tx.functionName is non-null
but tx.resolvedToken is null (intentional sentinel upstream); change the logic
to only fall back to tx.token when there is no decoded function (i.e., if
tx.functionName == null then use tx.token, otherwise keep heroToken null/absent)
and adjust the heroHeader behavior accordingly so a contract-call header with no
resolvedToken does not display the native hero; apply the same guard/fix to the
corresponding "done" and "overview" hero blocks (the places using
heroToken/heroHeader, VsOverviewToken, and any matching done/overview hero
rendering).
In
`@app/src/main/java/com/vultisig/wallet/ui/screens/transaction/SendTxOverviewScreen.kt`:
- Around line 304-307: UiTransactionInfo currently omits the tokenDisplay
transport field, so when resolvedToken is intentionally null the overview screen
cannot render sentinel-aware contract-call amounts; add a nullable tokenDisplay
property to UiTransactionInfo (alongside functionName, resolvedToken,
functionSignature, functionInputs) using the same TokenDisplay type used in the
send-flow model, and update SendTxOverviewScreen to prefer
UiTransactionInfo.tokenDisplay when UiTransactionInfo.resolvedToken is null so
decoded amounts render correctly in MAX/withdraw/repay fallback cases.
In `@app/src/main/java/com/vultisig/wallet/ui/screens/TransactionDoneScreen.kt`:
- Around line 256-262: In TransactionDoneScreen, the decoded amount
(transaction.tokenDisplay) is being rendered in the resolvedToken == null branch
but the stale fallback (transaction.token.value and transaction.token.fiatValue)
is still rendered later, causing duplicate rows; update the rendering logic so
that the block which shows transaction.token.value and
transaction.token.fiatValue is skipped when transaction.tokenDisplay != null
(e.g., change the later conditional to check resolvedToken == null &&
transaction.tokenDisplay == null or convert the first tokenDisplay block into an
else-if with the existing fallback), referencing OtherField,
UiHorizontalDivider, transaction.tokenDisplay, transaction.resolvedToken,
transaction.token.value and transaction.token.fiatValue.
In
`@data/src/main/kotlin/com/vultisig/wallet/data/repositories/ContractCallExtractor.kt`:
- Around line 21-30: The sentinelLabelFor logic incorrectly treats
increaseAllowance and decreaseAllowance as "Unlimited"; restrict the unlimited
sentinel to exact approvals only by updating UNLIMITED_APPROVAL_FUNCTIONS to
contain only "approve" and ensure sentinelLabelFor continues to return
"Unlimited" only for that exact function name; locate the constant
UNLIMITED_APPROVAL_FUNCTIONS and the function sentinelLabelFor to make this
change so increaseAllowance/decreaseAllowance no longer produce the "Unlimited"
label.
---
Outside diff comments:
In
`@app/src/main/java/com/vultisig/wallet/ui/components/buttons/VsHoldableButton.kt`:
- Around line 6-16: The file is missing the import for Modifier.matchParentSize,
causing a compile error where matchParentSize() is called in VsHoldableButton
(look for Modifier.matchParentSize() usage); fix it by adding the import
androidx.compose.foundation.layout.matchParentSize to the import block so the
call resolves correctly.
- Line 38: The progress animation duration (holdDuration) must be synchronized
with the platform long-press timeout; update VsHoldableButton so the animation
uses the system long-press timeout instead of an independent default: remove or
ignore the externally supplied holdDuration parameter and derive duration via
ViewConfiguration.getLongPressTimeout() (or compute a single derived value) and
feed that into the progress animation logic (the code currently keyed to
holdDuration around the progress animation). Keep using
combinedClickable.onLongClick for the action, so the visual fill completes at
the same system timeout; ensure any remembered/animated state (the progress
animate key) uses the derived duration value so the UI and onLongClick fire in
sync.
---
Nitpick comments:
In `@data/src/main/kotlin/com/vultisig/wallet/data/utils/AbiToJson.kt`:
- Around line 81-86: The parseBigIntAsJsonString function currently catches all
Exceptions which can mask unrelated errors; change the catch to specifically
catch NumberFormatException when calling BigInteger(value) and keep the fallback
JsonPrimitive(value) behavior so only parse errors are handled; update the catch
clause in parseBigIntAsJsonString and leave other logic (creating BigInteger and
returning JsonPrimitive(big.toString())) unchanged.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: ccdc0ce5-5152-49dc-9508-27bfce41b41d
📒 Files selected for processing (11)
app/src/main/java/com/vultisig/wallet/ui/components/VsOverviewToken.ktapp/src/main/java/com/vultisig/wallet/ui/components/buttons/VsHoldableButton.ktapp/src/main/java/com/vultisig/wallet/ui/models/VerifyTransactionViewModel.ktapp/src/main/java/com/vultisig/wallet/ui/models/keysign/JoinKeysignViewModel.ktapp/src/main/java/com/vultisig/wallet/ui/screens/TransactionDoneScreen.ktapp/src/main/java/com/vultisig/wallet/ui/screens/send/VerifySendScreen.ktapp/src/main/java/com/vultisig/wallet/ui/screens/transaction/SendTxOverviewScreen.ktapp/src/main/java/com/vultisig/wallet/ui/screens/transaction/UiModelOverviewExtensions.ktdata/src/main/kotlin/com/vultisig/wallet/data/repositories/ContractCallExtractor.ktdata/src/main/kotlin/com/vultisig/wallet/data/repositories/TokenRepository.ktdata/src/main/kotlin/com/vultisig/wallet/data/utils/AbiToJson.kt
|
could you please attach some screenshot of how it looks like here? |
- ContractCallExtractor: restrict "Unlimited" sentinel to approve() only. increaseAllowance/decreaseAllowance apply deltas, so MAX_UINT256 there is not an unlimited approval — it's an overflow or the opposite operation. - VsHoldableButton: launch animation in LaunchedEffect's own scope (drop rememberCoroutineScope) and cancel the previous job on each new Press to avoid stacking and race conditions. - VerifySendScreen / SendTxOverviewScreen: when the function is decoded but the token is not (MAX withdraw/repay sentinel), render the function name as a header-only hero — the native token fallback was showing a misleading "0 ETH" while the details section described a contract call. - UiTransactionInfo: pipe tokenDisplay through the mapper so the post- broadcast screen can render the decoded amount when resolvedToken is intentionally null (e.g. "Unlimited USDC" for an approve MAX). - TransactionDoneScreen / SendTxOverviewScreen details: suppress the native amount fallback whenever tokenDisplay is present — prevents two rows showing the same (or contradictory) amount.
There was a problem hiding this comment.
Actionable comments posted: 1
♻️ Duplicate comments (1)
app/src/main/java/com/vultisig/wallet/ui/screens/TransactionDoneScreen.kt (1)
70-83:⚠️ Potential issue | 🟠 MajorAvoid native-token fallback for decoded send calls with unresolved sentinel token.
On Line 75,
resolvedToken ?: tokenreintroduces the misleading native hero whenfunctionName != null && resolvedToken == null(intentional sentinel case). Render a header-only hero in that branch (same behavior already applied in verify screen).Suggested fix
if (transactionTypeUiModel is TransactionTypeUiModel.Send) { val transaction = transactionTypeUiModel.tx - val heroHeader = - transaction.functionName - ?: stringResource(R.string.tx_overview_screen_tx_send) - val heroToken = transaction.resolvedToken ?: transaction.token - - VsOverviewToken( - header = heroHeader, - valuedToken = heroToken, - shape = RoundedCornerShape(24.dp), - modifier = Modifier.fillMaxWidth(), - ) + if (transaction.functionName != null && transaction.resolvedToken == null) { + Text( + text = transaction.functionName, + style = Theme.brockmann.headings.title3, + color = Theme.v2.colors.text.primary, + modifier = Modifier.fillMaxWidth(), + ) + } else { + val heroHeader = + transaction.functionName + ?: stringResource(R.string.tx_overview_screen_tx_send) + val heroToken = transaction.resolvedToken ?: transaction.token + + VsOverviewToken( + header = heroHeader, + valuedToken = heroToken, + shape = RoundedCornerShape(24.dp), + modifier = Modifier.fillMaxWidth(), + ) + } }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@app/src/main/java/com/vultisig/wallet/ui/screens/TransactionDoneScreen.kt` around lines 70 - 83, The code in TransactionDoneScreen.kt currently uses transaction.resolvedToken ?: transaction.token inside the TransactionTypeUiModel.Send branch which resurrects a native-token hero when resolvedToken is intentionally null for decoded send calls; change the logic to detect when transaction.functionName != null and resolvedToken == null and in that case pass no token to VsOverviewToken so it renders a header-only hero (mirror the behavior used in the verify screen) instead of falling back to transaction.token; update the TransactionTypeUiModel.Send branch that builds heroHeader and heroToken and ensure VsOverviewToken is called without a valuedToken when resolvedToken is the sentinel null.
🧹 Nitpick comments (1)
data/src/main/kotlin/com/vultisig/wallet/data/repositories/ContractCallExtractor.kt (1)
127-152: Normalize ABI type tokens before matchinguint256/address.The matcher currently requires exact tokens (
"uint256","address"). If signatures include parameter names (for example,uint256 amount), extraction silently fails. Normalizing to base type makes this parser more resilient.Suggested refactor
private fun splitTopLevel(params: String): List<String> { @@ } + +private fun String.baseAbiType(): String = trim().substringBefore(' ') @@ - val uint256Idx = paramTypes.indexOf("uint256") + val uint256Idx = paramTypes.indexOfFirst { it.baseAbiType() == "uint256" } @@ - val addressIdx = paramTypes.indexOf("address") + val addressIdx = paramTypes.indexOfFirst { it.baseAbiType() == "address" } @@ - if (type == "address") { + if (type.baseAbiType() == "address") {🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@data/src/main/kotlin/com/vultisig/wallet/data/repositories/ContractCallExtractor.kt` around lines 127 - 152, The parser in ContractCallExtractor is matching exact strings "uint256" and "address" against paramTypes, which fails when ABI tokens include parameter names (e.g., "uint256 amount"); update the normalization of paramTypes before matching by transforming each entry to its base type (trim whitespace and take the leading token before any space or other suffix like parameter name or modifiers) so comparisons such as val uint256Idx = paramTypes.indexOf("uint256") and subsequent address checks work reliably across ExtractionStrategy cases (including FirstAddressBeforeFirstUint and NthAddress).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In
`@data/src/main/kotlin/com/vultisig/wallet/data/repositories/ContractCallExtractor.kt`:
- Around line 110-117: The parsing code in ContractCallExtractor that computes
parenStart and parenEnd needs an extra guard to avoid invalid ordering (e.g.,
")(") causing substring crashes: after computing parenStart and parenEnd, return
null if parenStart == -1 || parenEnd == -1 || parenEnd <= parenStart so the
subsequent substring calls (used to derive funcName and paramTypes and to look
up strategy in registry) are only executed on well-formed signatures.
---
Duplicate comments:
In `@app/src/main/java/com/vultisig/wallet/ui/screens/TransactionDoneScreen.kt`:
- Around line 70-83: The code in TransactionDoneScreen.kt currently uses
transaction.resolvedToken ?: transaction.token inside the
TransactionTypeUiModel.Send branch which resurrects a native-token hero when
resolvedToken is intentionally null for decoded send calls; change the logic to
detect when transaction.functionName != null and resolvedToken == null and in
that case pass no token to VsOverviewToken so it renders a header-only hero
(mirror the behavior used in the verify screen) instead of falling back to
transaction.token; update the TransactionTypeUiModel.Send branch that builds
heroHeader and heroToken and ensure VsOverviewToken is called without a
valuedToken when resolvedToken is the sentinel null.
---
Nitpick comments:
In
`@data/src/main/kotlin/com/vultisig/wallet/data/repositories/ContractCallExtractor.kt`:
- Around line 127-152: The parser in ContractCallExtractor is matching exact
strings "uint256" and "address" against paramTypes, which fails when ABI tokens
include parameter names (e.g., "uint256 amount"); update the normalization of
paramTypes before matching by transforming each entry to its base type (trim
whitespace and take the leading token before any space or other suffix like
parameter name or modifiers) so comparisons such as val uint256Idx =
paramTypes.indexOf("uint256") and subsequent address checks work reliably across
ExtractionStrategy cases (including FirstAddressBeforeFirstUint and NthAddress).
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: 25ba2b01-8774-4d52-bd48-5ee00416d127
📒 Files selected for processing (6)
app/src/main/java/com/vultisig/wallet/ui/components/buttons/VsHoldableButton.ktapp/src/main/java/com/vultisig/wallet/ui/screens/TransactionDoneScreen.ktapp/src/main/java/com/vultisig/wallet/ui/screens/send/VerifySendScreen.ktapp/src/main/java/com/vultisig/wallet/ui/screens/transaction/SendTxOverviewScreen.ktapp/src/main/java/com/vultisig/wallet/ui/screens/transaction/UiModelOverviewExtensions.ktdata/src/main/kotlin/com/vultisig/wallet/data/repositories/ContractCallExtractor.kt
🚧 Files skipped from review as they are similar to previous changes (2)
- app/src/main/java/com/vultisig/wallet/ui/components/buttons/VsHoldableButton.kt
- app/src/main/java/com/vultisig/wallet/ui/screens/transaction/SendTxOverviewScreen.kt
|
Could you please verify the swap transaction behavior with https://opensea.io/ and confirm it works as expected? Just want to make sure there are no unexpected issues with the swap flow on that platform. |
|
@coderabbitai full review |
✅ Actions performedFull review triggered. |
There was a problem hiding this comment.
Actionable comments posted: 3
🧹 Nitpick comments (4)
app/src/main/java/com/vultisig/wallet/ui/components/buttons/VsHoldableButton.kt (1)
37-37: Caller-suppliedholdDurationcan re-introduce the visual/firing mismatch.When a caller overrides the default with a value other than the platform long-press timeout,
combinedClickable.onLongClickwill still fire atViewConfiguration.longPressTimeoutMillis(~400 ms) regardless ofholdDuration, so the fill animation will again be out of sync with the actual long-press callback. Consider either:
- Documenting that
holdDurationis purely cosmetic and cannot extend/shorten the actual long-press window, or- Removing the parameter entirely and always using
LocalViewConfiguration.current.longPressTimeoutMillisto prevent misuse.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@app/src/main/java/com/vultisig/wallet/ui/components/buttons/VsHoldableButton.kt` at line 37, The caller-controlled holdDuration parameter can desynchronize the visual fill and the actual long-press event (combinedClickable.onLongClick fires at ViewConfiguration.longPressTimeoutMillis regardless), so remove the holdDuration parameter from the VsHoldableButton API and always source the duration from LocalViewConfiguration.current.longPressTimeoutMillis inside the component; update any references to holdDuration within VsHoldableButton (e.g., animation timing, CombinedClickable/combinedClickable usage and any local variables) to use the platform timeout and remove or adapt any external call sites that passed holdDuration.app/src/main/java/com/vultisig/wallet/ui/screens/send/VerifySendScreen.kt (2)
412-440: Using a borders token as a background fill is a theme-token mismatch.
Theme.v2.colors.variables.bordersLightis intended for stroke colors. For the expanded panel's background, switch to a backgrounds token (e.g.,Theme.v2.colors.backgrounds.secondary/tertiary) so the panel honors light/dark theming consistently and stays aligned with Figma. As per coding guidelines, "Use the project's existing theme tokens (Theme.v2.colors., Theme.brockmann.)".🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@app/src/main/java/com/vultisig/wallet/ui/screens/send/VerifySendScreen.kt` around lines 412 - 440, The panel is using a stroke token for its fill; update the Modifier.background call inside the AnimatedVisibility block (the Column that wraps VerifyCardJsonDetails) to use a background token such as Theme.v2.colors.backgrounds.secondary or .tertiary instead of Theme.v2.colors.variables.bordersLight so the expanded panel (where functionSignature/functionInputs are shown) follows background theming and Figma tokens.
402-409: Animate the chevron rotation for smoother expand/collapse.A bare
rotationZ = if (isExpanded) 180f else 0fsnaps.animateFloatAsStatematches the AnimatedVisibility timing and is idiomatic in Compose.Suggested fix
+ val chevronRotation by animateFloatAsState(targetValue = if (isExpanded) 180f else 0f) IconButton(onClick = { isExpanded = !isExpanded }, modifier = Modifier.size(16.dp)) { UiIcon( drawableResId = R.drawable.chevron, tint = Theme.v2.colors.text.tertiary, size = 8.dp, - modifier = Modifier.graphicsLayer(rotationZ = if (isExpanded) 180f else 0f), + modifier = Modifier.graphicsLayer(rotationZ = chevronRotation), ) }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@app/src/main/java/com/vultisig/wallet/ui/screens/send/VerifySendScreen.kt` around lines 402 - 409, The chevron rotation currently snaps because rotationZ is set directly; replace it with an animated float using animateFloatAsState (e.g., val rotation by animateFloatAsState(targetValue = if (isExpanded) 180f else 0f, animationSpec = ...) ) and use that animated value in Modifier.graphicsLayer(rotationZ = rotation) inside the IconButton in VerifySendScreen (the block with IconButton/UiIcon), so the chevron rotation is smooth and synced with the AnimatedVisibility timing.app/src/main/java/com/vultisig/wallet/ui/screens/TransactionDoneScreen.kt (1)
260-280: Inconsistent disclosure pattern between Verify and Done screens.
VerifySendScreenshows function signature/inputs inside a collapsibleTransactionDetailsSection(chevron + scrollable card), butTransactionDoneScreenlays them out as flat divider-separatedOtherFieldrows. For long ABI inputs (which can be hundreds of chars for swaps/multicalls), this expands the done screen unboundedly with no way to collapse. Consider mirroring the sameTransactionDetailsSectionhere for consistency and to keep the done-screen scrollable area sane.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@app/src/main/java/com/vultisig/wallet/ui/screens/TransactionDoneScreen.kt` around lines 260 - 280, TransactionDoneScreen currently renders long ABI fields as flat OtherField rows (surrounded by UiHorizontalDivider), causing unbounded expansion; replace the functionSignature and functionInputs blocks with the same TransactionDetailsSection used in VerifySendScreen so they are rendered inside a collapsible chevron + scrollable card. Specifically, remove the OtherField + UiHorizontalDivider pairs for transaction.functionSignature and transaction.functionInputs and invoke TransactionDetailsSection (passing the appropriate title strings and the functionSignature/functionInputs values) so long inputs are collapsible and scrollable like VerifySendScreen.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@app/src/main/java/com/vultisig/wallet/ui/screens/send/VerifySendScreen.kt`:
- Around line 391-410: The Row currently uses the LTR-locked
Arrangement.Absolute.SpaceBetween which prevents mirroring in RTL locales;
update the arrangement to the locale-aware Arrangement.SpaceBetween in the Row
declaration so the Text and IconButton positions flip correctly in RTL. Keep the
rest of the Row and children (Text, IconButton, isExpanded toggle, UiIcon
rotation) unchanged so only the arrangement behavior is corrected.
In
`@app/src/main/java/com/vultisig/wallet/ui/screens/transaction/SendTxOverviewScreen.kt`:
- Around line 76-97: Hero typography mismatches: SendTxOverviewScreen currently
uses Theme.brockmann.headings.title2 for tx.functionName while
TransactionDoneScreen and VerifySendScreen use headings.title3; pick the correct
Figma-approved heading (likely headings.title3), update SendTxOverviewScreen to
use that same Theme.brockmann.headings.title3 for the tx.functionName Text, and
verify all three screens (SendTxOverviewScreen, TransactionDoneScreen,
VerifySendScreen) render the same style for tx.functionName to ensure
consistency.
In
`@data/src/main/kotlin/com/vultisig/wallet/data/repositories/ContractCallExtractor.kt`:
- Around line 119-128: The current extraction code parses argsJson into a
JsonArray and unconditionally calls it.jsonPrimitive.content on every element,
which fails if an element is a nested array/object (e.g., tuple) and causes the
whole runCatching to return null; change the logic in ContractCallExtractor so
you parse argsJson with json.parseToJsonElement and cast to JsonArray but do NOT
call jsonPrimitive.content on every element up front—instead keep the
JsonElement list and only convert the specific indices you need (the indices
computed from paramTypes like uint256Idx, addressIdx/targetIdx) to primitive
content when reading those slots; use safe checks (is JsonPrimitive) or
toString/structured parsing for tuple slots so non-primitive elements no longer
abort the whole extraction.
---
Nitpick comments:
In
`@app/src/main/java/com/vultisig/wallet/ui/components/buttons/VsHoldableButton.kt`:
- Line 37: The caller-controlled holdDuration parameter can desynchronize the
visual fill and the actual long-press event (combinedClickable.onLongClick fires
at ViewConfiguration.longPressTimeoutMillis regardless), so remove the
holdDuration parameter from the VsHoldableButton API and always source the
duration from LocalViewConfiguration.current.longPressTimeoutMillis inside the
component; update any references to holdDuration within VsHoldableButton (e.g.,
animation timing, CombinedClickable/combinedClickable usage and any local
variables) to use the platform timeout and remove or adapt any external call
sites that passed holdDuration.
In `@app/src/main/java/com/vultisig/wallet/ui/screens/send/VerifySendScreen.kt`:
- Around line 412-440: The panel is using a stroke token for its fill; update
the Modifier.background call inside the AnimatedVisibility block (the Column
that wraps VerifyCardJsonDetails) to use a background token such as
Theme.v2.colors.backgrounds.secondary or .tertiary instead of
Theme.v2.colors.variables.bordersLight so the expanded panel (where
functionSignature/functionInputs are shown) follows background theming and Figma
tokens.
- Around line 402-409: The chevron rotation currently snaps because rotationZ is
set directly; replace it with an animated float using animateFloatAsState (e.g.,
val rotation by animateFloatAsState(targetValue = if (isExpanded) 180f else 0f,
animationSpec = ...) ) and use that animated value in
Modifier.graphicsLayer(rotationZ = rotation) inside the IconButton in
VerifySendScreen (the block with IconButton/UiIcon), so the chevron rotation is
smooth and synced with the AnimatedVisibility timing.
In `@app/src/main/java/com/vultisig/wallet/ui/screens/TransactionDoneScreen.kt`:
- Around line 260-280: TransactionDoneScreen currently renders long ABI fields
as flat OtherField rows (surrounded by UiHorizontalDivider), causing unbounded
expansion; replace the functionSignature and functionInputs blocks with the same
TransactionDetailsSection used in VerifySendScreen so they are rendered inside a
collapsible chevron + scrollable card. Specifically, remove the OtherField +
UiHorizontalDivider pairs for transaction.functionSignature and
transaction.functionInputs and invoke TransactionDetailsSection (passing the
appropriate title strings and the functionSignature/functionInputs values) so
long inputs are collapsible and scrollable like VerifySendScreen.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: b2b2487a-634c-43bf-b210-18af25e295c5
📒 Files selected for processing (8)
app/src/main/java/com/vultisig/wallet/ui/components/buttons/VsHoldableButton.ktapp/src/main/java/com/vultisig/wallet/ui/models/VerifyTransactionViewModel.ktapp/src/main/java/com/vultisig/wallet/ui/models/keysign/JoinKeysignViewModel.ktapp/src/main/java/com/vultisig/wallet/ui/screens/TransactionDoneScreen.ktapp/src/main/java/com/vultisig/wallet/ui/screens/send/VerifySendScreen.ktapp/src/main/java/com/vultisig/wallet/ui/screens/transaction/SendTxOverviewScreen.ktdata/src/main/kotlin/com/vultisig/wallet/data/repositories/ContractCallExtractor.ktdata/src/main/kotlin/com/vultisig/wallet/data/repositories/TokenRepository.kt
✅ Files skipped from review due to trivial changes (1)
- app/src/main/java/com/vultisig/wallet/ui/models/keysign/JoinKeysignViewModel.kt
🚧 Files skipped from review as they are similar to previous changes (1)
- data/src/main/kotlin/com/vultisig/wallet/data/repositories/TokenRepository.kt
|
Gonna pick up the PR to finish the changes |
ContractCallExtractor + the per-protocol registry + the MAX_UINT256 sentinel were a stepping-stone for showing what's actually moving in EVM contract calls. Blockaid simulation in #4306 supersedes them: it provides the authoritative Send/Swap hero, and HeroContent.Unverified covers the cannot-resolve case explicitly. Keeping the resolver alongside Blockaid leaves a front-runnable code path in the build — the registry keys on bare 4byte function names, so a poisoned 4byte signature plus matching strategy could surface a fabricated "Supply 100 USDC" headline even after the Blockaid hero ships. Removes - ContractCallExtractor + TokenAndAmount + ExtractionStrategy + the hand-curated DeFi registry + MAX_UINT256 sentinel + sentinelLabelFor - TokenRepository.getBuiltInTokenByContract (only consumer was the resolver) - JoinKeysignViewModel.resolveContractCall plus the tokenDisplay / resolvedToken fields it produced. FunctionInfo, TransactionDetailsUiModel, and UiTransactionInfo now carry only the 4byte-derived signature / inputs / functionName triple. - The duplicate "Amount" rows + trio guards in VerifySendScreen, TransactionDoneScreen, and SendTxOverviewScreen - The withContext(Dispatchers.IO) wrap at the call site; the suspend fn owns its own dispatcher boundary now. Adds - prettifyEvmFunctionName: splits on camelCase boundaries and title-cases each word, so supplyWithPermit becomes "Supply With Permit" instead of "SupplyWithPermit". Bundled cleanups - VsHoldableButton: drop the holdDuration parameter. No call site uses a custom value, and the parameter invited the bar-vs-onLongClick desync flagged by aminsato. Animation duration is now hardcoded to LocalViewConfiguration.current.longPressTimeoutMillis so the fill always completes when combinedClickable's onLongClick fires. - AbiToJson.parseBigIntAsJsonString: catch NumberFormatException specifically (BigInteger's only declared throw) and fall through to JsonNull instead of forwarding the raw input string. The args JSON is rendered verbatim in the signing UI; raw forwarding let lookalike Unicode / RTL overrides / zero-width codepoints in attacker-controlled ABI args reach the screen. - VerifySendScreen TransactionDetailsSection chevron row: locale-aware Arrangement.SpaceBetween instead of Arrangement.Absolute.SpaceBetween, which is LTR-locked.
|
🤖 Pushed Bundled cleanups while in the diff:
10 files, +63 / −305. Replied inline on each thread. Generated by Claude |
There was a problem hiding this comment.
Actionable comments posted: 2
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
app/src/main/java/com/vultisig/wallet/ui/components/buttons/VsHoldableButton.kt (1)
132-142:⚠️ Potential issue | 🟡 Minor | ⚡ Quick win
VsHoldableButtonDisabledPreviewpassesEnabledstate, never renders the disabled appearance.Line 137 should be
VsButtonState.Disabled. As-is, both previews render the enabled style, so the disabled UI is invisible in Android Studio's preview panel.🐛 Proposed fix
private fun VsHoldableButtonDisabledPreview() { VsHoldableButton( label = "Hold (Disabled)", - enabled = VsButtonState.Enabled, + enabled = VsButtonState.Disabled, onLongClick = {}, onClick = {}, modifier = Modifier.fillMaxWidth(), ) }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@app/src/main/java/com/vultisig/wallet/ui/components/buttons/VsHoldableButton.kt` around lines 132 - 142, The preview function VsHoldableButtonDisabledPreview incorrectly passes VsButtonState.Enabled so the disabled UI never renders; update the call inside VsHoldableButtonDisabledPreview to pass VsButtonState.Disabled for the enabled/state parameter (the VsHoldableButton invocation) so Android Studio shows the disabled appearance in the preview.
♻️ Duplicate comments (2)
app/src/main/java/com/vultisig/wallet/ui/screens/transaction/SendTxOverviewScreen.kt (1)
79-85:⚠️ Potential issue | 🟡 Minor | ⚡ Quick winHero typography is still
title2here whileVerifySendScreenusestitle3for the same field.Line 81 uses
Theme.brockmann.headings.title2;VerifySendScreenline 201 usesheadings.title3. A user sees the hero grow when navigating from the verify screen to the done/overview screen for the same transaction.🔧 Proposed fix
- style = Theme.brockmann.headings.title2, + style = Theme.brockmann.headings.title3,🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@app/src/main/java/com/vultisig/wallet/ui/screens/transaction/SendTxOverviewScreen.kt` around lines 79 - 85, The hero typography in SendTxOverviewScreen is using Theme.brockmann.headings.title2 for the tx.functionName Text, causing a size jump compared to VerifySendScreen which uses headings.title3; update the Text style in SendTxOverviewScreen (the Text that displays tx.functionName) to use Theme.brockmann.headings.title3 so both screens use the same hero typography.app/src/main/java/com/vultisig/wallet/ui/models/keysign/JoinKeysignViewModel.kt (1)
1339-1353:⚠️ Potential issue | 🟠 Major | 🏗️ Heavy lift
functionName(the signing-screen hero) is sourced from an unauthenticated, crowdsourced registry.
prettifyEvmFunctionNameprocesses text returned byFourByteRepository, which queries 4byte.directory withordering=created_at+firstOrNull(). The registry is crowdsourced and has no signature-authenticity guarantees: anyone can submit a fraudulent text for any 4-byte selector before the legitimate entry, causing every signing device to display an attacker-chosen string as the prominent action hero (e.g., "Supply 100 USDC" for atransferFromcall). The reviewerrkokhatskyialready raised this concern in the PR thread with the same risk analysis and proposed mitigations.
🧹 Nitpick comments (1)
app/src/main/java/com/vultisig/wallet/ui/models/keysign/JoinKeysignViewModel.kt (1)
1327-1334: ⚡ Quick win
decodeFunctionArgsfailure silently dropsfunctionNameeven when the signature was resolved.If
fourByteRepository.decodeFunctionArgsreturnsnullthe entireFunctionInfois null (line 1329return@withContext null), sofunctionNameis never set and the hero falls back to the native token display — even though the 4byte signature lookup succeeded. MakingFunctionInfo.inputsnullable preserves the function name in the hero when only arg-decoding fails:♻️ Proposed fix
internal data class FunctionInfo( val signature: String, - val inputs: String, + val inputs: String?, val functionName: String? = null, )return withContext(Dispatchers.IO) { val functionSignature = fourByteRepository.decodeFunction(memo) ?: return@withContext null - val functionInputs = - fourByteRepository.decodeFunctionArgs(functionSignature, memo) - ?: return@withContext null FunctionInfo( signature = functionSignature, - inputs = functionInputs, + inputs = fourByteRepository.decodeFunctionArgs(functionSignature, memo), functionName = prettifyEvmFunctionName(functionSignature), ) }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@app/src/main/java/com/vultisig/wallet/ui/models/keysign/JoinKeysignViewModel.kt` around lines 1327 - 1334, The current logic returns null when fourByteRepository.decodeFunctionArgs(functionSignature, memo) returns null, which drops the FunctionInfo (so prettifyEvmFunctionName is never used); instead, make FunctionInfo.inputs nullable and change the call in JoinKeysignViewModel to always construct FunctionInfo with signature = functionSignature, functionName = prettifyEvmFunctionName(functionSignature), and inputs = resultOfDecode (which may be null) rather than returning null; update the FunctionInfo data class (and any usages) to accept inputs: List<...>? so the hero can display the resolved functionName even if arg decoding failed.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@app/src/main/java/com/vultisig/wallet/ui/screens/send/VerifySendScreen.kt`:
- Around line 404-431: The inner scrolling container in
TransactionDetailsSection prevents users from reaching long JSON because the
outer Column already has a verticalScroll; remove the inner .heightIn(max =
300.dp) and .verticalScroll(rememberScrollState()) calls from the Column inside
AnimatedVisibility so VerifyCardJsonDetails (used with functionSignature and
functionInputs) can expand and rely on the parent scroll. Keep the
RoundedCornerShape background, padding and spacing but let the content size
naturally so the outer scroll handles all vertical scrolling.
- Around line 394-401: The IconButton in VerifySendScreen.kt is using
Modifier.size(16.dp) which conflicts with IconButton's internal 40.dp sizing;
replace the IconButton wrapper around UiIcon (the block that toggles isExpanded)
with a lightweight container such as Box (or make UiIcon itself handle clicks)
using Modifier.size(16.dp).clickable { isExpanded = !isExpanded } so the chevron
renders at 16.dp and the click behavior still toggles isExpanded; update the UI
element wrapping UiIcon (the onClick handler currently passed to IconButton) to
the new clickable container or UiIcon.onClick.
---
Outside diff comments:
In
`@app/src/main/java/com/vultisig/wallet/ui/components/buttons/VsHoldableButton.kt`:
- Around line 132-142: The preview function VsHoldableButtonDisabledPreview
incorrectly passes VsButtonState.Enabled so the disabled UI never renders;
update the call inside VsHoldableButtonDisabledPreview to pass
VsButtonState.Disabled for the enabled/state parameter (the VsHoldableButton
invocation) so Android Studio shows the disabled appearance in the preview.
---
Duplicate comments:
In
`@app/src/main/java/com/vultisig/wallet/ui/screens/transaction/SendTxOverviewScreen.kt`:
- Around line 79-85: The hero typography in SendTxOverviewScreen is using
Theme.brockmann.headings.title2 for the tx.functionName Text, causing a size
jump compared to VerifySendScreen which uses headings.title3; update the Text
style in SendTxOverviewScreen (the Text that displays tx.functionName) to use
Theme.brockmann.headings.title3 so both screens use the same hero typography.
---
Nitpick comments:
In
`@app/src/main/java/com/vultisig/wallet/ui/models/keysign/JoinKeysignViewModel.kt`:
- Around line 1327-1334: The current logic returns null when
fourByteRepository.decodeFunctionArgs(functionSignature, memo) returns null,
which drops the FunctionInfo (so prettifyEvmFunctionName is never used);
instead, make FunctionInfo.inputs nullable and change the call in
JoinKeysignViewModel to always construct FunctionInfo with signature =
functionSignature, functionName = prettifyEvmFunctionName(functionSignature),
and inputs = resultOfDecode (which may be null) rather than returning null;
update the FunctionInfo data class (and any usages) to accept inputs: List<...>?
so the hero can display the resolved functionName even if arg decoding failed.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: 95bafe83-502f-4463-944c-c6e4cd26830a
📒 Files selected for processing (8)
app/src/main/java/com/vultisig/wallet/ui/components/buttons/VsHoldableButton.ktapp/src/main/java/com/vultisig/wallet/ui/models/VerifyTransactionViewModel.ktapp/src/main/java/com/vultisig/wallet/ui/models/keysign/JoinKeysignViewModel.ktapp/src/main/java/com/vultisig/wallet/ui/screens/TransactionDoneScreen.ktapp/src/main/java/com/vultisig/wallet/ui/screens/send/VerifySendScreen.ktapp/src/main/java/com/vultisig/wallet/ui/screens/transaction/SendTxOverviewScreen.ktapp/src/main/java/com/vultisig/wallet/ui/screens/transaction/UiModelOverviewExtensions.ktdata/src/main/kotlin/com/vultisig/wallet/data/utils/AbiToJson.kt
🚧 Files skipped from review as they are similar to previous changes (3)
- app/src/main/java/com/vultisig/wallet/ui/models/VerifyTransactionViewModel.kt
- app/src/main/java/com/vultisig/wallet/ui/screens/transaction/UiModelOverviewExtensions.kt
- app/src/main/java/com/vultisig/wallet/ui/screens/TransactionDoneScreen.kt
…n_function_display # Conflicts: # app/src/main/java/com/vultisig/wallet/ui/models/VerifyTransactionViewModel.kt # app/src/main/java/com/vultisig/wallet/ui/screens/send/VerifySendScreen.kt
- VerifySendScreen TransactionDetailsSection: drop the inner verticalScroll + heightIn(300.dp) cap. Outer scaffold already has a verticalScroll, so the nested one trapped the last lines of long function signatures / inputs out of reach. - VerifySendScreen chevron toggle: replace IconButton (which enforces a 40dp minimum touch target) with a 16dp Box + clickable so the chevron renders at its intended size. - VsHoldableButton disabled preview: pass VsButtonState.Disabled so the preview actually shows the disabled appearance. - SendTxOverviewScreen function-name hero: align typography with VerifySendScreen / TransactionDoneScreen at headings.title3 instead of title2. - JoinKeysignViewModel.getTransactionFunctionInfo: make FunctionInfo.inputs nullable. Args decoding is best-effort now — a malformed ABI / unsupported tuple shape no longer drops the whole FunctionInfo, so the function name still surfaces as the small-text caption above the hero.
|
🤖 Pushed
Also merged latest The duplicate finding about the 4byte trust boundary on Generated by Claude |
|
Two PR-level notes (not tied to specific lines):
|
…iders - prettifyEvmFunctionName: detect missing `(` via `substringBefore`'s `missingDelimiterValue = ""` instead of comparing lengths; hoist the boundary regex into a compiled constant; extend the regex with a second alternation so acronym-prefixed names split too (`WBTCSwap` → `"WBTC Swap"`). Digit-prefixed names like `ERC20transferFrom` are still left as-is — splitting on digits produces uglier output than it fixes. - TransactionDoneScreen: drop the explicit UiHorizontalDivider() before the function-signature and function-inputs rows. OtherField already renders its own bottom divider, so the explicit one above stacked two horizontal lines and orphaned a leading divider when memo was absent.
Summary
Supply With Permit) as a text title above the transaction details, replacing the misleading native-amount card. Falls back to the existingVsOverviewTokenfor plain sends and other chains.VsOverviewTokengains awithContainerparam so it can be embedded without its own background/border (used by the verify screen).VsHoldableButton: drop the unusedholdDurationparameter; animation duration is now hardcoded to the platform long-press timeout so the fill always finishes whenonLongClickfires.AbiToJson.parseBigIntAsJsonString: catchNumberFormatExceptiononly and fall back toJsonNullinstead of forwarding raw input — closes the lookalike-Unicode / RTL-override path into the rendered function-inputs box.Scope and follow-up
The resolved-amount hero (
HeroContent.Send/Swap) and the explicitHeroContent.Unverifiedwarning state ship with the Blockaid simulation work in #4306, which stacks on this branch. The 4byte-driven function-name caption in this PR becomes the small-text title above that hero.Platforms
Android part. Companion PRs:
paaao/dapp-display-improvementpaaao/dapp_function_displayTest plan
VsOverviewTokenhero, native amount + fiat rows)decodeFunctionArgsfails → function name still surfaces, function-inputs row is absentonLongClickfires./gradlew :app:assembleDebug :app:testDebugUnitTest :data:testDebugUnitTest🤖 Generated with Claude Code
Summary by CodeRabbit
New Features
Refactor