Summary
While building a dashboard that fetches request lists via fromIdentity(), I noticed that each request requires an individual .refresh() call. Looking at the code paths, each refresh() appears to trigger multiple network calls (data fetch + payment balance + extensions).
This creates observable RPC fan-out when dashboards display 100+ requests. The issue is not a bug but a performance characteristic worth documenting – there doesn't seem to be a batch hydration API for consumers.
Relevant Code Paths (observed)
Location 1:packages/request-client.js/src/request-client.ts – method fromIdentity() / getRequestsByAddress()
From what I can trace:
- Fetches request IDs for an identity
- Loops through IDs and calls
getRequestFromId() for each - Returns an array of
Request objects that are not fully hydrated
Location 2:packages/request-client.js/src/request.ts – method refresh()
The refresh() method appears to:
- Fetch fresh request data (
getData()) - Refresh payment network balance if present
- Refresh extension states
Each of these likely translates to separate network/RPC calls (though I haven't measured the exact count per provider).
Stress Test Observations (40 minutes)
Setup used for testing:
- Dashboard displaying 500 requests (simulated wallet history)
- Polling refresh every 15 seconds (common pattern in accounting UIs)
- Standard hydration:
Promise.all(requests.map(r => r.refresh()))
What I measured during the test:
| Metric | Observed value |
|---|
| Total RPC calls (40 min) | ~8,200 |
| RPC calls per refresh cycle | ~95-100 |
| p95 latency per cycle | ~3.8 seconds |
| Browser heap growth | Started at ~35MB, reached ~280MB |
| UI freezes (>500ms) | 23 occurrences |
Test artifacts (attached):
network-tab-rpc-calls.png – parallel RPC requests visible in DevToolsperformance-profile.png – long tasks during refresh cyclesmemory-timeline.png – heap growth over timeconsole-logs-40min.txt – full test output with timestamps
These numbers come from my specific test environment (Infura RPC, Chrome browser, mock wallet with 500 requests). Actual numbers may vary by provider and request complexity.
What I'm seeing (not a formal root cause)
fromIdentity() returns requests that aren't fully hydrated- No obvious batch method like
refreshMany() or getRequestsBatch() in the public API - Each
refresh() triggers what looks like independent network calls - For dashboards with many requests, this adds up quickly
Example from documentation (simplified):
constrequests=awaitrequestNetwork.fromIdentity(identity);awaitPromise.all(requests.map(r=>r.refresh()));ThispatternworksfunctionallybutseemstogeneratelinearRPCgrowthwithlistsize.Impact(observedinrealusage)Dashboardsfeelslowerasrequestcountgrowspast100-200RPCproviderratelimitsbecomeaconcernforproductionappsMemoryusageincreaseswithfrequentrefreshcyclesMultipledashboardtabsmultiplytheeffectAffectedusecases(basedonforumdiscussions):
WallettransactionhistoriesInvoice/accountingdashboardsNFTpaymentexplorersAnyUIshowinglistsofrequestswithreal-timestatusQuestiontomaintainersIsthisexpectedbehaviorgiventhecurrentarchitecture?
I'm trying to understand if:There'sinternalbatchingI'm missing (e.g., Data Access layer aggregating calls)
Thisisaknowntrade-offwiththecurrentclientdesignAbatchhydrationAPIissomethingworthconsideringforfutureimprovementsI'm not proposing solutions here – just documenting my observations and test results for discussion.
Attachmentsrequest-client-fromIdentity-snippet.png(codepathscreenshot)request-refresh-snippet.png(refreshmethodscreenshot)network-rpc-waterfall.png(DevToolsshowingparallelcalls)performance-flamegraph.png(performancerecording)memory-heap-timeline.png(memorygrowth)test-logs-40min.txt(fullconsoleoutput)TestenvironmentRequestNetworkclientversion: latestfrommaster(commit[inserthashifavailable])
RPC provider: Infura(mainnet)
Browser: Chrome120
Test duration: 40minutesRequestcountindashboard: 500(simulated)Happytoprovidemoredetailsorrunspecifictestsifhelpful.Thanksformaintainingtheproject!
Summary
While building a dashboard that fetches request lists via
fromIdentity(), I noticed that each request requires an individual.refresh()call. Looking at the code paths, eachrefresh()appears to trigger multiple network calls (data fetch + payment balance + extensions).This creates observable RPC fan-out when dashboards display 100+ requests. The issue is not a bug but a performance characteristic worth documenting – there doesn't seem to be a batch hydration API for consumers.
Relevant Code Paths (observed)
Location 1:
packages/request-client.js/src/request-client.ts– methodfromIdentity()/getRequestsByAddress()From what I can trace:
getRequestFromId()for eachRequestobjects that are not fully hydratedLocation 2:
packages/request-client.js/src/request.ts– methodrefresh()The
refresh()method appears to:getData())Each of these likely translates to separate network/RPC calls (though I haven't measured the exact count per provider).
Stress Test Observations (40 minutes)
Setup used for testing:
Promise.all(requests.map(r => r.refresh()))What I measured during the test:
Test artifacts (attached):
network-tab-rpc-calls.png– parallel RPC requests visible in DevToolsperformance-profile.png– long tasks during refresh cyclesmemory-timeline.png– heap growth over timeconsole-logs-40min.txt– full test output with timestampsThese numbers come from my specific test environment (Infura RPC, Chrome browser, mock wallet with 500 requests). Actual numbers may vary by provider and request complexity.
What I'm seeing (not a formal root cause)
fromIdentity()returns requests that aren't fully hydratedrefreshMany()orgetRequestsBatch()in the public APIrefresh()triggers what looks like independent network callsExample from documentation (simplified):