Skip to content

Load additional NFT pages and release iterator sessions - #118

Open
Jim8y wants to merge 2 commits into
neoorder:masterfrom
Jim8y:fix/audit-p2-06
Open

Jim8y wants to merge 2 commits into
neoorder:masterfrom
Jim8y:fix/audit-p2-06

Conversation

@Jim8y

@Jim8y Jim8y commented Sep 5, 2026

Copy link
Copy Markdown
Contributor

Summary

The wallet currently stops NFT enumeration after 100 items across all curated collections. This change adds explicit load-more pagination so later items and collections remain reachable, while releasing the node iterator session when enumeration finishes, fails, or the wallet page is left.

  • Append pages of up to 100 NFTs; bound iterator and metadata batches to 25, and continue when a node returns a smaller nonempty batch.
  • Add loading, load-more, and localized failure states without treating a failed request as an empty NFT collection.
  • Keep load-more unavailable while replacing a pagination session; queued clicks cannot consume the first page twice or read an already completed session. Preserve the existing serialized cleanup order.
  • Supply the two paging messages in all existing resource locales.
  • Cancel active reads before disposing the iterator; terminate remote sessions with an independent cleanup timeout.
  • Keep the existing curated NFT collection source. No custom-token entry, dAPI asset picker, or transaction broadcasting is added.

Validation

  • dotnet test tests/p2-06/NftPager.Tests.csproj --no-restore --nologo: 22 passed. Covers 137 NFTs across two collections with a seven-item server limit, disposal/cancellation, failure, oversized replies, serialized cleanup, two production-WalletPage handover regressions and 13 satellite locales.
  • iOS 26.5 / iPhone 17 simulator: follow-up 26/26 native assertions passed.
  • Android API 36 / arm64 emulator: follow-up 26/26 native assertions passed.
  • Both native runs exercised the actual WalletPage, TokenManager, and RpcClient using controlled HTTP responses: first 100 items, load-more to all 137 including the second collection, iterator cleanup on completion and leaving, fresh first-page loading on return, and bounded requests. No live assets were signed or broadcast. This is synthetic-inventory integration evidence, not live-chain wallet validation.
  • Follow-up native runs held the actual terminatesession HTTP request. During that wait, two actual load-more handler calls could not start reading the replacement session. After release only its first 100 items loaded; extra clicks after all 137 items completed did not set failure. Each of 13 actual satellite resource sets supplied both keys without parent/English fallback.
  • Follow-up product validation: after removing all external QA entry points, both platforms fully rebuilt with zero warnings/errors, installed and displayed the actual Home/four-tab product UI. No OneGate crash was observed; Android's device-wide buffer retained an earlier unrelated system Bluetooth crash.
  • The follow-up patch against the initial PR commit c159c9a has SHA-256 b9fc1d71adf60ad3e62c30603a46c0d30df4c19ff0455104520343904af1d520; full patch against 623603d is 3d5e097d003edba687aee579b1420034b0f85256a623244fe9bbb169b4633cb5. Both native follow-up runs match these hashes; older 8-check runs are not used as substitute evidence. Screenshots and QA artifacts remain outside the repository; no uploaded screenshot or hosted CI result is claimed here.

Limits and integration notes

  • A node may expire an idle iterator session. This change does not add keepalive or cursor resumption; a later failure preserves already displayed items and asks the user to pull down to restart loading.
  • Leaving the wallet page releases its session. Returning, including from NFT details, reloads the first page of up to 100 items; later-page position and scrolling are not preserved.
  • This is an independent change against master at 623603d634f07eaead14745da87920a356159be0, not a combined validation of other audit branches. When integrating other accepted RpcClient fixes, preserve their transfer/signer checks alongside this cancellation and pagination code; linked-source test projects that include RpcClient will also need the new pager source.
  • Preserve the solution test-project and localization-key union when integrating other independent PRs. The new test project retains a non-build/non-output relationship to the app without triggering MAUI builds for its standalone test run.

