Skip to content

Contact activity history is scoped to the default wallet and cross-filters boost ids between wallets #1139

Description

@jvsena42

Summary

ActivityRepo.contactActivities queries only the default wallet and applies one wallet's boost ids to every row. Neither is user-visible today, because contact actions are still gated off for hardware activities — but both become real the moment that gate is lifted.

Raised from the iOS port review (synonymdev/bitkit-ios#648), where the gate was lifted and the read side had to be widened to match. Filing here so Android doesn't hit it when it follows.

Why it is latent right now

ActivityDetailScreen.kt:250 still carries the interim gate:

showContactActions = isPaykitEnabled && !uiState.isHardwareActivity,

So no hardware activity can be given a contact through the UI, and a query that skips hardware wallets currently misses nothing.

Not verified: whether any automatic contact-resolution path (e.g. the Paykit private on-chain address resolver) can attach a contact to a hardware row without going through this button. If it can, the first problem below is already live. Worth a look when picking this up.

The two problems

ActivityRepo.contactActivities (ActivityRepo.kt:416):

val txIdsInBoostTxIds = getTxIdsInBoostTxIds()          // defaults to WalletScope.default
getActivities(
    filter = ActivityFilter.ALL,
    sortDirection = SortDirection.DESC,                 // walletId defaults to WalletScope.default
).getOrThrow()
    .filterNot { it.isReplacedSentTransaction(txIdsInBoostTxIds) }
    .filter { PubkyPublicKeyFormat.matches(it.contact(), normalizedKey) }
  1. Default wallet only. getActivities' parameter is walletId: String? = WalletScope.default (ActivityRepo.kt:367), where null means all wallets. No argument is passed, so hardware rows are dropped before contact matching. Once contacts can be assigned to a hardware activity, the assignment succeeds and the contact's screen then never shows that row — a write/read asymmetry.

  2. Boost ids applied across wallets. getTxIdsInBoostTxIds() is default-scoped too (ActivityRepo.kt:320). Widening only the query would test hardware rows against the normal wallet's boost ids. Boost chains never cross wallets, so that is a false match, and it silently hides rows.

The second one is easy to dismiss as theoretical — it isn't. The iOS regression test for it failed against the unfixed code with the hardware row filtered out entirely ([] instead of ["contact-boost-hw"]) when the two wallets shared a transaction id.

Suggested fix

Mirror what ActivityListViewModel's replaced-transaction filtering already does — resolve a boost set per wallet. Filtering by contact first means only the wallets that actually matched get warmed:

val matches = getActivities(
    walletId = null,                                    // global: merges hardware wallets
    filter = ActivityFilter.ALL,
    sortDirection = SortDirection.DESC,
).getOrThrow()
    .filter { PubkyPublicKeyFormat.matches(it.contact(), normalizedKey) }

val txIdsByWallet = matches.map { it.walletId }.distinct()
    .associateWith { getTxIdsInBoostTxIds(it) }

matches.filterNot { it.isReplacedSentTransaction(txIdsByWallet[it.walletId].orEmpty()) }

iOS reference

synonymdev/bitkit-ios#648ActivityService.get(contact:), with two regression tests in BitkitTests/ActivityListTest.swift: one asserting the result set spans both the normal and hardware wallets, one asserting a shared transaction id in two wallets is not cross-filtered. Both were confirmed to fail against the unfixed code.

Note the platforms differ in one respect: iOS caches the boost-tx-id set per wallet, Android re-queries each time (CoreService.kt:1671). The per-wallet fix applies either way, but Android has no cache invalidation to worry about.

Related

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

    Milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions