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 (17)
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
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
dac0d16a #5 2025-11-24 17:34:18 ~18 min android/arm64 📄log
Commit #️⃣ Finished (UTC) Duration Platform Result
6279b745 #6 2025-11-25 17:49:18 ~32 min android/arm64 📄log
✖️ fe7146c #5 2025-12-03 02:21:40 ~13 min ios/aarch64 📱ipa
✔️ fe7146c #9 2025-12-03 14:15:35 ~13 min android/arm64 🤖apk 📲

@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

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 :)

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

@caveman-eth
Copy link
Collaborator Author

@igor-sirotin any info on the CI runs? Is it because the status-go PR isn't merged?

@igor-sirotin
Copy link
Contributor

@igor-sirotin any info on the CI runs? Is it because the status-go PR isn't merged?

Sorry for the delay. Let's ping infra one more time.
@siddarthkay can you please take a look? 🙏

It is not related to status-go PR for sure, except for this one. It will pass when you point to develop branch after merging:
image

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.

Thank you for the alignments! Looks good to me from the UI side.

I think ideally we should have some review from wallet team. @dlipicar @Khushboo-dev-cpp could you take a look?

import QtQuick

QtObject {
function getContactPublicKeyByAddress(address) {
Copy link
Member

Choose a reason for hiding this comment

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

The general rule we try to follow and fully align to in the future is to keep those stubs empty. It's up to tests/pages to provided mocked impl.


property bool showSavedAddresses
property bool isAccountTokensReloading
property int lastReloadTimestamp: Date.now() / 1000
Copy link
Member

Choose a reason for hiding this comment

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

It's ok now, but when converted to non-singleton we will make it an empty stub as well, moving the mock to e.g. storybook/mocks

id: root

property SharedStores.NetworkConnectionStore networkConnectionStore
property var rootStore // Injected from parent, not singleton
Copy link
Member

Choose a reason for hiding this comment

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

it could be strictly typed

property alias starButton: starButton

signal openSendModal(string recipient)
signal menuRequested(string name, string address, string ensName, var tags)
Copy link
Member

Choose a reason for hiding this comment

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

basically it's not necessary (and not recommended) to pass via a signal stuff that is already known to the caller (as the caller provided it via properties).

import QtQuick

QtObject {
function getContactPublicKeyByAddress(address) {
Copy link
Member

Choose a reason for hiding this comment

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

Ideally stubs should remain empty, it's up to tests/pages to provide mocks. If needed to be shared, can go to storybook/mocks as well.

@jrainville
Copy link
Member

@caveman-eth I forked your branch and opened a test PR with it here: #19446

The goal is to have the full CI suite running. There is no easy way to do it for external contributors safely.

If that PR passes, we can merge your work once you rebase! Make sure to rebase the status-go PR first. We'll merge that one, then you can point to the develop commit containing your fix and we'll merge the desktop branch!

@jrainville
Copy link
Member

It seems like both mobile builds fail on CI on your PR, even though they work in other PRs, so it's something specific here.

Sadly, the build is not very nice with the logs:


[2025-12-02T17:03:45.201Z] Building Android mobile library...

[2025-12-02T17:03:45.202Z] CC="/opt/android-sdk/ndk/27.2.12479018/toolchains/llvm/prebuilt/linux-x86_64/bin/clang --target=aarch64-linux-android28 --sysroot=/opt/android-sdk/ndk/27.2.12479018/toolchains/llvm/prebuilt/linux-x86_64/sysroot" CGO_ENABLED=1 GOOS=android GOARCH=arm64 CGO_LDFLAGS="-Os -flto" CGO_CFLAGS="-Os -flto -fembed-bitcode -I//usr/lib/jvm/java-17-openjdk-amd64/include -I//usr/lib/jvm/java-17-openjdk-amd64/include/darwin" \

[2025-12-02T17:03:45.203Z] go build -buildmode=c-shared -tags 'gowaku_no_rln nowatchdog disable_torrent' \

[2025-12-02T17:03:45.203Z] 	-ldflags="-checklinkname=0 -X github.com/status-im/status-go/vendor/github.com/ethereum/go-ethereum/metrics.EnabledStr=true" \

[2025-12-02T17:03:45.203Z] 	-o "build/bin/libstatus.so" ./build/bin/statusgo-lib

[2025-12-02T17:04:26.620Z] Android library built

[2025-12-02T17:04:26.621Z] build/bin/libstatus.so: ELF 64-bit LSB shared object, ARM aarch64, version 1 (SYSV), dynamically linked, Go BuildID=LZCbP7mBcPY4HJU1GXQX/nlbSh2JARKxGwa_EIyFV/TNtvlZlnzycWiPIb7J9I/sN4ozfXXuCkoupr40JBV, BuildID[sha1]=5be9fecfb8ca45ef9f7be7398625dca561f88554, with debug_info, not stripped

[2025-12-02T17:04:26.621Z] make[3]: Leaving directory '/home/jenkins/workspace/status-desktop-android/3/vendor/status-go'

[2025-12-02T17:04:26.621Z] Copying library to mobile lib directory

[2025-12-02T17:04:26.621Z] Building Status Desktop Lib

[2025-12-02T17:04:29.914Z] make[2]: *** [Makefile:69: /home/jenkins/workspace/status-desktop-android/3/mobile/lib/android/qt6/libnim_status_client.so] Error 1

[2025-12-02T17:04:29.914Z] make[2]: Leaving directory '/home/jenkins/workspace/status-desktop-android/3/mobile'

[2025-12-02T17:04:29.914Z] make[1]: *** [Makefile:95: apk] Error 2

[2025-12-02T17:04:29.914Z] make[1]: Leaving directory '/home/jenkins/workspace/status-desktop-android/3/mobile'

[2025-12-02T17:04:29.914Z] make: *** [Makefile:908: mobile-build] Error 2

script returned exit code 2

To get better logs, we need to use the V=1 arg. If you haven't setup the mobile, I can try to do it myself and put the logs here

@jrainville
Copy link
Member

jrainville commented Dec 2, 2025

@caveman-eth I ran the build with V=1 and it logged the issue:

/home/jonathan/dev/status-desktop2/src/app_service/service/following_address/service.nim(39, 8) Error: cannot bind another '=destroy' to: Service:ObjectType; previous declaration was constructed here implicitly: /home/jonathan/dev/status-desktop2/src/app_service/service/following_address/service.nim(36, 5)
make[1]: *** [Makefile:69: /home/jonathan/dev/status-desktop2/mobile/lib/android/qt6/libnim_status_client.so] Error 1
make[1]: Leaving directory '/home/jonathan/dev/status-desktop2/mobile'
make: *** [Makefile:908: mobile-build] Error 2

The delete proc at the bottom of the file. That's what we do for the other services. Looks like a weird issue with QtObject. It's particularly weird because it only happens in mobile builds.

It happened in three files. I pushed a commit in my own PR to fix it. You can cherry-pick it to yours: 83b827d

@jrainville
Copy link
Member

Alright! Everything passes in #19446 now!

So @caveman-eth just rebase the status-go branch. Once it's merged, rebase this one and point to develop's commit and use the commit I created here.

@caveman-eth
Copy link
Collaborator Author

caveman-eth commented Dec 2, 2025

Alright! Everything passes in #19446 now!

So @caveman-eth just rebase the status-go branch. Once it's merged, rebase this one and point to develop's commit and use the commit I created here.

@jrainville
Excellent, thanks for digging through and solving that.
I've rebased the status-go pr.
I've noticed now there are waku builds failing, is that an issue?

@jrainville
Copy link
Member

Alright! Everything passes in #19446 now!
So @caveman-eth just rebase the status-go branch. Once it's merged, rebase this one and point to develop's commit and use the commit I created here.

@jrainville Excellent, thanks for digging through and solving that. I've rebased the status-go pr. I've noticed now there are waku builds failing, is that an issue?

Those builds are "beta" we could say. They are very flaky right now and not required to merge, so it's fine

caveman-eth and others added 7 commits December 2, 2025 22:20
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.
Corrected indentation for 'avatar' and 'isFollowingAddress' properties.

Advanced the status-go submodule pointer to live commit 4c3fda7768ed7d35abb187877aac5962e3ee19bc

Fix spacing property to handle undefined values

Updated the spacing assignment to use optional chaining
@caveman-eth
Copy link
Collaborator Author

@jrainville I rebased and also added in your commit, but seems that the mobile builds are still failing here.

@jrainville
Copy link
Member

@jrainville I rebased and also added in your commit, but seems that the mobile builds are still failing here.

Looks like a normal flaky error. I'll reset my PR to your changes to double check, but if all is good I'll merge 😄

@jrainville
Copy link
Member

Everything passed! Merging. Congrats on your amazing work @caveman-eth

@jrainville jrainville merged commit 0d0665f into status-im:master Dec 3, 2025
11 of 15 checks passed
@caveman-eth
Copy link
Collaborator Author

caveman-eth commented Dec 3, 2025

Everything passed! Merging. Congrats on your amazing work @caveman-eth

@jrainville
Amazing 🫡
Thanks for all the help and reviews 💪

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