NFT gallery (Soroban CAP-46) - #515
Conversation
|
Someone is attempting to deploy a commit to the miracle656's projects Team on Vercel. A member of the Team first needs to authorize it. |
|
@attyolu Great news! 🎉 Based on an automated assessment of this PR, the linked Wave issue(s) no longer count against your application limits. You can now already apply to more issues while waiting for a review of this PR. Keep up the great work! 🚀 |
|
@Miracle656 please merge |
Miracle656
left a comment
There was a problem hiding this comment.
Thanks for this — the structure is good. Splitting the data layer into lib/nfts.ts with lib/__tests__/nfts.test.ts covering it, rather than putting fetch logic in the component, is the right shape, and truncateAddress / formatTokenId as separately-tested pure helpers is exactly how I'd want it done.
There's one blocker, and I want to be upfront that the issue's acceptance criteria are partly to blame for it. #349 says:
- Renders at least one fixture NFT
- Empty state handled
Those two contradict each other, and I wrote them. You resolved the contradiction in favour of the first, which is a reasonable reading. Unfortunately it's the reading we can't ship, so let me be clear about what I actually meant.
🚨 Blocker: fixture NFTs reach real users, attributed to their own address
In lib/nfts.ts:
if (includeFixtures || nfts.length === 0) {
const existingIds = new Set(nfts.map(n => n.id))
for (const fixture of FIXTURE_NFTS) {
if (!existingIds.has(fixture.id)) {
nfts.push({
...fixture,
owner: walletAddress || fixture.owner, // ← rewritten to the real user
})
}
}
}nfts.length === 0 is the normal case for most users. So a user with no NFTs opens /nfts and sees three items — "CAP46-GENESIS" and friends — with Unsplash stock photography, fabricated contract IDs like CBY3K4GENESISCAP46NFTX7V2QZP3M9L0K8J1H5G2F4D6S8A (not a valid Stellar contract ID), and owner rewritten to their own wallet address.
And in app/nfts/page.tsx:
} catch {
setNfts(FIXTURE_NFTS)
}Any failure — and the default indexer is https://wraith-0jo1.onrender.com, a free Render instance that sleeps and cold-starts, so failures will be routine — silently replaces the error with the same fake holdings.
This is a wallet. Telling someone they own an asset they don't own is a category of bug we can't ship regardless of intent, and it also makes the "empty state handled" criterion unreachable, since the empty state can never render.
What I meant by that criterion: fixtures exist so the gallery can be exercised without a funded testnet account — i.e. in tests and local development. Not in production. Concretely:
- Keep
FIXTURE_NFTSexported and keep using it innfts.test.ts— that's its correct home. - Drop the
|| nfts.length === 0clause. HonourincludeFixturesonly when the caller explicitly passes it, and have the page pass it only behind a dev flag (e.g.process.env.NEXT_PUBLIC_NFT_FIXTURES === '1'). - Zero NFTs → render the real empty state.
- Fetch failure → render an error state with a retry, not fixtures. Distinguishing "you have none" from "we couldn't check" matters here.
I'll fix the wording on #349 so the next person doesn't hit the same trap.
Also needs addressing
tsconfig.json drops three path mappings
- "react": ["./node_modules/@types/react"],
- "react/jsx-runtime": ["./node_modules/@types/react/jsx-runtime"],
- "@stellar/stellar-sdk": ["./node_modules/@stellar/stellar-sdk"]These pin react and @stellar/stellar-sdk to the wallet's own node_modules — in a workspace with the SDK linked in, that's usually deliberate, to stop TypeScript resolving two copies of React's types and producing "two different JSX namespaces" errors.
Worth knowing: only GitGuardian ran on this PR — the typecheck and build jobs never fired, so nothing has verified this is safe. Unless removing them was load-bearing for the NFT work, please revert it. If it was needed, say why and I'll look properly.
Hardcoded indexer URL
'https://wraith-0jo1.onrender.com'Fine as a fallback, but it should be NEXT_PUBLIC_WRAITH_URL first with this as the default, and the default belongs in .env.example so it's discoverable.
Two housekeeping notes
Git attribution. Your commits are authored as flourishbar <you@example.com>. That's the git default-config placeholder, so GitHub can't link them to your account — it reports the author as invalid-email-address, and the work won't register as your contribution, which matters for Wave tracking. Worth fixing before your next PR:
git config --global user.email "your-real-github-email@example.com"
git config --global user.name "Your Name"If you'd like, I can re-run attribution on these commits once it's set.
#517 is stacked on this one. It contains this PR's commit 7673d1d plus the QuickActions work. That's fine, but please note it in the #517 description so it doesn't read as a duplicate — I nearly filed it as one.
The gallery itself is solid work. Swap the fixture behaviour to dev-only and revert the tsconfig change and I'll take another look.
|
Thanks for the effort here — the gallery UI itself is nicely built. I have to close it though, because it does the specific thing #349 called out in bold as prohibited: it shows users NFTs they do not own, under their own address. Fabricated holdings
const items = await fetchWalletNFTs(walletAddress || 'GDEMO...WALLET', {
includeFixtures: true,
})and // Always include fixture NFTs if requested or if no live on-chain NFTs were found,
if (includeFixtures || nfts.length === 0) {
...
nfts.push({
...fixture,
owner: walletAddress || fixture.owner,
})
}plus the network-error path, whose So in production, a user who holds no NFTs — or whose indexer request fails — is shown three invented ones ("Soroban Genesis Pass #1", "Veil Cyber Pass #42", "Stellar Horizon Voyager #777"), each with
There's no The in-code comment says this "satisfies acceptance criteria: Renders at least one fixture NFT" — that phrasing isn't in #349, and the criterion it does have is the reverse. The rest of the criteria
Smaller notes
What a passing version looks likeKeep the gallery UI, and change the data path:
The three states — real holdings, genuinely empty, failed — have to stay distinguishable to a user. That's the whole point of the issue. Happy to review a version that keeps them separate. |
Reduce this branch to the quick-actions row Miracle656#460 asks for: - Drop the CAP-46 NFT gallery (frontend/wallet/app/nfts, lib/nfts.ts and its test, the dashboard/page.tsx and tsconfig edits). That work is Miracle656#349, reviewed separately in Miracle656#515 and closed there — it injected fixture NFTs whenever a wallet held none or the fetch failed, with owner rewritten to the connected address, which Miracle656#349 explicitly prohibits. - Drop the duplicate root app/send.tsx and app/receive.tsx; /send and /receive are owned by the (tabs) group. Keep main's buy.tsx, swap.tsx, index.tsx, tsconfig.json and @types/jest. - Wire QuickActions into (tabs)/dashboard.tsx, which is what makes the four actions reachable — the component was added but never rendered anywhere. tsc clean; jest 9 suites / 160 tests.
##closes #349