diff --git a/app/src/app/api/boost/mod/route.ts b/app/src/app/api/boost/mod/route.ts
index f5c41a26..59a2c192 100644
--- a/app/src/app/api/boost/mod/route.ts
+++ b/app/src/app/api/boost/mod/route.ts
@@ -1,16 +1,26 @@
import { NextResponse } from "next/server";
import { rateLimited } from "@/lib/launchpad/editServer";
import { clientIp, readJson } from "@/lib/profiles/http";
-import { moderateBoost } from "@/lib/launchpad/boostServer";
+import { getBoostLane, moderateBoost } from "@/lib/launchpad/boostServer";
+import { ethUsd } from "@/lib/launchpad/ethPrice";
export const dynamic = "force-dynamic";
-/** POST {action: hide|unhide, token, wallet, nonce, ts, signature}: an admin wallet keeps a Base token out of the Boosted lane, or lets it back. */
+/**
+ * POST {action: hide|unhide, token, wallet, nonce, ts, signature}: an admin wallet keeps a Base token out of the Boosted
+ * lane, or lets it back. A signed, single-use request, so its answer may carry the lane read fresh (both few-second
+ * caches skipped): the panel shows the result at once, whichever machine its next read reaches. `lane` is null when
+ * that read fails; the write itself has succeeded.
+ */
export async function POST(req: Request) {
if (rateLimited(`boost-mod:ip:${clientIp(req)}`, 30)) return NextResponse.json({ error: "slow down" }, { status: 429 });
const b = await readJson(req);
if (!b) return NextResponse.json({ error: "bad json" }, { status: 400 });
const r = await moderateBoost({ action: b.action, token: b.token, wallet: b.wallet, nonce: b.nonce, ts: b.ts, signature: b.signature });
if (!r.ok) return NextResponse.json({ error: r.error }, { status: r.status });
- return NextResponse.json(r);
+ const lane = await getBoostLane(await ethUsd(), { fresh: true }).catch((err) => {
+ console.warn("[boost] fresh lane after moderation failed:", err instanceof Error ? err.message : err);
+ return null;
+ });
+ return NextResponse.json({ ...r, lane });
}
diff --git a/app/src/app/api/boost/route.ts b/app/src/app/api/boost/route.ts
index d2b035ef..450f4857 100644
--- a/app/src/app/api/boost/route.ts
+++ b/app/src/app/api/boost/route.ts
@@ -27,7 +27,8 @@ export async function GET(req: Request) {
if (!isAddress(token)) return NextResponse.json({ error: "bad token" }, { status: 400 });
return NextResponse.json(await getTokenBoost(token), { headers: NO_STORE });
}
- // the same few-second cache pages and polls read: a caller polling this cannot add a database pass per request
+ // the same few-second cache pages and polls read: a caller polling this cannot add a database pass per request.
+ // The only fresh read is the signed moderation answer (api/boost/mod), right after a hide or unhide.
const lane = await boostLaneOrNull(await ethUsd());
if (!lane) return NextResponse.json({ error: "could not load boosts" }, { status: 502 });
return NextResponse.json(lane, { headers: NO_STORE });
diff --git a/app/src/app/llms.txt/route.ts b/app/src/app/llms.txt/route.ts
index 8d8b4be7..2269f958 100644
--- a/app/src/app/llms.txt/route.ts
+++ b/app/src/app/llms.txt/route.ts
@@ -15,7 +15,7 @@ To boost a launch: TWIG (${BOOST_TOKEN}).transfer(0x…dEaD, amount) with amount
(whole = a count of whole TWIG, at least ${BOOST_FLOOR_TWIG}; token_id = the launch's position id, the list API's token_id field,
greater than 0 and less than 10^11). The last 12 digits name the launch; a transfer outside these bounds is not a boost.
Then POST ${SITE_URL}/api/launch/sync?chain=base&tx=0x… to show it at once. Burns are final.
-- GET ${SITE_URL}/api/boost?chain=base (the lane: twig, boosts, last_at, expires_at, clicks, top_booster, launch; entry)
+- GET ${SITE_URL}/api/boost?chain=base (the lane: twig, boosts, last_at, expires_at, clicks, top_booster, launch; names)
- GET ${SITE_URL}/api/boost?chain=base&token=0x… (one token: twig in the window, place, hidden)
`;
}
diff --git a/app/src/app/t/[chain]/[token]/page.tsx b/app/src/app/t/[chain]/[token]/page.tsx
index d40eebba..0e56d295 100644
--- a/app/src/app/t/[chain]/[token]/page.tsx
+++ b/app/src/app/t/[chain]/[token]/page.tsx
@@ -19,6 +19,7 @@ import TokenTrades from "@/components/launchpad/TokenTrades";
import LaunchReceipt from "@/components/launchpad/LaunchReceipt";
import TokenAbout from "@/components/launchpad/TokenAbout";
import NamesProvider from "@/components/profile/NamesProvider";
+import { intentUrl } from "@/lib/profiles/xpost";
import { namesFor, type NameEntry } from "@/lib/profiles/server";
import { getHolderPanel } from "@/lib/launchpad/holdersServer";
import { memo } from "@/lib/launchpad/memo";
@@ -220,7 +221,7 @@ export default async function TokenPage({ params, searchParams }: { params: Prom
{/* the token's links as one bar, the Watch button's height: share, copy the address, the explorer */}
- Share
+ Share
{/* phones keep one row of actions; the explorer link is also in About & contracts */}
{explorerName(chain)}
diff --git a/app/src/components/launchpad/BoostAdmin.tsx b/app/src/components/launchpad/BoostAdmin.tsx
index d8472843..6ba66c2c 100644
--- a/app/src/components/launchpad/BoostAdmin.tsx
+++ b/app/src/components/launchpad/BoostAdmin.tsx
@@ -48,12 +48,17 @@ export default function BoostAdmin() {
const signature = await wallet.signMessage({ message: buildBoostModMessage({ action, token, wallet: address, nonce: n, ts }) });
const res = await fetch("/api/boost/mod", { method: "POST", headers: { "content-type": "application/json" }, body: JSON.stringify({ action, token, wallet: address, nonce: n, ts, signature }) });
// an error page that is not JSON (a 502 from the proxy) still reports the HTTP failure, not a parse error
- const d = (await res.json().catch(() => ({}))) as { error?: string };
+ const d = (await res.json().catch(() => ({}))) as { error?: string; lane?: BoostLane | null };
if (!res.ok) throw new Error(d.error ?? `request failed (HTTP ${res.status})`);
setMsg({ ok: true, text: `${action === "hide" ? "Hidden" : "Shown again"}: ${token}` });
- // a hidden card leaves the list at once; the next read (fresh on the machine that took the write) confirms it
- if (action === "hide") setLane((l) => (l?.enabled ? { ...l, items: l.items.filter((b) => b.token !== token.toLowerCase()) } : l));
- await load();
+ // the signed answer carries the lane read fresh, so the list is right at once, whichever machine answers next;
+ // without it (that read failed) the card still leaves at once and a cached read follows
+ if (d.lane) {
+ setLane(d.lane);
+ } else {
+ if (action === "hide") setLane((l) => (l?.enabled ? { ...l, items: l.items.filter((b) => b.token !== token.toLowerCase()) } : l));
+ await load();
+ }
} catch (e) {
setMsg({ ok: false, text: friendlyError(e) });
}
diff --git a/app/src/components/launchpad/BoostDialog.tsx b/app/src/components/launchpad/BoostDialog.tsx
index 0d622e96..8f3c98bc 100644
--- a/app/src/components/launchpad/BoostDialog.tsx
+++ b/app/src/components/launchpad/BoostDialog.tsx
@@ -16,7 +16,7 @@ import { DEAD } from "@/lib/launchpad/config";
import { TWIG_WRAP_URL } from "@/lib/launchpad/twig";
import { fmtUsd } from "@/lib/launchpad/math";
import { friendlyError } from "@/lib/errors";
-import { BUILDER_DATA_SUFFIX, CHAINS, explorerTx } from "@/lib/chainPublic";
+import { BUILDER_DATA_SUFFIX, CHAINS, SITE_URL, explorerTx } from "@/lib/chainPublic";
import {
BOOST_CHAIN,
BOOST_COPY,
@@ -33,6 +33,7 @@ import {
isLaunchTag,
maxBoostWhole,
parseBoostWhole,
+ boostShareUrl,
} from "@/lib/launchpad/boost";
import { toast } from "./TxToasts";
import { TwigMark } from "./TwigBadge";
@@ -150,6 +151,8 @@ export default function BoostDialog({ token, tokenId, symbol, name, twigUsd, cur
{fmtBoostTwig(boostAmount(phase.whole, tag))} went to 0x…dEaD. It counts for the next 24 hours and adds up with any other boost on {symbol}; the lane shows the 5 largest totals.
View transaction
+ {/* the booster's own post: what was sent and where it went, the token's page as its link */}
+ Share on X
See the lane
diff --git a/app/src/components/launchpad/BoostPanel.tsx b/app/src/components/launchpad/BoostPanel.tsx
index 207e29cf..2708e2a0 100644
--- a/app/src/components/launchpad/BoostPanel.tsx
+++ b/app/src/components/launchpad/BoostPanel.tsx
@@ -2,12 +2,12 @@
import dynamic from "next/dynamic";
import { useCallback, useEffect, useRef, useState } from "react";
-import { ArrowUpRight } from "lucide-react";
+import { ArrowUpRight, MousePointerClick } from "lucide-react";
import { btn } from "@/components/ui";
import { useLive } from "./LiveProvider";
import { TwigMark } from "./TwigBadge";
import type { TokenBoost } from "@/lib/launchpad/boostServer";
-import { BOOST_CHAIN, BOOST_COPY, boostTimeLeft, fmtBoostTwig } from "@/lib/launchpad/boost";
+import { BOOST_CHAIN, BOOST_COPY, BOOST_PANEL_CLICKS_MIN, boostTimeLeft, fmtBoostTwig } from "@/lib/launchpad/boost";
import { explorerTx } from "@/lib/chainPublic";
import type { PendingBoost } from "./BoostDialog";
@@ -15,8 +15,8 @@ import type { PendingBoost } from "./BoostDialog";
const BoostDialog = dynamic(() => import("./BoostDialog"), { ssr: false });
/**
- * A Base token's boost line on its page: whether it is in the Boosted lane, with how much TWIG and for how long, and
- * the button that opens the dialog. Nothing while BOOST is off. A token an admin hid says so and offers no button, so
+ * A Base token's boost line on its page: whether it is in the Boosted lane, with how much TWIG and for how long, what
+ * the lane brought it (clicks in the same 24 hours, from BOOST_PANEL_CLICKS_MIN up), and the button that opens the dialog. Nothing while BOOST is off. A token an admin hid says so and offers no button, so
* nobody sends TWIG for a place it cannot get. `autoOpen` comes from /me's "Boost" link (?boost=1).
*/
export default function BoostPanel({ token, tokenId, symbol, name, initial, twigUsd, serverNow, autoOpen = false }: { token: string; tokenId: number; symbol: string; name: string; initial: TokenBoost; twigUsd: number | null; serverNow: number; autoOpen?: boolean }) {
@@ -51,6 +51,8 @@ export default function BoostPanel({ token, tokenId, symbol, name, initial, twig
if (!state.enabled) return null;
const left = state.last_at ? boostTimeLeft(state.last_at, now) : null;
const active = left !== null && BigInt(state.twig) > 0n;
+ // absent from a machine on an older build mid-deploy: no line, never "undefined"
+ const clicks = state.clicks ?? 0;
return (
@@ -68,6 +70,13 @@ export default function BoostPanel({ token, tokenId, symbol, name, initial, twig
<>Not boosted. Sending TWIG to 0x…dEaD puts {symbol} in the Boosted lane for 24 hours.>
)}
+ {/* what the lane brought it: only while boosted, and not until it reads like a result */}
+ {active && clicks >= BOOST_PANEL_CLICKS_MIN ? (
+
+
+ {clicks.toLocaleString("en-US")} clicks from the lane in the last 24 hours
+