Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file added app/public/museworld-mark.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
32 changes: 22 additions & 10 deletions app/src/app/t/[chain]/[token]/opengraph-image.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,19 @@ import sharp from "sharp";
import { readFile } from "node:fs/promises";
import path from "node:path";
import { GITLAWB_LOGO_BG, GITLAWB_LOGO_PATH } from "@/lib/launchpad/gitlawb";
import { MUSEWORLD_BLUE, MUSEWORLD_LOGO_PATH } from "@/lib/launchpad/museworld";

/** Gitlawb's logo from public/, inlined once per process (satori needs a data URL or absolute URL). */
let gitlawbLogo: Promise<string | null> | null = null;
function gitlawbLogoDataUrl(): Promise<string | null> {
return (gitlawbLogo ??= readFile(path.join(process.cwd(), "public", GITLAWB_LOGO_PATH))
.then((b) => `data:image/png;base64,${b.toString("base64")}`)
.catch(() => null));
/** Brand logos from public/, inlined once per process each (satori needs a data URL or absolute URL). */
const brandLogos = new Map<string, Promise<string | null>>();
function logoDataUrl(publicPath: string): Promise<string | null> {
let p = brandLogos.get(publicPath);
if (!p) {
p = readFile(path.join(process.cwd(), "public", publicPath))
.then((b) => `data:image/png;base64,${b.toString("base64")}`)
.catch(() => null);
brandLogos.set(publicPath, p);
}
return p;
}

export const alt = "token on openlaunch.lol";
Expand Down Expand Up @@ -69,7 +75,9 @@ export default async function TokenOg({ params }: { params: Promise<{ chain: str
const [usd, fonts] = await Promise.all([ethUsd(), loadOgFonts()]);
const l = isChainKey(chain) && isAddress(token) ? await memo(`og:${chain}:${token.toLowerCase()}`, 30_000, () => getLaunch(chain, token, usd)) : null;
const card = l ? shapeCard(l, nowMs()) : null;
const glLogo = card?.quote?.kind === "gitlawb" ? await gitlawbLogoDataUrl() : null;
const glLogo = card?.quote?.kind === "gitlawb" ? await logoDataUrl(GITLAWB_LOGO_PATH) : null;
const mwLogo = card?.quote?.kind === "museworld" ? await logoDataUrl(MUSEWORLD_LOGO_PATH) : null;
const official = card?.quote?.kind === "museworld";
const logo = l ? await memo(`og-logo:${chain}:${token.toLowerCase()}`, 60_000, () => ownLogo(l.image_url)) : null;
const h = hueOf(token);
return new ImageResponse(
Expand Down Expand Up @@ -117,8 +125,12 @@ export default async function TokenOg({ params }: { params: Promise<{ chain: str
</div>
<div style={{ display: "flex", flexWrap: "wrap", gap: 12, marginTop: 26 }}>
{card.quote ? (
<span style={{ display: "flex", alignItems: "center", gap: 10, height: 40, padding: "0 16px 0 7px", borderRadius: 999, border: `1px solid ${LINE}`, background: "#fff", color: BODY, fontSize: 19, fontWeight: 600, whiteSpace: "nowrap" }}>
{card.quote.kind === "gitlawb" ? (
<span style={{ display: "flex", alignItems: "center", gap: 10, height: 40, padding: "0 16px 0 7px", borderRadius: 999, border: `1px solid ${official ? MUSEWORLD_BLUE : LINE}`, background: official ? MUSEWORLD_BLUE : "#fff", color: official ? "#fff" : BODY, fontSize: 19, fontWeight: 600, whiteSpace: "nowrap" }}>
{card.quote.kind === "museworld" ? (
<span style={{ display: "flex", width: 30, height: 30, borderRadius: 8, overflow: "hidden" }}>
{mwLogo ? <img src={mwLogo} width={30} height={30} alt="" /> : null}
</span>
) : card.quote.kind === "gitlawb" ? (
<span style={{ display: "flex", alignItems: "center", justifyContent: "center", width: 30, height: 30, borderRadius: 8, background: GITLAWB_LOGO_BG, overflow: "hidden" }}>
{glLogo ? <img src={glLogo} width={30} height={30} alt="" /> : null}
</span>
Expand All @@ -127,7 +139,7 @@ export default async function TokenOg({ params }: { params: Promise<{ chain: str
{card.quote.ticker}
</span>
)}
priced in {card.quote.symbol}
{official ? `official · priced in ${card.quote.symbol}` : `priced in ${card.quote.symbol}`}
</span>
) : null}
{[card.fee, "liquidity locked forever", card.age].map((t, i) => (
Expand Down
5 changes: 4 additions & 1 deletion app/src/app/t/[chain]/[token]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ import { NATIVE, SWAP_SITES, TICK_SPACING, type Quote } from "@/lib/launchpad/co
import UnlistedPairBadge from "@/components/launchpad/UnlistedPairBadge";
import { GITLAWB_SITE } from "@/lib/launchpad/gitlawb";
import GitlawbBadge from "@/components/launchpad/GitlawbBadge";
import MuseworldBadge from "@/components/launchpad/MuseworldBadge";
import { MUSEWORLD_SITE } from "@/lib/launchpad/museworld";
import { fmtCompact, fmtPrice, fmtQuote, fmtUsd, pipsToPct } from "@/lib/launchpad/math";
import { marketCount as count } from "@/lib/launchpad/token-market";
import { marketUsd } from "@/lib/launchpad/market-format";
Expand Down Expand Up @@ -121,7 +123,7 @@ export default async function TokenPage({ params }: { params: Promise<{ chain: s
<TokenAvatar chain={l.chain} token={l.token} symbol={l.symbol} image={l.image_url} size={56} className="shrink-0 rounded-2xl" />
<div className="min-w-0">
<h1 className="break-words font-display text-2xl font-bold tracking-[-0.03em] text-ink sm:text-3xl">{l.name}</h1>
<div className="mt-1 flex flex-wrap items-center gap-x-2 gap-y-1 text-[11px] text-muted"><span className="font-mono text-body">${l.symbol}</span><span aria-hidden>·</span>{quote.key === "gitlawb" ? <GitlawbBadge label="Paired with GITLAWB" /> : <span>Paired with {quote.symbol}</span>}{unlisted ? <UnlistedPairBadge symbol={quote.symbol} /> : null}<span aria-hidden>·</span><span title={new Date(l.block_time).toUTCString()}>Launched {ago(l.block_time, now)} ago</span></div>
<div className="mt-1 flex flex-wrap items-center gap-x-2 gap-y-1 text-[11px] text-muted"><span className="font-mono text-body">${l.symbol}</span><span aria-hidden>·</span>{quote.key === "gitlawb" ? <GitlawbBadge label="Paired with GITLAWB" /> : quote.key === "museworld" ? <MuseworldBadge label="Paired with MUSEWORLD" /> : <span>Paired with {quote.symbol}</span>}{unlisted ? <UnlistedPairBadge symbol={quote.symbol} /> : null}<span aria-hidden>·</span><span title={new Date(l.block_time).toUTCString()}>Launched {ago(l.block_time, now)} ago</span></div>
</div>
</div>
<div className="flex max-w-full flex-wrap items-center gap-2">
Expand Down Expand Up @@ -173,6 +175,7 @@ export default async function TokenPage({ params }: { params: Promise<{ chain: s
<Row k="Launched" v={new Date(l.block_time).toUTCString().replace(" GMT", " UTC")} />
</dl>
{unlisted ? <p className="mt-4 text-xs leading-relaxed text-muted text-pretty">Paired with {quote.symbol}, a token openlaunch does not list. The launch contracts accept any ERC-20 as the pair; its name here is what its own contract reports, so check the pair token address above. Prices are in {quote.symbol} only, with no USD figure, and this launch is not ranked in Trending.</p> : null}
{quote.key === "museworld" ? <p className="mt-4 text-xs leading-relaxed text-muted text-pretty">Paired with MUSEWORLD, the official token of <a href={MUSEWORLD_SITE} target="_blank" rel="noreferrer" className="underline decoration-line underline-offset-2 hover:text-ink">Museworld</a>, where AI agents launch their tokens. {mode === "free" ? "This pool has no trading fee." : mode === "burn" ? "Every trading fee on this pool is burned as MUSEWORLD." : "Trading fees on this pool are paid out in MUSEWORLD."} USD figures use the MUSEWORLD/GITLAWB pool on openlaunch (checked against its 30-minute average) and GITLAWB&apos;s own price.</p> : null}
{stockQuote ? <p className="mt-4 text-xs leading-relaxed text-muted text-pretty">Paired with {stockQuote.name} ({stockQuote.symbol}), a third-party tokenized stock. These securities are not offered to US persons. The quote asset is identified from the issuer registry, not its token name.</p> : null}
{quote.key === "gitlawb" ? <p className="mt-4 text-xs leading-relaxed text-muted text-pretty">Paired with GITLAWB, <a href={GITLAWB_SITE} target="_blank" rel="noreferrer" className="underline decoration-line underline-offset-2 hover:text-ink">Gitlawb</a>&apos;s token{GITLAWB_ORIGIN[chain]}. {mode === "free" ? "This pool has no trading fee." : mode === "burn" ? "Every trading fee on this pool is burned as GITLAWB." : "Trading fees on this pool are paid out in GITLAWB."} USD figures use the Uniswap v4 WETH/GITLAWB pool price on Base.</p> : null}
</section>}
Expand Down
4 changes: 2 additions & 2 deletions app/src/components/launchpad/LaunchRow.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import { fmtQuote, fmtUsd, pipsToPct } from "@/lib/launchpad/math";
import { CHAIN_SHORT } from "@/lib/chainPublic";
import { ago } from "@/lib/launchpad/time";
import ChangeChip from "./ChangeChip";
import GitlawbBadge, { isGitlawbQuote } from "./GitlawbBadge";
import { QuoteBrandBadge } from "./MuseworldBadge";
import UnlistedPairBadge from "./UnlistedPairBadge";
import { marketUsd } from "@/lib/launchpad/market-format";
import { capDisplay } from "@/lib/launchpad/market-cap";
Expand Down Expand Up @@ -55,7 +55,7 @@ export default function LaunchRow({ l, rank, window = "all", hl = null, now, pop
<div className="min-w-0">
<div className="flex min-w-0 items-center gap-2">
<span className="truncate text-sm font-semibold text-ink">{l.name}</span>
{isGitlawbQuote(l.quote_key) ? <GitlawbBadge /> : null}
<QuoteBrandBadge quoteKey={l.quote_key} />
{l.quote_key === "other" ? <UnlistedPairBadge symbol={l.quote_symbol} className="shrink-0" /> : null}
{hl?.kind === "new" ? <span className="shrink-0 text-[10px] font-medium text-brand">New</span> : null}
</div>
Expand Down
4 changes: 2 additions & 2 deletions app/src/components/launchpad/LaunchTape.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { useEffect, useRef, useState } from "react";
import { Activity, ArrowDownLeft, ArrowUpRight, Plus } from "lucide-react";
import { useLive } from "./LiveProvider";
import TokenAvatar from "./TokenAvatar";
import GitlawbBadge, { isGitlawbQuote } from "./GitlawbBadge";
import { QuoteBrandBadge } from "./MuseworldBadge";
import type { FeedItem } from "@/lib/launchpad/queries";
import { fmtQuote } from "@/lib/launchpad/math";
import { CHAIN_SHORT, shortAddr } from "@/lib/chainPublic";
Expand Down Expand Up @@ -61,7 +61,7 @@ export default function LaunchTape({ initial }: { initial: FeedItem[] }) {
<Icon size={12} aria-hidden="true" className={tone} />
<span className={tone}>{item.kind === "launch" ? "Launched" : buy ? "Buy" : "Sell"}</span>
{item.kind === "swap" ? <span className="min-w-0 truncate font-mono text-body tnum">{fmtQuote(item.quote_wei, item.quote_decimals, item.quote_symbol)}</span> : null}
{isGitlawbQuote(item.quote_key) ? <GitlawbBadge /> : null}
<QuoteBrandBadge quoteKey={item.quote_key} />
{item.kind === "swap" && item.is_dev ? <span className="text-warm-ink">· creator</span> : null}
<span className="ml-auto shrink-0 text-[10px] text-muted">{CHAIN_SHORT[item.chain]}</span>
</div>
Expand Down
6 changes: 3 additions & 3 deletions app/src/components/launchpad/MeDashboard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import { getPublicClient, getWalletClient } from "wagmi/actions";
import type { Address } from "viem";
import TokenAvatar from "./TokenAvatar";
import ChainBadge from "./ChainBadge";
import GitlawbBadge, { isGitlawbQuote } from "./GitlawbBadge";
import { QuoteBrandBadge } from "./MuseworldBadge";
import UnlistedPairBadge from "./UnlistedPairBadge";
import FeeChip, { feeModeOf } from "./FeeChip";
import EditTokenSheet from "./EditTokenSheet";
Expand Down Expand Up @@ -247,7 +247,7 @@ function WalletDashboard({ address, isConnected }: { address: Address | undefine
<span className="font-semibold text-[15px] text-ink truncate">{l.name}</span>
<span className="font-mono text-xs text-muted">{l.symbol}</span>
<ChainBadge chain={l.chain} />
{isGitlawbQuote(l.quote_key) ? <GitlawbBadge /> : null}
<QuoteBrandBadge quoteKey={l.quote_key} />
{l.quote_key === "other" ? <UnlistedPairBadge symbol={l.quote_symbol} /> : null}
</div>
<div className={styles.tokenMeta}>
Expand Down Expand Up @@ -300,7 +300,7 @@ function WalletDashboard({ address, isConnected }: { address: Address | undefine
<span className="font-semibold text-ink truncate">{t.name}</span>
<span className="font-mono text-xs text-muted">{t.symbol}</span>
<ChainBadge chain={t.chain} />
{isGitlawbQuote(t.quote_key) ? <GitlawbBadge /> : null}
<QuoteBrandBadge quoteKey={t.quote_key} />
{t.quote_key === "other" ? <UnlistedPairBadge symbol={t.quote_symbol} /> : null}
</div>
<div className={styles.tokenMeta}>
Expand Down
40 changes: 40 additions & 0 deletions app/src/components/launchpad/MuseworldBadge.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import { BadgeCheck } from "lucide-react";
import { MUSEWORLD_BLUE, MUSEWORLD_LOGO_PATH, MUSEWORLD_SYMBOL } from "@/lib/launchpad/museworld";
import GitlawbBadge, { isGitlawbQuote } from "./GitlawbBadge";

/** Museworld's logo tile (white "m" and sparkle on Museworld blue, transparent corners baked in), served from our origin. */
export function MuseworldMark({ size = 16, className = "" }: { size?: number; className?: string }) {
// eslint-disable-next-line @next/next/no-img-element
return <img src={MUSEWORLD_LOGO_PATH} alt="" width={size} height={size} className={`shrink-0 ${className}`} />;
}

/**
* Official badge for tokens paired with MUSEWORLD, Museworld's token (lib/launchpad/museworld.ts). Same geometry as the
* GITLAWB badge so the two sit side by side in any row: "sm" is 20px tall and fits a text-sm line. Museworld blue with
* the logo tile flush left and a check seal on the right: the mark that says the pair is official, not just named so.
* The badge follows the server-resolved quote key, which comes from the quote ADDRESS, never from an on-chain name.
*/
export default function MuseworldBadge({ size = "sm", className = "", label = MUSEWORLD_SYMBOL }: { size?: "sm" | "md"; className?: string; label?: string }) {
return (
<span
className={`inline-flex shrink-0 items-center rounded-md border border-black/10 font-semibold leading-none whitespace-nowrap text-white dark:border-white/25 ${size === "md" ? "h-6 gap-1.5 pl-[3px] pr-1.5 text-[11px]" : "h-5 gap-1 pl-[2px] pr-1 text-[10px] tracking-wide"} ${className}`}
style={{ background: MUSEWORLD_BLUE }}
title="Official pair: MUSEWORLD, Museworld's token. Launches paired with it are made inside Museworld by its AI agents. USD from the MUSEWORLD/GITLAWB pool."
>
<MuseworldMark size={size === "md" ? 18 : 16} />
{label}
<BadgeCheck size={size === "md" ? 13 : 12} strokeWidth={2.25} aria-label="official" className="shrink-0" />
</span>
);
}

export function isMuseworldQuote(quoteKey: string | null | undefined): boolean {
return quoteKey === "museworld";
}

/** The brand badge a quote earns, if any: GITLAWB's or Museworld's official one. One place for every list to call. */
export function QuoteBrandBadge({ quoteKey, size = "sm", className = "" }: { quoteKey: string | null | undefined; size?: "sm" | "md"; className?: string }) {
if (isGitlawbQuote(quoteKey)) return <GitlawbBadge size={size} className={className} />;
if (isMuseworldQuote(quoteKey)) return <MuseworldBadge size={size} className={className} />;
return null;
}
5 changes: 3 additions & 2 deletions app/src/components/launchpad/TrendingStrip.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@ import Link from "next/link";
import { useEffect, useRef, useState } from "react";
import { ArrowUpRight, ChartNoAxesCombined } from "lucide-react";
import TokenAvatar from "./TokenAvatar";
import GitlawbBadge, { isGitlawbQuote } from "./GitlawbBadge";
import { isGitlawbQuote } from "./GitlawbBadge";
import { QuoteBrandBadge, isMuseworldQuote } from "./MuseworldBadge";
import ChangeChip from "./ChangeChip";
import { useLive } from "./LiveProvider";
import type { LaunchRow } from "@/lib/launchpad/queries";
Expand Down Expand Up @@ -83,7 +84,7 @@ export default function TrendingStrip({ initial }: { initial: Snap }) {
<Link href={`/t/${row.chain}/${row.token}`} className={`group block h-full rounded-xl border bg-card p-3.5 transition-colors hover:border-muted motion-reduce:transition-none ${index === 0 ? "border-line-strong" : "border-line"}`}>
<div className="mb-3 flex items-center justify-between text-[11px] text-muted">
<span className="font-mono tnum">0{index + 1}<span className="sr-only"> ranked</span></span>
{isGitlawbQuote(row.quote_key) ? <span className="flex items-center gap-1">{CHAIN_SHORT[row.chain]} · <GitlawbBadge /></span> : <span>{CHAIN_SHORT[row.chain]} · {row.quote_symbol}</span>}
{isGitlawbQuote(row.quote_key) || isMuseworldQuote(row.quote_key) ? <span className="flex items-center gap-1">{CHAIN_SHORT[row.chain]} · <QuoteBrandBadge quoteKey={row.quote_key} /></span> : <span>{CHAIN_SHORT[row.chain]} · {row.quote_symbol}</span>}
<ArrowUpRight size={13} aria-hidden="true" className="text-muted group-hover:text-ink" />
</div>
<div className="flex min-w-0 items-center gap-2">
Expand Down
Loading
Loading