Skip to content

feat: dApp signing — function-name title + collapsible details (Phase 1)#4054

Merged
johnnyluo merged 18 commits into
mainfrom
paaao/dapp_done_screen_function_display
May 3, 2026
Merged

feat: dApp signing — function-name title + collapsible details (Phase 1)#4054
johnnyluo merged 18 commits into
mainfrom
paaao/dapp_done_screen_function_display

Conversation

@realpaaao
Copy link
Copy Markdown
Contributor

@realpaaao realpaaao commented Apr 13, 2026

Summary

  • For EVM contract calls, show the decoded function name (e.g. Supply With Permit) as a text title above the transaction details, replacing the misleading native-amount card. Falls back to the existing VsOverviewToken for plain sends and other chains.
  • Collapsible "Transaction Details" section (chevron + AnimatedVisibility) for the raw function signature and decoded inputs.
  • Suppress the native amount + fiat rows when a function name is present — contract calls typically send 0 ETH, so the duplicate "0 ETH" row was misleading.
  • VsOverviewToken gains a withContainer param so it can be embedded without its own background/border (used by the verify screen).
  • VsHoldableButton: drop the unused holdDuration parameter; animation duration is now hardcoded to the platform long-press timeout so the fill always finishes when onLongClick fires.
  • AbiToJson.parseBigIntAsJsonString: catch NumberFormatException only and fall back to JsonNull instead 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 explicit HeroContent.Unverified warning 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:

  • Extension: vultisig/vultisig-windows — paaao/dapp-display-improvement
  • iOS: vultisig/vultisig-ios — paaao/dapp_function_display

Test plan

  • Plain ETH / USDC send → unchanged layout (VsOverviewToken hero, native amount + fiat rows)
  • EVM contract call (e.g. Aave supply) → function-name title above the details, native "Amount" / "Value" rows suppressed
  • EVM contract call where decodeFunctionArgs fails → function name still surfaces, function-inputs row is absent
  • Collapsible "Transaction Details" expands/collapses; long signatures + inputs scroll via the outer scaffold
  • Verify-side hold-to-confirm: animation fill completes when onLongClick fires
  • ./gradlew :app:assembleDebug :app:testDebugUnitTest :data:testDebugUnitTest

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Display smart contract function names as the transaction "hero"
    • Expandable transaction details section showing function signatures and inputs
  • Refactor

    • Redesigned long-press button interaction for more reliable press/hold behavior
    • Improved handling/serialization of large numeric values in contract data
    • Transaction overviews now suppress native amount when showing contract action details

realpaaao and others added 6 commits April 10, 2026 06:12
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>
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Apr 13, 2026

Note

Reviews paused

It 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 reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Relocates 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.

Changes

Transaction + ABI + UI Flow

Layer / File(s) Summary
Data Shape
app/src/main/java/com/vultisig/wallet/ui/models/VerifyTransactionViewModel.kt, app/src/main/java/com/vultisig/wallet/ui/models/keysign/JoinKeysignViewModel.kt, app/src/main/java/com/vultisig/wallet/ui/screens/transaction/SendTxOverviewScreen.kt
Added nullable functionSignature, functionInputs, and functionName to TransactionDetailsUiModel and FunctionInfo; removed top-level functionSignature/functionInputs from VerifyTransactionUiModel; extended UiTransactionInfo with optional function metadata.
Core Implementation
app/src/main/java/com/vultisig/wallet/ui/models/keysign/JoinKeysignViewModel.kt
Refactored getTransactionFunctionInfo to perform 4byte signature + ABI arg decoding on Dispatchers.IO; populated FunctionInfo.functionName; added prettifyEvmFunctionName to extract/title-case function name.
Data Layer Mapping
app/src/main/java/com/vultisig/wallet/ui/screens/transaction/UiModelOverviewExtensions.kt
TransactionTypeUiModel.toUiTransactionInfo() now copies functionName, functionSignature, and functionInputs into the Send branch's UiTransactionInfo.
UI Components
app/src/main/java/com/vultisig/wallet/ui/components/VsOverviewToken.kt, app/src/main/java/com/vultisig/wallet/ui/components/buttons/VsHoldableButton.kt
VsOverviewToken gains withContainer: Boolean = true to optionally skip background/border/padding; VsHoldableButton replaced custom pointer-loop long-press with combinedClickable + MutableInteractionSource, removed holdDuration parameter, and animates press progress via interaction events.
Screens / Wiring
app/src/main/java/com/vultisig/wallet/ui/screens/send/VerifySendScreen.kt, app/src/main/java/com/vultisig/wallet/ui/screens/TransactionDoneScreen.kt, app/src/main/java/com/vultisig/wallet/ui/screens/transaction/SendTxOverviewScreen.kt
Hero area now shows functionName title for contract calls or falls back to VsOverviewToken; native amount rows suppressed when functionName is present; signature/inputs rendering unified into a new expandable TransactionDetailsSection that displays signature and inputs when available.
ABI Serialization
data/src/main/kotlin/com/vultisig/wallet/data/utils/AbiToJson.kt
Big-integer ABI values (int*/uint*) are serialized as JSON strings (BigInteger.toString()); invalid numeric input now yields JsonNull instead of raw primitive.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Poem

