Skip to content

Upgrade 2026-07: P0 fixes, BNB Chain config, SEO/RSC refactor - #1

Open
AmazingAng wants to merge 4 commits into
mainfrom
upgrade/2026-07
Open

AmazingAng wants to merge 4 commits into
mainfrom
upgrade/2026-07

Conversation

@AmazingAng

Copy link
Copy Markdown
Member

Review branch for the 2026-07 upgrade. Three groups of changes: correctness fixes, BNB Chain configuration, and the SEO/RSC refactor. No visual changes — the rendered output is intentionally identical.

1. Fixes

Issue Fix
SBT mint marked itself successful as soon as a transaction hash came back, so a reverted transaction still showed "minted" useWaitForTransactionReceipt; success only on a confirmed receipt, reverts surface the existing error state
Closing the GitHub OAuth popup left the login button pending forever and leaked a message listener polls popup.closed, rejects and cleans up
`fallbackSrc
First/last chapter rendered <Link href="/course/x/undefined"> renders a disabled button instead
No error.tsx anywhere, so one API 500 produced Next's default error page added root, not-found and course-segment boundaries
Stored XSS: contributor markdown passed through rehype-raw with no sanitization, while the JWT sat in a JS-readable cookie rehype-sanitize allowlist (below) plus secure and sameSite=lax on the cookie
.env was committed to the repository removed from the index and added to .gitignore (contents were NEXT_PUBLIC_* only, no secrets)
images.remotePatterns: "**" turned the image optimizer into an open proxy narrowed to an allowlist

2. BNB Chain readiness

Chain id, both contract addresses and the subgraph endpoint are now environment-driven (NEXT_PUBLIC_SBT_CHAIN_ID, NEXT_PUBLIC_SBT_ADDRESS, NEXT_PUBLIC_SBT_MINTER_ADDRESS, NEXT_PUBLIC_SBT_GRAPH_URL, NEXT_PUBLIC_SBT_OPENSEA_URL) and documented in .env.sample. A missing chain id now throws instead of silently falling back to Sepolia — the previous behaviour paired a Sepolia chain with a Base mainnet minter address. bsc and bscTestnet are added to the wagmi network list.

Reown metadata was still the template default (appkit-example / appkitexampleapp.com), which is the name users saw in their wallet signing prompt; it now reads WTF Academy with the real domain.

3. SEO/RSC refactor

The chapter page was "use client" with two serial useSuspenseQuery calls, so course content — the site's entire organic-search surface — was invisible to crawlers. There was no sitemap, no robots, no hreflang, and the chapter layout hardcoded the title "WTF Solidity课程" for every course.

  • Chapter and course-list pages are now server components: parallel fetch, cache()-deduped (metadata and body share one API call), force-dynamic for the same data freshness as before.
  • Markdown and shiki render on the server. Highlighting uses shiki's dual-theme output with the same theme pair, flipped by CSS, so per-token colours match the previous client rendering exactly. Interactive pieces stay as client islands: copy button, image zoom, quiz footer, progress tracking.
  • Per-chapter generateMetadata (title, description, OG, canonical, hreflang), app/sitemap.ts (both locales, falls back to static routes if the API is down), app/robots.ts, and a site-wide title template.
  • Fixed middleware.ts locale-redirecting sitemap.xml and robots.txt into 404s.
  • Client shiki (still used by quiz pages) now uses a singleton highlighter with lazy grammar loading, fixing a concurrent-init race and a bug that rebuilt the highlighter with every language ever seen.

Sanitize schema: default rehype-sanitize rules (drops script, on* handlers, javascript: URLs) extended to keep className/style, images, video, details/summary, the full KaTeX MathML tag set, and iframes restricted to YouTube and Bilibili embed URLs. Verified against hostile input — script tags, onerror and non-allowlisted iframes are stripped while legitimate embeds and math survive.

Verification

tsc --noEmit clean; pnpm build succeeds with no backend running. Chapter and course-list SSR output was checked against a mock API: full highlighted content, correct per-chapter metadata, and sitemap output for both locales.

Note for reviewers

The SBT contract addresses are placeholders until the BNB Chain deployment lands (see WTFAcademy/WTF-SBT#8). The claim page will fail until they are filled in.

🤖 Generated with Claude Code

Fixes:
- SBT mint waits for the transaction receipt (a revert now surfaces an
  error instead of showing success)
- GitHub OAuth popup: closed-popup rejection, opener guard, no code logs
- Image fallback precedence bug; invalid blurDataURL removed
- Prev/next chapter links no longer emit /undefined
- Error boundaries: app/[lang]/error.tsx, not-found.tsx, course/error.tsx
- Token cookie: secure + sameSite=lax; .env removed from the git index

BNB Chain readiness:
- Chain, contract and subgraph endpoints are env-driven
  (NEXT_PUBLIC_SBT_CHAIN_ID / ADDRESS / MINTER_ADDRESS / GRAPH_URL);
  a missing chain id now fails loudly instead of falling back to sepolia
- bsc and bscTestnet added to the wagmi network list
- Reown metadata set to WTF Academy (was the appkit-example template)
- images.remotePatterns narrowed to an allowlist; dead deps removed

SEO/RSC refactor:
- Chapter and course pages are server components (parallel fetch, React
  cache() dedup, force-dynamic)
- Server-side markdown with shiki dual-theme highlighting, visually
  identical to the previous client rendering
- rehype-sanitize allowlist closes the stored-XSS vector in contributor
  markdown while keeping YouTube/Bilibili embeds and KaTeX
- Per-chapter generateMetadata, sitemap.xml, robots.txt, hreflang and a
  site-wide title template; middleware no longer localizes sitemap/robots
- Client shiki: singleton highlighter, lazy grammars, stable query keys

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@vercel

vercel Bot commented Jul 26, 2026 •

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
frontend-v2 Ready Ready Preview, Comment Jul 26, 2026 6:43pm

Request Review

Founder decision: minting is free, with 0.01 BNB suggested as an opt-in
donation. The signed mint_price is a minimum enforced by the contract, so
the console sets it to 0 and the UI suggests the amount.

- Fix: the transaction sent the typed donation amount even when the
  donation checkbox was unchecked, so a user opting out still paid.
  Validation consulted the checkbox but the value did not; both now read
  one source, and unchecking keeps the typed amount instead of wiping it
- Fix: the claim page pre-filled a hardcoded 6900000000000000 wei
  (0.0069 ETH) instead of the course's configured price, which would have
  overridden the new zero price. It now reads sbt_token.price and falls
  back to the 0.01 suggestion when the price is 0
- Fix: the success screen thanked users for "donating 0 BNB" after a free
  mint; that line now only renders when a donation was actually made
- Suggested donation default 0.1 to 0.01
- Currency symbol now comes from SBT_CHAIN.nativeCurrency.symbol
  everywhere; the hardcoded "ETH" in both dictionaries becomes {symbol}
  interpolation, so copy follows the configured chain
- Share-on-X copy uses the chain's handle (BNB Chain instead of @base)
- Donation input rejects negative and NaN values
- Document that the address previously hardcoded as the SBT contract is
  actually the minter (the real Base ERC1155 is 0xB05D4249...5E8c5c)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@AmazingAng

Copy link
Copy Markdown
Member Author

Update: BNB pricing — free mint with a suggested 0.01 BNB donation (acf493e)

Founder decision: minting is free, with 0.01 BNB suggested as an opt-in donation. Since the signed mint_price is only a minimum enforced by the contract, the console sets it to 0 and the UI carries the suggestion.

Three real bugs surfaced while implementing this:

  1. Opting out of the donation still paid. The transaction sent parseEther(donationAmount) regardless of the checkbox — validation consulted the checkbox but the value did not. Both now read one source, and unchecking keeps the typed amount rather than wiping it.
  2. The claim page pre-filled a hardcoded 6900000000000000 wei (0.0069 ETH) instead of the course's configured price, which would have silently overridden the new zero price. It now reads sbt_token.price and falls back to the 0.01 suggestion when the price is 0.
  3. The success screen thanked users for "donating 0 BNB" after a free mint; that line now only renders when a donation was actually made.

Also: currency symbol comes from SBT_CHAIN.nativeCurrency.symbol everywhere (the hardcoded "ETH" in both dictionaries became {symbol} interpolation, so copy follows the configured chain), share-on-X copy uses the chain's handle instead of @base, and the donation input rejects negative/NaN values.

Behaviour matrix (checkbox × typed amount × signed price) was walked through for all 8 combinations: unchecked always sends 0, checked sends the typed amount, and a course with a non-zero price still blocks an under-payment.

Before launch

NEXT_PUBLIC_SBT_OPENSEA_URL still defaults to the Base collection — set it to the BSC collection or the "view your NFT" button points at the old chain. Contract addresses also need filling in once WTFAcademy/WTF-SBT#8 is deployed.

AmazingAng and others added 2 commits July 27, 2026 02:41
The repo had no CI at all. Runs tsc --noEmit, pnpm lint and pnpm build
on PRs and pushes to main/upgrade/**.

The build step sets NEXT_PUBLIC_SBT_* placeholders because the SBT
constants now fail fast on a missing chain id rather than silently
falling back to Sepolia; without them the build cannot compile. No
backend is contacted (all API-dependent routes are request-dynamic).

Also pins pnpm via packageManager so CI and local installs agree
instead of inferring the version from the lockfile.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
pnpm/action-setup errors when a version is given both in the workflow and
in package.json packageManager. Drop the workflow copy so the manifest is
the single source. Node 20 is deprecated on GitHub runners.

This branch was successfully deployed

1 active deployment
Preview — 8649b082 Deployed Jul 26, 2026 by vercel[bot]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant