feat(web): Moonwell Score — public viewer for the credit-report capability#17
Open
imthatcarlos wants to merge 10 commits into
Open
feat(web): Moonwell Score — public viewer for the credit-report capability#17imthatcarlos wants to merge 10 commits into
imthatcarlos wants to merge 10 commits into
Conversation
A self-contained, shareable surface for the new credit-report capability —
turns the terminal-only score into something people can drop in an X reply.
Adds:
- /score — empty-state hero with ENS / Basename / 0x input + curated example
links. Reads ?s=<input> and renders the editorial monospace score card,
components breakdown, health-factor stats, activity summary, and the
report's disclaimers (surfaced per SKILL.md).
- /score/compare — side-by-side cards for two wallets via ?a=&b=, with a
derived one-line verdict ("X scores higher", "X carries less risk", …)
designed for quote-tweets.
- Pre-filled X intent share button + copy-link, with three rotated tweet
templates so concurrent shares don't read identically.
- Insufficient-history fallback (calm "no record yet" state) so wallets that
have never used Moonwell still produce a meaningful page rather than a 404.
- Client-side ENS + Basenames resolution via viem (added as a web dep);
Universal Resolver hit at the time of submit so URLs keep human names.
Surface-only — the root agents.moonwell.fi landing page is intentionally
untouched, and the credit-report API at api.moonwell.fi is consumed unchanged.
Constraint: agents.moonwell.fi runs as Workers Static Assets (output:export),
so per-subject dynamic OG images can't be generated at request time. v1.1
will add a thin Cloudflare Worker companion to render Satori-driven PNGs
keyed on subject; for v1, the page emits a generic Moonwell Score card.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…l favicon Addressing review feedback on the initial /score surface: - Basenames resolution was broken because viem's Base chain config has no ensUniversalResolver. Drop the standalone Base client and resolve every name (including *.base.eth) through the mainnet Universal Resolver, which follows the L1 Resolver's CCIP-Read pointer to the L2 record. Swap the RPC to publicnode — cloudflare-eth.com currently reverts on resolveWithGateways and breaks the offchain hop. - Extract SiteNav and SiteFooter into shared components so the root landing and /score render the same header/footer. Adds a "Score" link to the root nav and an "Agents" link on /score; brand suffix flips between Agents / Score so users still know where they are. - Mount LunarTerrain on the /score and /score/compare surfaces so the background reads as the same site as the root landing. - Tighten the empty-state copy. "Score any wallet on Moonwell." replaces the wordier hero; description trimmed to a single line that names the three signals (score, tier, fingerprint) without listing chains in body prose. - Replace the stock Next favicon with a Moonwell-branded one. icon.svg in the App Router conventions slot drives modern browsers and tab tabs. apple-icon.png covers iOS home-screen pinning; generated via a one-shot sharp script (scripts/generate-apple-icon.mjs) so the SVG remains the source of truth. - Wire ?demo=1 on /score to load lib/sample-report.json (a snapshot of the production credit-report response for 0x1a1E…389F9) — lets us walk the populated card without burning API requests during local design passes. Easy to drop pre-merge if we don't want the fixture in the repo. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Swap the in-repo generated SVG favicon for the canonical assets served by moonwell.fi: - favicon.ico (32x32 ICO) pulled from https://moonwell.fi/favicon.ico - apple-icon.png resized from https://moonwell.fi/apple-touch-icon.png (800x800 source → 180x180 to match iOS home-screen convention) Drop the now-unneeded icon.svg and the sharp-based generator script — moonwell.fi is the source of truth, refresh by re-fetching if it ever changes. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Companion commit to the previous deletion — adds the actual official binaries from moonwell.fi that Next.js will emit as the site icons: - favicon.ico — 32x32 from https://moonwell.fi/favicon.ico - apple-icon.png — resized from https://moonwell.fi/apple-touch-icon.png to 180x180 for iOS home-screen pinning Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Deploying with
|
| Status | Name | Latest Commit | Updated (UTC) |
|---|---|---|---|
| ✅ Deployment successful! View logs |
moonwell-api | de789f3 | May 26 2026, 04:22 PM |
Deploying with
|
| Status | Name | Latest Commit | Updated (UTC) |
|---|---|---|---|
| ✅ Deployment successful! View logs |
moonwell-ai | de789f3 | May 26 2026, 04:22 PM |
The score page at agents.moonwell.fi/score needs a mainnet RPC for ENS /
Basenames resolution. Public RPCs (cloudflare-eth, publicnode) are flaky
under load and don't always carry the CCIP-Read gateway hop reliably.
This adds a thin pass-through inside the api Worker so the browser can hit
api.moonwell.fi/v1/rpc/mainnet (and /base) without ever seeing the upstream
auth — paid provider keys stay in the Worker secret store.
Surface:
POST /v1/rpc/mainnet
POST /v1/rpc/base
body { jsonrpc: "2.0", id, method, params }
Constraints (deliberate):
- Read methods only. eth_sendTransaction etc. rejected with -32601 so the
proxy can't be turned into a free relay.
- One request per call — no batch. Adds parsing surface for negligible
wire benefit.
- Upstream HTTP error bodies are never echoed. Providers leak stack
fragments and sometimes the RPC URL with embedded key — log
server-side, return a generic 502.
- JSON-RPC error responses ARE forwarded verbatim so viem can read
error.code for CCIP-Read OffchainLookup, execution reverts, etc.
New env binding: MAINNET_RPC_URL (set via `wrangler secret put`). The
existing BASE_RPC_URL is reused. Docs in wrangler.jsonc updated.
Tests: 9 new cases cover the chain allow-list, method allow-list,
GET rejection, missing-config 503, unreachable-upstream 503, verbatim
success forwarding, verbatim JSON-RPC error forwarding, and the critical
'do not echo upstream HTTP body' security property.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Swap the score page's mainnet viem client from a public RPC (ethereum-rpc.publicnode.com) to the new Moonwell-controlled proxy in the api Worker. Honours NEXT_PUBLIC_MOONWELL_API_URL (already used by the credit-report client) so local dev points at a local Worker when set, production resolves to api.moonwell.fi. Pre-requisite for shipping: set MAINNET_RPC_URL in the api Worker (`pnpm exec wrangler secret put MAINNET_RPC_URL`). Until that's set the proxy returns 503 and the score page surfaces a 'resolver error' on any ENS / Basename input. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
imthatcarlos
added a commit
that referenced
this pull request
May 28, 2026
Bring the Moonwell-branded site icons onto this branch — the same binaries as PR #17 (commit 46b0ee0, sourced from moonwell.fi). Replaces the default Next.js favicon and adds the apple-touch-icon; Next.js auto-emits both from src/app/. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
New Cloudflare Worker (packages/share-card) that renders the score as a social share card, plus wiring on the /score page. - GET /og/:subject -> 1200x630 PNG via Satori (workers-og): semicircular FICO-style gauge (grounded track, only the active tier colored), score + tier in the hollow, Moonwell logomark, tier moon-phase, strategy + health, resolved ENS/Basename subject, bordered frame. - GET /s/:subject -> HTML landing with absolute per-subject OG/Twitter meta + a bot-gated redirect to the live score page. - /score renders the card image (no component drift) and shares the /s/ link; share-copy gains landingUrlFor/cardImageUrlFor (?name= passthrough). - Edge-cached (SWR), no secrets — data via api.moonwell.fi/v1/credit-report. - Root scripts: dev:share / deploy:share. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Fill the tier color from 300 up to the score (muted remainder + marker at the score), instead of coloring only the tier's band at the far end. Drops the FICO band-edge approximation — it's now a pure progress fill to the real score, colored by the API tier. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Collaborator
Author
Code reviewFound 1 issue:
moonwell-ai/packages/web/src/app/loans/components/LoansShell.tsx Lines 8 to 16 in fd32d2d For reference, moonwell-ai/packages/web/src/app/score/components/ScoreShell.tsx Lines 12 to 17 in fd32d2d And root moonwell-ai/packages/web/src/app/page.tsx Lines 75 to 80 in fd32d2d 🤖 Generated with Claude Code - If this code review was useful, please react with 👍. Otherwise, react with 👎. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
A shareable
/scorepage for the Moonwell credit-report capability, plus a dynamic per-subject share card for social unfurls. Onchain credit for agents. Powered by Moonwell.What's in it
/scorepage —packages/web(static export)0xinput → editorial-mono score card: score (300–850), tier, strategy fingerprint, health factor, components, activity, disclaimers (verbatim perSKILL.md)./score/compare?a=&b=— side-by-side with a derived one-line verdict.Share card —
packages/share-card(new Cloudflare Worker)GET /og/:subject→ 1200×630 PNG via Satori (workers-og): a grounded semicircular FICO-style gauge (muted track, only your tier colored, marker at your score), the score + tier nested in the hollow, Moonwell logomark, tier moon-phase, strategy + health, resolved ENS/Basename, bordered frame.GET /s/:subject→ HTML landing with absolute per-subject OG/Twitter meta + a bot-gated redirect to the live page.api.moonwell.fi/v1/credit-report.API —
packages/api/v1/rpc/:chainproxy so the score page resolves ENS/Basenames without exposing an auth'd RPC URL in the browser.Deploy checklist (needs Cloudflare access)
pnpm deploy:api(ships the new/v1/rpcroute)packages/api:wrangler secret put MAINNET_RPC_URL→https://rpc.moonwell.fi/main/evm/1pnpm deploy:share(no secrets)agents.moonwell.fi/s/*andagents.moonwell.fi/og/*→ the share-card worker (must take precedence over the static-assets worker)https://agents.moonwell.fi/s/<address>into X / Discord / opengraph.xyzFollow-ups (non-blocking)
Test plan
pnpm dev:web+pnpm dev:share(setNEXT_PUBLIC_SHARE_BASE=http://localhost:8788)/score?demo=1— card image renders;/score?s=<addr>— live, "Post to X" posts the/s/linkpnpm --filter @moonwell-ai/share-card test+typecheckgreenpnpm build(web) clean🤖 Generated with Claude Code