🐰 A rabbit on the branch of code,
I prettify names and lighten load,
Big ints now snug in stringy beds,
Buttons listen, tokens shed their threads,
Transaction tales wear kinder heads. ✨

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 13.79% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly describes the main changes: adding function-name display in a hero section and collapsible transaction details for dApp signing flows.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch paaao/dapp_done_screen_function_display

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@realpaaao
Copy link
Copy Markdown
Contributor Author

@coderabbitai review

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Apr 14, 2026

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@realpaaao
Copy link
Copy Markdown
Contributor Author

@coderabbitai review

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Apr 14, 2026

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 | 🔴 Critical

Add the missing matchParentSize import.

Line 96 calls Modifier.matchParentSize(), but the required import androidx.compose.foundation.layout.matchParentSize is 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 | 🟠 Major

The progress bar animation and onLongClick callback use different, independently configured timeouts.

holdDuration drives the fill animation (line 71), but combinedClickable.onLongClick triggers 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 holdDuration parameter and drive both from a fixed constraint.

Code locations
  • Line 38: holdDuration parameter definition
  • Lines 61–82: Progress animation keyed to holdDuration
  • Lines 87–93: combinedClickable with platform-governed onLongClick
🤖 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 to NumberFormatException.

BigInteger(value) only needs a parse-error fallback here. Catching Exception also hides unrelated failures in this conversion path.

♻️ Proposed change
 private fun parseBigIntAsJsonString(value: String): JsonElement {
     return try {
         val big = BigInteger(value)
         JsonPrimitive(big.toString())
-    } catch (_: Exception) {
+    } catch (_: NumberFormatException) {
         JsonPrimitive(value)
     }
 }
Based on learnings: Do not use a broad catch (`catch (Exception)`) to swallow errors in Kotlin.
🤖 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

📥 Commits

Reviewing files that changed from the base of the PR and between 940c88f and eb4ce57.

📒 Files selected for processing (11)
  • app/src/main/java/com/vultisig/wallet/ui/components/VsOverviewToken.kt
  • app/src/main/java/com/vultisig/wallet/ui/components/buttons/VsHoldableButton.kt
  • app/src/main/java/com/vultisig/wallet/ui/models/VerifyTransactionViewModel.kt
  • app/src/main/java/com/vultisig/wallet/ui/models/keysign/JoinKeysignViewModel.kt
  • app/src/main/java/com/vultisig/wallet/ui/screens/TransactionDoneScreen.kt
  • app/src/main/java/com/vultisig/wallet/ui/screens/send/VerifySendScreen.kt
  • app/src/main/java/com/vultisig/wallet/ui/screens/transaction/SendTxOverviewScreen.kt
  • app/src/main/java/com/vultisig/wallet/ui/screens/transaction/UiModelOverviewExtensions.kt
  • data/src/main/kotlin/com/vultisig/wallet/data/repositories/ContractCallExtractor.kt
  • data/src/main/kotlin/com/vultisig/wallet/data/repositories/TokenRepository.kt
  • data/src/main/kotlin/com/vultisig/wallet/data/utils/AbiToJson.kt

Comment thread app/src/main/java/com/vultisig/wallet/ui/components/buttons/VsHoldableButton.kt Outdated
Comment thread app/src/main/java/com/vultisig/wallet/ui/screens/send/VerifySendScreen.kt Outdated
Comment thread app/src/main/java/com/vultisig/wallet/ui/screens/TransactionDoneScreen.kt Outdated
@johnnyluo
Copy link
Copy Markdown
Contributor

could you please attach some screenshot of how it looks like here?

@realpaaao
Copy link
Copy Markdown
Contributor Author

@johnnyluo
image

- 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.
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

♻️ Duplicate comments (1)
app/src/main/java/com/vultisig/wallet/ui/screens/TransactionDoneScreen.kt (1)

70-83: ⚠️ Potential issue | 🟠 Major

Avoid native-token fallback for decoded send calls with unresolved sentinel token.

On Line 75, resolvedToken ?: token reintroduces the misleading native hero when functionName != 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 matching uint256 / 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

📥 Commits

Reviewing files that changed from the base of the PR and between eb4ce57 and 559c4d8.

