fix(my-markets): scope creator ownership scan lifecycle to the active wallet - #2426
fix(my-markets): scope creator ownership scan lifecycle to the active wallet#2426Bayyan16 wants to merge 1 commit into
Conversation
|
@Bayyan16 is attempting to deploy a commit to the Khubair Nasir's projects Team on Vercel. A member of the Team first needs to authorize it. |
|
Important Review skippedAuto reviews are disabled on base/target branches other than the default branch. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
|
Independent verification — not an approval (QA/Security own that), just evidence for whoever reviews. Verdict: the tests bind to the fix. Method: faithfully restored #2425 on the PR branch and re-ran the suite. First attempt was wrong and I want to flag it, because the same trap caught me on #2430 and it's the main way this technique produces false accusations. I initially neutralised only the render-time gate: creatorScanKey !== null && creatorScanState.key === creatorScanKey
↓
creatorScanState.key !== nullAll 29 tests passed — which reads as "the tests don't catch the bug". They do. This fix is layered, and I'd only disabled one layer:
With (2) still live the previous wallet's rows are cleared regardless, so green was correct. Your own comment there — "always has a new key and is withheld immediately by render-time gating" — is what tipped me off that there were two. Neutralising both: That's the exact reported symptom, and it's the only test that moves — the other 28 cover behaviour that legitimately shouldn't change. Two notes1. Tested at the component layer. 2. Defence in depth is good, but only one layer is pinned. The single failing test binds to the combination. If someone later deleted the render gate at :331 as redundant, all 29 would stay green — same as the effect-side reset being load-bearing on its own. Worth knowing before either is "simplified" later; not something I'd change now. No changes requested from me. |
|
Thank you for the careful layered mutation check and for correcting the initial partial mutation. I understand that neutralising only the render-time gate did not restore the reported bug because the effect-side reset still cleared the previous wallet’s results. Neutralising both layers caused the exact wallet-switch regression to fail while the remaining tests stayed green, confirming that the component-level test binds to the user-visible invariant. Since no changes were requested, I’ll keep the current PR head unchanged and leave formal review and merge decisions to the appropriate maintainers. |
Summary
Fixes #2425.
This PR makes the
/my-marketscreator ownership lifecycle explicitly wallet-scoped and prevents pending, failed, or stale ownership scans from being rendered as successfully verified results.useCreatedMarkets()performs two complementary creator-detection paths:marketauthhas rotated to a stake-pool PDA.Previously, the LP ownership scan ran asynchronously outside the lifecycle exposed by the hook. The hook returned only the general discovery loading and error state, while creator results were stored in a shared array without identifying the wallet or scan that produced them.
That allowed
/my-marketsto expose three incorrect states:This PR introduces a wallet-scoped creator scan state, propagates its loading and error lifecycle, prevents stale cross-wallet results, and avoids redundant RPC scans when discovery returns a new array with identical content.
Problem
The previous hook contract returned:
However, creator discovery was not complete when
discoveryLoadingbecamefalse.The hook still needed to perform:
to recover completed creator markets through creator-owned LP portfolios.
Because that asynchronous ownership scan was not represented in the returned state, the page could interpret an unresolved or failed scan as a final result.
Previous lifecycle
Ownership scan failures were also caught without being surfaced through the hook error contract, making these two conditions indistinguishable:
Root Cause
1. Creator ownership scanning was not included in loading state
The hook exposed only:
loading: discoveryLoadingThe asynchronous LP-portfolio scan could therefore remain pending after the hook had already reported that loading was complete.
2. Ownership RPC failures were not exposed
A rejected
getProgramAccounts()request did not contribute to the returned error state.The page could therefore render an empty result with:
even though creator ownership verification had failed.
3. Creator results were not associated with their originating wallet
Creator markets were stored in a shared state array without a wallet key or scan identity.
After:
the already committed wallet A results remained available while the wallet B scan was pending.
The existing cancellation guard prevented some late asynchronous writes, but it did not invalidate results that had already been committed for the previous wallet.
4. Discovery array replacements could trigger duplicate scans
Market discovery can return a new array reference while preserving the exact same:
Depending directly on the
marketsarray reference caused an unnecessary ownership RPC scan for content-equivalent discovery results.This could increase RPC load and replace a verified result with an error from a redundant scan.
Implementation
Wallet-scoped creator scan state
The shared creator market array has been replaced with an explicit lifecycle model:
The state now records:
Content-derived scan identity
The creator scan key is derived from the inputs that materially define creator verification:
This ensures that results are valid only for the exact wallet and market identity that produced them.
A changed wallet, market set, program set, or refresh generation produces a new scan key.
Render-time stale-result gating
Creator results are exposed only when:
and the matching scan has completed successfully.
This immediately withholds wallet A results when wallet B becomes active, even before the new effect finishes executing.
The fix does not rely only on calling
setLoading(true)from an effect, because effects run after render and could otherwise allow a transient stale render.Creator scan loading lifecycle
The hook now treats creator verification as loading when:
The returned loading contract is now:
As a result,
/my-marketscannot render a confirmed empty state before creator ownership verification finishes.Creator scan error propagation
Ownership RPC failures now produce a scan-specific error state instead of being converted into a verified empty result.
The returned error contract is:
A failed ownership scan therefore reaches the page's existing recoverable error path.
No
/my-marketspage change is required because the page already renders states in the correct order:The issue was in the hook contract, not the page state ordering.
Complete RPC verification
Configured program scans are executed through
Promise.all().If one required program ownership scan fails, the overall creator result is not committed as a successful partial or empty result.
This prevents incomplete verification from being presented as authoritative.
Individually malformed portfolio accounts remain safely ignored without creating a creator match.
Authority and LP-portfolio detection preserved
The patch preserves both existing creator detection paths:
Incomplete launch
A market remains identifiable when the connected wallet still matches:
Completed launch
After authority rotation, the creator market is recovered through a creator-owned LP portfolio returned by
getProgramAccounts().Trading-only portfolios continue to be excluded through the existing
isLpPortfolio()guard.This PR does not broaden creator ownership detection to trading participants.
Stale async completion protection
Each ownership scan captures its active scan key and market snapshot.
A completion is committed only while the scan remains active. Effect cleanup marks superseded executions as cancelled, preventing older wallet or market-set scans from overwriting newer state.
Same-content discovery stability
The latest discovery objects are stored in a ref:
The creator ownership effect no longer depends directly on the raw
marketsarray reference.Instead, execution is controlled by the content-derived scan key.
This means:
does not trigger another
getProgramAccounts()request.Verified slab identities are still merged with the latest discovery objects, so the hook does not retain stale market object snapshots.
Refresh behavior
The returned
refetch()now:This ensures manual refresh covers both discovery layers.
Behavior Before
Pending ownership scan
Failed ownership scan
Wallet switch
Same-content discovery replacement
Behavior After
Pending ownership scan
Failed ownership scan
Wallet switch
Same-content discovery replacement
Regression Tests
Four regression tests were added to cover the lifecycle corrected by this PR.
1. Pending creator ownership scan keeps the hook loading
Verifies that
loadingremainstrueafter market discovery has finished but whilegetProgramAccounts()is still unresolved.2. Creator ownership RPC failures are surfaced
Verifies that an ownership RPC rejection produces a non-null error instead of returning a successful-looking empty result.
3. Previous-wallet markets are withheld
Verifies that wallet A markets are immediately hidden after wallet B becomes active and before wallet B ownership verification completes.
4. Same-content discovery replacements do not rescan
Verifies that replacing the discovery array with content-equivalent data:
Existing Behavior Preserved
The existing regression suite confirms that the patch continues to:
configV17.marketauth;Validation
Targeted test suite
Result:
TypeScript
pnpm --filter @percolator/app exec tsc --noEmitResult:
Production build
NEXT_PUBLIC_API_URL="http://127.0.0.1:3001" \ pnpm --filter @percolator/app buildResult:
The application compiled successfully and completed static page generation.
Formatting and scope checks
Result:
Only the following files are changed:
No package, lockfile, page component, Next.js configuration, or unrelated application source was modified.
Tooling Note
Direct ESLint execution is currently blocked by an existing repository dependency/configuration issue involving ESLint,
@eslint/eslintrc, and AJV:The failure occurs while ESLint loads its configuration, before either changed file is analyzed.
This PR does not modify ESLint configuration, package dependencies, or the lockfile. Source validation is covered by the passing TypeScript check, targeted regression suite, and production build.
Files Changed
app/hooks/useCreatedMarkets.tsapp/__tests__/components/MyMarkets.test.tsxScope and Security Clarification
This PR fixes client-side wallet-scoped data correctness and UI lifecycle handling.
It does not claim or modify:
On-chain authorization remains the final enforcement boundary.
The corrected behavior ensures that
/my-marketsno longer presents pending, failed, or previous-wallet ownership data as a final verified result.Acceptance Criteria