Skip to content

Conversation

@caveman-eth
Copy link
Collaborator

@caveman-eth caveman-eth commented Oct 31, 2025

What does the PR do

Adds a new "Following Addresses" tab to the wallet that displays onchain followings from the Ethereum Follow Protocol (EFP). Users can view, search, and save their EFP followings directly in Status.

Issue: #18686
Status-go dependency: status-im/status-go#7052

Features:

  • New wallet address book style tab displaying EFP onchain followings ("friends").
  • Server-side search by name, ENS, or address
  • Pagination with 10 addresses per page
  • Star button to save followings to address book with ENS name pre-filled
  • ENS avatars displayed throughout

Technical implementation:

  • New following_address service module in Nim with async RPC task handling
  • Full MVC module stack integrated into wallet section
  • QML components following master's architecture patterns:
    • FollowingAddressesView - extends RightTabBaseView with custom header
    • WalletFollowingAddressesHeader - custom header component matching SavedAddresses pattern
    • FollowingAddresses - main content component with search and pagination
    • FollowingAddressesDelegate - individual address list item
  • Enhanced SavedAddressActivityPopup and SavedAddressesDelegate to support following addresses
  • Signal-based reactivity for address book changes
  • In-memory caching with debounced search (250ms)

Affected areas

  • Wallet (new tab)
  • Saved Addresses (popup enhancement)

Architecture compliance

  • I am familiar with the application architecture and agreed good practices.

My PR is consistent with this document: QML Architecture Guidelines

Screencapture of the functionality

Video: https://streamable.com/3y4s1f

Image:
efp

Impact on end user

Before:

  • No visibility into onchain EFP followings

After:

  • New tab displays EFP followings with search and pagination
  • One-click save to address book

How to test

  1. Navigate to Wallet → Following Addresses tab
  2. Verify EFP followings are displayed
  3. Test search by entering a name, ENS, or address
  4. Test pagination (forward/back navigation)
  5. Click star button to save an address with pre-filled ENS name
  6. Test refresh button
  7. Verify star icon becomes filled after saving an address

Risk

Low risk. New feature with no modifications to existing wallet functionality.

  • Following addresses are read-only from external API
  • Graceful fallback if API is unreachable (empty state)
  • Error handling prevents crashes
  • 11 unit tests for Go backend

@caveman-eth caveman-eth requested review from alaibe and removed request for a team October 31, 2025 21:48
@caveman-eth caveman-eth changed the title feat: add EFP following addresses to address book feat: add EFP (Ethereum Follow Protocol) addresses to address book Oct 31, 2025
@caveman-eth
Copy link
Collaborator Author

@status-im-auto
Copy link
Member

status-im-auto commented Oct 31, 2025

Jenkins Builds

Click to see older builds (11)
Commit #️⃣ Finished (UTC) Duration Platform Result
221ddba #1 2025-10-31 22:00:30 ~11 min linux/x86_64-nwaku 📄log
✔️ f36c685 #2 2025-11-07 20:32:54 ~16 min linux/x86_64-nwaku 📦tgz
3d9e76c #3 2025-11-07 20:46:49 ~9 min linux/x86_64-nwaku 📄log
3d9e76c #4 2025-11-07 21:00:01 ~9 min linux/x86_64-nwaku 📄log
✔️ dda7ca5 #5 2025-11-10 21:27:23 ~21 min linux/x86_64-nwaku 📦tgz
✔️ ab2ab87 #6 2025-11-10 22:16:31 ~14 min linux/x86_64-nwaku 📦tgz
✔️ fc57e15 #8 2025-11-13 00:18:05 ~15 min linux/x86_64-nwaku 📦tgz
✔️ 06a861e #11 2025-11-19 10:43:51 ~15 min linux/x86_64-nwaku 📦tgz
06a861e #1 2025-11-21 02:54:17 ~8 min ios/aarch64 📄log
9c84ea0 #2 2025-11-21 11:10:23 ~7 min ios/aarch64 📄log
✔️ 9c84ea0 #12 2025-11-21 11:25:34 ~22 min linux/x86_64-nwaku 📦tgz
Commit #️⃣ Finished (UTC) Duration Platform Result
024ac2a #3 2025-11-21 17:55:34 ~7 min ios/aarch64 📄log
✔️ 024ac2a #13 2025-11-21 18:05:31 ~17 min linux/x86_64-nwaku 📦tgz
b64c56f #4 2025-11-21 18:45:25 ~6 min android/arm64 📄log
b64c56f #4 2025-11-21 18:45:31 ~6 min ios/aarch64 📄log
✔️ b64c56f #14 2025-11-21 18:54:18 ~15 min linux/x86_64-nwaku 📦tgz

@caybro
Copy link
Member

caybro commented Nov 6, 2025

Wow, nice job at a first glance! Pls rebase to get rid of the conflicts

Copy link
Member

@jrainville jrainville left a comment

Choose a reason for hiding this comment

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

Midway review. I checked all Nim files for now.

Great job! I think the biggest things are the loading props/events are not used. Also, there are way too many info logs 😅
Can you trim most of them down and maybe switch some of them to debug

Copy link
Member

@jrainville jrainville left a comment

Choose a reason for hiding this comment

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

Super nice job! I finished reviewing. I found no big problem in the QML, though it is less my area of expertise, so let's see what the experts say 😄

id: noFollowingAddresses
Layout.fillWidth: true
Layout.preferredHeight: 44
visible: RootStore.followingAddresses.count === 0
Copy link
Member

Choose a reason for hiding this comment

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

I think we're moving away from using Singleton stores. It makes it hard to test and use the components in Sotrybook.

To "fix" this, just add a property at the top and pass the needed property from the store in the parent.

cc @noeliaSD @micieslak

Copy link
Member

Choose a reason for hiding this comment

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

Oh, I see that the existing components already use RootStore like that. I'll wait to see what the others say, since it seems the other components need refactoring anyway

}

// Full-width divider above pagination (extends beyond content padding)
Rectangle {
Copy link
Member

Choose a reason for hiding this comment

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

I think we already have a component for Separators. @caybro @noeliaSD @micieslak please help here

Copy link
Member

Choose a reason for hiding this comment

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

Yeah, Separator but it's not very useful in this context

Copy link
Member

@caybro caybro left a comment

Choose a reason for hiding this comment

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

Great job! Just a couple of comments, mostly regarding some good QML practices and component structure

@caveman-eth
Copy link
Collaborator Author

@caybro thanks for the feedback, I've made your recommended changes and rebased ontop of the latest commits.

Copy link
Member

@jrainville jrainville left a comment

Choose a reason for hiding this comment

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

Looks good from my side!

I haven't tested manually though

self.allCollectiblesModule.load()
info "wallet-section: loading assetsModule"
self.assetsModule.load()
info "wallet-section: loading savedAddressesModule"
Copy link
Member

Choose a reason for hiding this comment

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

The info logs here can be removed as well

@caveman-eth caveman-eth force-pushed the master branch 2 times, most recently from 6407be9 to a4a17bc Compare November 19, 2025 10:20
@igor-sirotin
Copy link
Contributor

@caybro can you please re-review this PR? Let's get it merged

@caveman-eth
Copy link
Collaborator Author

I have updated the branch with latest upstream commits, and resolved a status-go conflict.

Copy link
Member

@caybro caybro left a comment

Choose a reason for hiding this comment

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

Keep up the good work, we're getting there 💪

import "../stores"
import ".."

import AppLayouts.Wallet.stores as WalletStores
Copy link
Member

Choose a reason for hiding this comment

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

You definitely don't need all those imports :)

property var activeNetworks

readonly property int maxHeight: 341
height: implicitHeight > maxHeight ? maxHeight : implicitHeight
Copy link
Member

Choose a reason for hiding this comment

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

Don't set maxHeight or height at all, the menu will figure it out


StatusAction {
text: {
var savedAddr = WalletStores.RootStore.getSavedAddress(root.address)
Copy link
Member

Choose a reason for hiding this comment

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

You should extract the savedAddr, isSaved etc.. as a readonly property of this StatusAction, and evaluate it only once, instead of multiple times for each property here


font.weight: Font.Medium
textPosition: StatusBaseButton.TextPosition.Left
textColor: Theme.palette.primaryColor1
Copy link
Member

Choose a reason for hiding this comment

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

I think these 3 props are the default already

id: listView
objectName: "FollowingAddressesView_followingAddresses"
anchors.fill: parent
spacing: 8
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
spacing: 8
spacing: Theme.halfPadding

property string lastReloadedTime

/* Indicates whether the content is being loaded */
property bool loading
Copy link
Member

Choose a reason for hiding this comment

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

required

Copy link
Member

Choose a reason for hiding this comment

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

hmm, required is helpful when no value makes no sense and there is no good default value and e.g. component would be rendered incorrectly because of that. In this case required is not needed imo.

BlockchainExplorersMenu {
id: blockchainExplorersMenu
flatNetworks: root.activeNetworks
onNetworkClicked: {
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
onNetworkClicked: {
onNetworkClicked: (shortname, isTestnet) => {

visible: !!root.name
enabled: root.showButtons
type: StatusRoundButton.Type.Quinary
radius: 8
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
radius: 8
radius: Theme.radius

Copy link
Member

Choose a reason for hiding this comment

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

and above too

}

title: name
objectName: name || "followingAddressDelegate"
Copy link
Member

Choose a reason for hiding this comment

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

You usually don't set it here, but rather inside the (List)view, here it's kinda useless

import "../stores"
import ".."

import AppLayouts.Wallet.stores as WalletStores
Copy link
Member

Choose a reason for hiding this comment

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

Again, all those imports needed?

@caveman-eth
Copy link
Collaborator Author

Keep up the good work, we're getting there 💪

@caybro thank you, will address asap

@igor-sirotin
Copy link
Contributor

@caveman-eth we've sent you an invite for collaboration in status-desktop as well.
Please accept, so that CI can be triggered 🙏

Copy link
Member

@micieslak micieslak left a comment

Choose a reason for hiding this comment

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

That's huge, nice work! Definitely good job.

I left some comments mainly regarding qml code structure. I think the doc that may help a lot there is our guide: https://github.com/status-im/status-desktop/blob/master/guidelines/QML_ARCHITECTURE_GUIDE.md

Thanks!

property string lastReloadedTime

/* Indicates whether the content is being loaded */
property bool loading
Copy link
Member

Choose a reason for hiding this comment

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

hmm, required is helpful when no value makes no sense and there is no good default value and e.g. component would be rendered incorrectly because of that. In this case required is not needed imo.

StatusListItem {
id: root

property SharedStores.NetworkConnectionStore networkConnectionStore
Copy link
Member

Choose a reason for hiding this comment

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

Please don't make low-level UI components like delegates dependent directly on stores. Please refer to https://github.com/status-im/status-desktop/blob/master/guidelines/QML_ARCHITECTURE_GUIDE.md#stores for details.

import shared.popups
import shared.stores as SharedStores

import "../popups"
Copy link
Member

Choose a reason for hiding this comment

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

Please use identified module imports whenever possible.

id: root

property SharedStores.NetworkConnectionStore networkConnectionStore
property var activeNetworks
Copy link
Member

Choose a reason for hiding this comment

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

As a model it should be named activeNetworksModel and ideally there should be description of expected roles and their types in the comment.

property var tags
property string avatar

property int usage: FollowingAddressesDelegate.Usage.Delegate
Copy link
Member

Choose a reason for hiding this comment

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

Instead of introducing usage method, it would be cleaner to expose clicked signal and externalize that differentiation from that component.


readonly property bool isAddressSaved: {
savedAddressesVersion
var savedAddr = WalletStores.RootStore.getSavedAddress(root.address)
Copy link
Member

Choose a reason for hiding this comment

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

Please don't use that singleton store there. Delegates should not rely on delegates at all.

Btw, the singleton stores are intended to be refactored to non-singleton components soon.

Probably the easiest option here is to externalize that property (adding top level property isAddressSaved and resolving it externally). It will allow removing from there Connections object as well.

icon.name: d.isAddressSaved ? "star-icon" : "star-icon-outline"
enabled: !d.isAddressSaved
onClicked: {
var nameToUse = root.ensName || root.address
Copy link
Member

Choose a reason for hiding this comment

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

please use "modern" js (const/let instead of var)

property alias starButton: starButton

signal openSendModal(string recipient)
signal menuRequested(var menuModel)
Copy link
Member

Choose a reason for hiding this comment

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

please don't "pack" parametes into signle model. It's shorter but harder to read and maintain. Please use explicit list of parameters with their own types and names.


StatusAction {
text: {
var savedAddr = WalletStores.RootStore.getSavedAddress(root.address)
Copy link
Member

Choose a reason for hiding this comment

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

Please use const/let consistently in the whole pr.

}
objectName: "addToSavedAddressesAction"
enabled: {
var savedAddr = WalletStores.RootStore.getSavedAddress(root.address)
Copy link
Member

Choose a reason for hiding this comment

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

This WalletStore.RootStore is a singleton right now (probably the last one) and will be refactored soon. Because of that, please take this store via public API of that that component (and others as well) and inside the component use it as a regular, non-singleton object.

Copy link
Member

@micieslak micieslak left a comment

Choose a reason for hiding this comment

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

One more quite important thing I forgot in previous comments.

For UI development we use our internal tool Storybook. The UI components have their own "pages" there where are instantiated in separation. It speeds up development and further maintenance. I'd love if you could add pages for the components you created. Thanks!

@caveman-eth
Copy link
Collaborator Author

caveman-eth commented Nov 21, 2025

@caybro @micieslak thank you for your reviews, I've pushed your suggestions now, I appreciate the comments. let me know if I've missed anything :)

Introduces support for EFP following addresses throughout the wallet app, including backend RPCs, service layer, controller, model, and QML UI integration. Adds new modules, views, delegates, and updates wallet section logic to display and manage onchain friends from EFP, with ENS and avatar support. Also updates SavedAddressesDelegate to handle EFP addresses and refines wallet header and layout for the new section.
…ing addresses

Cleans up excessive debug logging and removes unused loading state and cache checks from the following addresses modules and service. Updates the QML views to simplify loading indicators and ensures data is loaded when the user navigates to the following addresses page.

Update status-go submodule to latest commit
Extracted FollowingAddressMenu into a separate QML component and updated FollowingAddressesDelegate to use a menuRequested signal for menu actions. Improved property requirements and data flow in FollowingAddresses and FollowingAddressesView, and updated connections for refreshing and updating following addresses. Minor UI text adjustment in WalletFollowingAddressesHeader and code cleanup in RootStore. This refactor improves modularity, maintainability, and clarity of the following addresses feature.
Eliminated info log statements from the load() method in the wallet_section module to reduce console output and clean up the code.
Refactors FollowingAddresses components to use injected rootStore and activeNetworksModel, improving testability and modularity. Adds storybook pages for FollowingAddressMenu, FollowingAddressesDelegate, and FollowingAddressesView for interactive testing and development. Updates RootStore stubs and related stores to support new signals and mock data. Improves pagination, event handling, and UI consistency in the following addresses workflow.
@igor-sirotin
Copy link
Contributor

@caveman-eth I'm checking with infra team why CI only triggered partially

@caveman-eth
Copy link
Collaborator Author

@caveman-eth I'm checking with infra team why CI only triggered partially

@igor-sirotin Ok thanks. I also don't have read perms for jenkins, so I can't see why its the checks are failing.

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.

6 participants