📒 Files selected for processing (6)
  • app/src/main/java/com/vultisig/wallet/ui/components/buttons/VsHoldableButton.kt
  • app/src/main/java/com/vultisig/wallet/ui/screens/TransactionDoneScreen.kt
  • app/src/main/java/com/vultisig/wallet/ui/screens/send/VerifySendScreen.kt
  • app/src/main/java/com/vultisig/wallet/ui/screens/transaction/SendTxOverviewScreen.kt
  • app/src/main/java/com/vultisig/wallet/ui/screens/transaction/UiModelOverviewExtensions.kt
  • data/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

Comment thread app/src/main/java/com/vultisig/wallet/ui/models/keysign/JoinKeysignViewModel.kt Outdated
Comment thread app/src/main/java/com/vultisig/wallet/ui/models/keysign/JoinKeysignViewModel.kt Outdated
Comment thread app/src/main/java/com/vultisig/wallet/ui/models/keysign/JoinKeysignViewModel.kt Outdated
Comment thread app/src/main/java/com/vultisig/wallet/ui/models/keysign/JoinKeysignViewModel.kt Outdated
Comment thread data/src/main/kotlin/com/vultisig/wallet/data/repositories/TokenRepository.kt Outdated
Comment thread data/src/main/kotlin/com/vultisig/wallet/data/utils/AbiToJson.kt Outdated
aminsato

This comment was marked as off-topic.

Comment thread app/src/main/java/com/vultisig/wallet/ui/components/buttons/VsHoldableButton.kt Outdated
Comment thread data/src/main/kotlin/com/vultisig/wallet/data/utils/AbiToJson.kt Outdated
@aminsato
Copy link
Copy Markdown
Collaborator

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.

Comment thread data/src/main/kotlin/com/vultisig/wallet/data/utils/AbiToJson.kt Outdated
@aminsato
Copy link
Copy Markdown
Collaborator

@coderabbitai full review

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Apr 19, 2026

✅ Actions performed

Full review triggered.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🧹 Nitpick comments (4)
app/src/main/java/com/vultisig/wallet/ui/components/buttons/VsHoldableButton.kt (1)

37-37: Caller-supplied holdDuration can re-introduce the visual/firing mismatch.

When a caller overrides the default with a value other than the platform long-press timeout, combinedClickable.onLongClick will still fire at ViewConfiguration.longPressTimeoutMillis (~400 ms) regardless of holdDuration, so the fill animation will again be out of sync with the actual long-press callback. Consider either:

  • Documenting that holdDuration is purely cosmetic and cannot extend/shorten the actual long-press window, or
  • Removing the parameter entirely and always using LocalViewConfiguration.current.longPressTimeoutMillis to 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.bordersLight is 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 0f snaps. animateFloatAsState matches 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.

VerifySendScreen shows function signature/inputs inside a collapsible TransactionDetailsSection (chevron + scrollable card), but TransactionDoneScreen lays them out as flat divider-separated OtherField rows. 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 same TransactionDetailsSection here 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

📥 Commits

Reviewing files that changed from the base of the PR and between 559c4d8 and c61df9c.

📒 Files selected for processing (8)
  • app/src/main/java/com/vultisig/wallet/ui/components/buttons/VsHoldableButton.kt
  • app/src/main/java/com/vultisig/wallet/ui/models/VerifyTransactionViewModel.kt
  • app/src/main/java/com/vultisig/wallet/ui/models/keysign/JoinKeysignViewModel.kt
  • app/src/main/java/com/vultisig/wallet/ui/screens/TransactionDoneScreen.kt
  • app/src/main/java/com/vultisig/wallet/ui/screens/send/VerifySendScreen.kt
  • app/src/main/java/com/vultisig/wallet/ui/screens/transaction/SendTxOverviewScreen.kt
  • data/src/main/kotlin/com/vultisig/wallet/data/repositories/ContractCallExtractor.kt
  • data/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

@rkokhatskyi
Copy link
Copy Markdown
Collaborator

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.
@rkokhatskyi
Copy link
Copy Markdown
Collaborator

rkokhatskyi commented May 1, 2026

🤖 Pushed f90ed371b. Dropped the contract-call resolver (ContractCallExtractor + per-protocol DeFi registry + MAX_UINT256 sentinel + the tokenDisplay/resolvedToken fields) — #4306 will own the resolved hero through Blockaid simulation, and keeping the registry path alive next to it would leave a front-runnable surface that the new hero is meant to remove.

Bundled cleanups while in the diff:

  • VsHoldableButton: dropped the unused holdDuration parameter, hardcoded animation to the platform long-press timeout
  • AbiToJson.parseBigIntAsJsonString: catch NumberFormatException only, fall back to JsonNull instead of forwarding raw input
  • TransactionDetailsSection chevron row: Arrangement.SpaceBetween instead of Absolute.SpaceBetween (LTR-locked)

