From 7ac3676d1530792506496a968c052df9d4aa46e4 Mon Sep 17 00:00:00 2001 From: mul53 Date: Mon, 14 Sep 2026 12:20:22 +0200 Subject: [PATCH] Show the Invest catalog to everyone, not just existing holders MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Gating the whole Invest section on holdings left anyone with a zero xStock balance looking at an Earn page that ends after the vault tiles — no heading, no catalog, no way in. The section that sells you your first stock was only visible once you already owned one. The escape hatch the gate assumed — "discovery depends entirely on the Stocks tab" — does not exist on qa. The desktop sidebar's NAV_ITEMS is a hardcoded Wallet/Earn/Rewards/Activity, and the redesigned bottom bar reads WHITELIST_TAB_NAMES, which is index/earn/rewards. Neither lists Stocks, so /stocks is reachable only by typing it. So the gate moves off the section and onto the positions panel, which is the part that actually has nothing to say when you hold nothing. The panel keeps its isHoldingsLoading hold-back so it doesn't appear and then vanish; the catalog is no longer behind that, because it has nothing to wait for and a 164-token mainnet multicall is a slow thing to block a page on. One consequence worth naming: useXStockHoldings defaults data to [] on error, so a failed multicall is indistinguishable from holding nothing. Before this it silently hid the whole section from a genuine holder. Now it costs them only the positions panel. Co-Authored-By: Claude Opus 5 --- components/Earn/EarnInvestSection.tsx | 56 ++++++++++++++++----------- 1 file changed, 33 insertions(+), 23 deletions(-) diff --git a/components/Earn/EarnInvestSection.tsx b/components/Earn/EarnInvestSection.tsx index 61c52463..67fa71a0 100644 --- a/components/Earn/EarnInvestSection.tsx +++ b/components/Earn/EarnInvestSection.tsx @@ -153,36 +153,46 @@ export const EarnInvestSection = () => { collapse(); }; - // The whole section is for people who already hold something, so it is absent - // rather than empty for everyone else. Held back while the holdings read is in - // flight too: appearing and then vanishing reads worse than arriving late. - if (isHoldingsLoading || holdings.length === 0) return null; + // The positions panel is for people who already hold something, so it is absent + // rather than empty for everyone else, and held back while the holdings read is + // in flight: appearing and then vanishing reads worse than arriving a beat late. + // + // The catalog below is deliberately not gated on it. It is the only route from + // Earn into buying a first stock, so it has to render for holders and + // non-holders alike, and it must not wait on a chain read to do so. + const hasPositions = !isHoldingsLoading && holdings.length > 0; return ( Invest - Your positions - - {holdings.map(holding => { - const token = tokensBySymbol.get(holding.ticker); - const price = prices[holding.ticker]; + {hasPositions && ( + <> + Your positions - return ( - token && openStock(token, 'sell')} - /> - ); - })} - + + {holdings.map(holding => { + const token = tokensBySymbol.get(holding.ticker); + const price = prices[holding.ticker]; + + return ( + token && openStock(token, 'sell')} + /> + ); + })} + + + )} - + {/* Falls back to the section's subtitle slot when nothing sits above it. */} + Buy, sell and use as collateral