Copilot AI lite review requested due to automatic review settings September 5, 2026 08:38

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🟡 Changes recommended

There are a few correctness/UX and consistency issues to address (notably initial load/cleanup ordering in WalletPage and missing localized keys in other satellite .resx files).

Once you've addressed the issues Copilot identified, you can request another Copilot review.

Pull request overview

Adds explicit NFT “load more” pagination to the wallet so enumeration can continue past the first 100 items, while ensuring RPC iterator sessions are terminated on completion, failure, cancellation, or when leaving the wallet page.

Changes:

  • Introduces NftPager + NftPageSession to page NFT enumeration (100 per UI page; bounded 25-item iterator/metadata batches) and reliably dispose/terminate iterator sessions.
  • Updates RpcClient and TokenManager to expose NFT paging as an async stream and to propagate cancellation through RPC calls.
  • Updates WalletPage UI/state to support loading, failure messaging, and a “Load more NFTs” footer; adds localization keys and adds a focused test project validating paging + cleanup behaviors.
File summaries
File Description
tests/p2-06/NftPagerTests.cs Adds unit tests covering multi-collection paging, cancellation/disposal, metadata failure, oversized replies, and cleanup behavior.
tests/p2-06/NftPager.Tests.csproj New test project compiling pager/session sources via linked files and referencing app project without building it.
OneGateApp/Services/TokenManager.cs Switches NFT loading from single-shot list to async page enumeration.
OneGateApp/Services/RPC/RpcClient.cs Adds cancellation-aware RPC send and replaces GetNFTs with iterator-based GetNFTPages plus session termination.
OneGateApp/Services/RPC/NftPager.cs New pager that stitches per-collection iterator traversals into 100-item UI pages with bounded batches.
OneGateApp/Services/NftPageSession.cs New UI session wrapper ensuring safe cancellation + async-enumerator disposal sequencing.
OneGateApp/Pages/WalletPage.xaml.cs Moves wallet NFT loading to paged reads, adds “load more”/loading/failure state, and closes sessions on page exit.
OneGateApp/Pages/WalletPage.xaml Adds footer UI for failure text, spinner, and “Load more NFTs” button; adjusts empty-state visibility.
OneGateApp/Properties/Strings.resx Adds new resource keys for “Load more NFTs” and partial-load failure message (EN).
OneGateApp/Properties/Strings.zh-Hans.resx Adds Simplified Chinese translations for the new NFT paging UI strings.
OneGateApp/Properties/Strings.Designer.cs Adds strongly-typed accessors for the new resource keys.
OneGateApp.slnx Registers the new test project in the solution.
Review details

Files not reviewed (1)

  • OneGateApp/Properties/Strings.Designer.cs: Generated file
  • Files reviewed: 11/12 changed files
  • Comments generated: 3
  • Review effort level: Lite

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment on lines +89 to +93
NftLoadFailed = false;
HasMoreNFTs = true;
IsLoadingNFTs = false;
if (previous is not null) await CloseNftSessionAsync(previous);
await LoadNFTPageAsync(session);
Comment on lines 79 to +84
var tokens = await LoadNep11TokensAsync(includeHiddens);
NFT[] nfts = await rpcClient.GetNFTs(account.ScriptHash, tokens.Select(p => p.Hash).ToArray());
foreach (NFT nft in nfts)
nft.TokenInfo = tokens.First(p => p.Hash == nft.CollectionId);
return nfts;
await foreach (NFT[] nfts in rpcClient.GetNFTPages(account.ScriptHash, tokens.Select(p => p.Hash).ToArray(), cancellationToken))
{
foreach (NFT nft in nfts) nft.TokenInfo = tokens.First(p => p.Hash == nft.CollectionId);
yield return nfts;
}
Comment on lines +1291 to +1296
<data name="LoadMoreNFTs" xml:space="preserve">
<value>Load more NFTs</value>
</data>
<data name="NFTLoadFailed" xml:space="preserve">
<value>Some NFTs could not be loaded. Pull down to retry.</value>
</data>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

2 participants