10 files, +63 / −305. Replied inline on each thread.


Generated by Claude

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

VsHoldableButtonDisabledPreview passes Enabled state, 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 win

Hero typography is still title2 here while VerifySendScreen uses title3 for the same field.

Line 81 uses Theme.brockmann.headings.title2; VerifySendScreen line 201 uses headings.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.

prettifyEvmFunctionName processes text returned by FourByteRepository, which queries 4byte.directory with ordering=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 a transferFrom call). The reviewer rkokhatskyi already 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

decodeFunctionArgs failure silently drops functionName even when the signature was resolved.

If fourByteRepository.decodeFunctionArgs returns null the entire FunctionInfo is null (line 1329 return@withContext null), so functionName is never set and the hero falls back to the native token display — even though the 4byte signature lookup succeeded. Making FunctionInfo.inputs nullable 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

📥 Commits

Reviewing files that changed from the base of the PR and between c61df9c and f90ed37.

📒 Files selected for processing (8)
  • app/src/main/java/com/vultisig/wallet/ui/components/buttons/VsHoldableButton.kt
  • app/src/main/java/com/vultisig/wallet/ui/models/VerifyTransactionViewModel.kt
  • app/src/main/java/com/vultisig/wallet/ui/models/keysign/JoinKeysignViewModel.kt
  • app/src/main/java/com/vultisig/wallet/ui/screens/TransactionDoneScreen.kt
  • app/src/main/java/com/vultisig/wallet/ui/screens/send/VerifySendScreen.kt
  • app/src/main/java/com/vultisig/wallet/ui/screens/transaction/SendTxOverviewScreen.kt
  • app/src/main/java/com/vultisig/wallet/ui/screens/transaction/UiModelOverviewExtensions.kt
  • data/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

Comment thread app/src/main/java/com/vultisig/wallet/ui/screens/send/VerifySendScreen.kt Outdated
…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.
@rkokhatskyi
Copy link
Copy Markdown
Collaborator

🤖 Pushed fee609421 addressing the rest of the CodeRabbit review:

  • VsHoldableButtonDisabledPreview now passes VsButtonState.Disabled so the preview actually renders the disabled state
  • SendTxOverviewScreen function-name hero typography aligned to headings.title3 (matches VerifySendScreen and TransactionDoneScreen, no size jump between screens)
  • FunctionInfo.inputs is now nullable: arg decoding is best-effort, so a malformed ABI no longer drops the whole FunctionInfo and the function name still surfaces above the hero

Also merged latest main (commit ba79eb460) so the branch is no longer conflicting.

The duplicate finding about the 4byte trust boundary on functionName is the same concern I raised earlier — addressed by demoting 4byte to a small-text caption (vs. the hero amount, which Blockaid will own in #4306) and falling through to HeroContent.Unverified when Blockaid simulation is unavailable.


Generated by Claude

Copy link
Copy Markdown
Collaborator

@aminsato aminsato left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(Moved to inline comments below.)

Comment thread app/src/main/java/com/vultisig/wallet/ui/models/keysign/JoinKeysignViewModel.kt Outdated
Comment thread app/src/main/java/com/vultisig/wallet/ui/screens/TransactionDoneScreen.kt Outdated
Comment thread data/src/main/kotlin/com/vultisig/wallet/data/utils/AbiToJson.kt
@aminsato
Copy link
Copy Markdown
Collaborator

aminsato commented May 2, 2026

Two PR-level notes (not tied to specific lines):

  • Still-open threads from earlier rounds — several threads from CodeRabbit / earlier reviews reference tokenDisplay / resolvedToken / ContractCallExtractor.kt, which don't exist in the current diff. Worth replying "removed in 559c4d8, scoped to Phase 1" (or similar) on those threads so the review state reflects reality.
  • Test plan in the PR description — still mentions Aave supply with "Unlimited USDC" and Aave withdraw with MAX_UINT256, but the sentinel handling and Blockaid resolution were scoped out. The visible behavior in this PR is just the function-name hero + suppressed native amount. Worth updating the test plan so reviewers know what to actually verify.

…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.
@rkokhatskyi rkokhatskyi changed the title feat: dApp display Phase 1 — hero display + sentinel policy feat: dApp signing — function-name title + collapsible details (Phase 1) May 2, 2026
Copy link
Copy Markdown
Contributor

@johnnyluo johnnyluo left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@johnnyluo johnnyluo enabled auto-merge May 3, 2026 00:05
@johnnyluo johnnyluo added this pull request to the merge queue May 3, 2026
Merged via the queue into main with commit 259b4ac May 3, 2026
2 checks passed
@johnnyluo johnnyluo deleted the paaao/dapp_done_screen_function_display branch May 3, 2026 00:27
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants