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
108 changes: 96 additions & 12 deletions app/globals.css
Original file line number Diff line number Diff line change
Expand Up @@ -6593,21 +6593,48 @@ html[data-theme="dark"] .theme-toggle-sun {
.classic-v3-split-row label > span:first-child,
.profile-v3-payout > span {
color: var(--muted);
font-size: 12px;
font-size: 13px;
font-weight: 640;
}

.classic-v3-fee-control > small,
.classic-v3-reward-note {
color: var(--muted);
font-size: 11.5px;
line-height: 1.35;
color: var(--text-soft);
font-size: 13px;
line-height: 1.4;
margin: 0;
}

.classic-v3-fee-breakdown {
display: flex;
flex-wrap: wrap;
gap: 4px 12px;
justify-content: space-between;
margin: 0;
}

.classic-v3-fee-breakdown > div {
align-items: baseline;
display: inline-flex;
gap: 5px;
min-width: 0;
white-space: nowrap;
}

.classic-v3-fee-control > small span {
color: var(--border-strong);
margin: 0 3px;
.classic-v3-fee-breakdown dt,
.classic-v3-fee-breakdown dd {
margin: 0;
}

.classic-v3-fee-breakdown dt {
color: var(--muted);
font-size: 12px;
}

.classic-v3-fee-breakdown dd {
color: var(--text-soft);
font-size: 13px;
font-variant-numeric: tabular-nums;
font-weight: 650;
}

.classic-v3-fees legend,
Expand Down Expand Up @@ -6765,7 +6792,7 @@ html[data-theme="dark"] .theme-toggle-sun {
background: transparent;
color: var(--muted);
font: inherit;
font-size: 13px;
font-size: 13.5px;
font-weight: 650;
padding: 0 10px;
white-space: nowrap;
Expand Down Expand Up @@ -6865,9 +6892,62 @@ html[data-theme="dark"] .theme-toggle-sun {
background: var(--surface-soft);
border: 1px solid var(--border);
border-radius: 16px;
grid-template-columns: minmax(0, 1fr) minmax(190px, 246px);
column-gap: 12px;
display: grid;
grid-template-columns: minmax(120px, 1fr) minmax(190px, 230px);
margin: 0;
padding: 13px;
row-gap: 12px;
}

.classic-v3-amount-label {
display: grid;
gap: 3px;
min-width: 0;
}

.classic-v3-amount-label strong {
color: var(--text);
font-size: 14px;
font-weight: 650;
}

.classic-v3-amount-label small {
color: var(--muted);
font-size: 12px;
line-height: 1.35;
margin: 0;
white-space: nowrap;
}

.classic-v3-buy-preview {
border-top: 1px solid var(--border);
display: grid;
gap: 8px;
grid-column: 1 / -1;
grid-template-columns: repeat(2, minmax(0, 1fr));
padding-top: 11px;
}

.classic-v3-buy-preview > span {
display: grid;
gap: 2px;
min-width: 0;
}

.classic-v3-buy-preview small {
color: var(--muted);
font-size: 12px;
line-height: 1.35;
}

.classic-v3-buy-preview strong {
color: var(--text);
font-size: 14px;
font-variant-numeric: tabular-nums;
font-weight: 650;
line-height: 1.35;
overflow-wrap: anywhere;
}

.classic-v3-initial-buy .meme-dev-buy-input button {
Expand Down Expand Up @@ -6903,7 +6983,7 @@ html[data-theme="dark"] .theme-toggle-sun {
.classic-v3-custody legend,
.classic-v3-custody label > span:first-child {
color: var(--muted);
font-size: 12px;
font-size: 13px;
font-weight: 650;
}

Expand Down Expand Up @@ -7274,10 +7354,14 @@ html[data-theme="dark"] .theme-toggle-sun {
}

.classic-v3-reward-mode button {
font-size: 12px;
font-size: 12.5px;
padding-inline: 4px;
}

.classic-v3-buy-preview {
grid-template-columns: 1fr;
}

}

@media (min-width: 1120px) {
Expand Down
79 changes: 65 additions & 14 deletions components/launch-builder.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@ import {
createDeepDraft,
createEmptyDraft,
createStockPairedDraft,
getClassicInitialBuyPreview,
maximumClassicDevBuyWei,
MEME_MIN_INITIAL_BUY_ETH,
MEME_MIN_INITIAL_BUY_ETH_LABEL,
Expand Down Expand Up @@ -2971,6 +2972,16 @@ const initialBuyAccessOptions: readonly LaunchSelectOption<
{ value: "cliff-linear", label: "Cliff, then linear vesting" },
];

const compactInitialBuyTokenFormatter = new Intl.NumberFormat("en-US", {
compactDisplay: "short",
maximumFractionDigits: 2,
notation: "compact",
});

const initialBuySupplyFormatter = new Intl.NumberFormat("en-US", {
maximumFractionDigits: 2,
});

function EnhancedClassicFeeStep({
draft,
setDraft,
Expand Down Expand Up @@ -3005,12 +3016,19 @@ function EnhancedClassicFeeStep({
return Number.isFinite(share) ? total + share : total;
}, 0);
const splitIsComplete = Math.abs(splitTotal - 100) < 0.001;
const initialBuyPreview = getClassicInitialBuyPreview(
draft.initialBuyEth,
draft.buySwapFeePercent,
);
const initialBuyTokenLabel = draft.tokenSymbol.trim()
? `$${draft.tokenSymbol.trim().toUpperCase()}`
: "tokens";
const rewardNote =
draft.rewardDestinationMode === "launcher"
? "Rewards go to the launch wallet"
? "Paid to the launch wallet"
: draft.rewardDestinationMode === "external"
? "Rewards go to one wallet"
: "Each wallet claims its own share";
? "Paid to the selected wallet"
: "Each recipient claims their share";

return (
<section className="classic-v3-settings" aria-labelledby="classic-v3-fees">
Expand Down Expand Up @@ -3042,12 +3060,20 @@ function EnhancedClassicFeeStep({
updateClassicV3Draft({ [key]: value })
}
/>
<small>
{Number.isFinite(creatorFeeBps)
? formatClassicV3Percent(creatorFeeBps)
: "—"}{" "}
creator <span>·</span> 0.10% Programmable
</small>
<dl className="classic-v3-fee-breakdown">
<div>
<dt>Creator</dt>
<dd>
{Number.isFinite(creatorFeeBps)
? formatClassicV3Percent(creatorFeeBps)
: "—"}
</dd>
</div>
<div>
<dt>Programmable</dt>
<dd>0.10%</dd>
</div>
</dl>
</div>
);
})}
Expand Down Expand Up @@ -3079,7 +3105,7 @@ function EnhancedClassicFeeStep({
</button>
))}
</div>
<small className="classic-v3-reward-note">{rewardNote}</small>
<p className="classic-v3-reward-note">{rewardNote}</p>
</fieldset>
</div>

Expand Down Expand Up @@ -3186,11 +3212,14 @@ function EnhancedClassicFeeStep({
</div>

<div className="classic-v3-initial-buy">
<label className="meme-dev-buy" htmlFor="classic-v3-dev-buy">
<span>
<div className="meme-dev-buy">
<label
className="classic-v3-amount-label"
htmlFor="classic-v3-dev-buy"
>
<strong>Amount</strong>
<small>Minimum {MEME_MIN_INITIAL_BUY_ETH_LABEL}</small>
</span>
</label>
<span className="meme-dev-buy-input">
<input
id="classic-v3-dev-buy"
Expand All @@ -3215,7 +3244,29 @@ function EnhancedClassicFeeStep({
</button>
<span>ETH</span>
</span>
</label>
<div
className="classic-v3-buy-preview"
role="status"
aria-live="polite"
>
<span>
<small>Estimated tokens</small>
<strong>
{initialBuyPreview
? `≈ ${compactInitialBuyTokenFormatter.format(initialBuyPreview.tokenAmount)} ${initialBuyTokenLabel}`
: "—"}
</strong>
</span>
<span>
<small>Share of supply</small>
<strong>
{initialBuyPreview
? `≈ ${initialBuySupplyFormatter.format(initialBuyPreview.supplyPercent)}%`
: "—"}
</strong>
</span>
</div>
</div>

<fieldset
className="classic-v3-custody"
Expand Down
67 changes: 66 additions & 1 deletion lib/launch.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { parseEther } from "viem";
import { formatEther, parseEther } from "viem";

export const PLATFORM_FEE_BPS = 10;
export const CLASSIC_TOTAL_SWAP_FEE_PERCENT = "1";
Expand All @@ -14,6 +14,12 @@ export const MEME_MIN_INITIAL_BUY_WEI = 600_000_000_000_000n;
export const MEME_MIN_INITIAL_BUY_ETH = "0.0006";
export const MEME_MIN_INITIAL_BUY_ETH_LABEL =
`${MEME_MIN_INITIAL_BUY_ETH} ETH`;
const MEME_MIN_USABLE_TICK = -887_200;
const MEME_INITIAL_SQRT_PRICE = 1.0001 ** (MEME_INITIAL_TICK / 2);
const MEME_MIN_SQRT_PRICE = 1.0001 ** (MEME_MIN_USABLE_TICK / 2);
const MEME_INITIAL_LIQUIDITY =
MEME_TOKEN_SUPPLY_WHOLE /
(MEME_INITIAL_SQRT_PRICE - MEME_MIN_SQRT_PRICE);
export const ADAPTIVE_MIN_FDV_INDEX = -887_272;
export const ADAPTIVE_MAX_FDV_INDEX = 887_272;
export const ADAPTIVE_MIN_FEE_BPS = 100;
Expand Down Expand Up @@ -50,6 +56,13 @@ export type ClassicInitialBuyCustodyMode =
| "linear"
| "cliff-linear";

export type ClassicInitialBuyPreview = {
grossEthAmount: number;
poolEthAmount: number;
tokenAmount: number;
supplyPercent: number;
};

export type AdaptiveCurvePointDraft = {
fdvIndex: number;
totalSwapFeeBps: number;
Expand Down Expand Up @@ -248,6 +261,58 @@ export function parseInitialBuyWei(value: string | null | undefined) {
}
}

export function getClassicInitialBuyPreview(
initialBuyEth: string,
buyFeePercent: string,
): ClassicInitialBuyPreview | null {
const initialBuyWei = parseInitialBuyWei(initialBuyEth);
const normalizedFeePercent = Number(buyFeePercent.trim());

if (
initialBuyWei === null ||
!Number.isInteger(normalizedFeePercent) ||
normalizedFeePercent < 1 ||
normalizedFeePercent > 10
) {
return null;
}

const grossEthAmount = Number(formatEther(initialBuyWei));
const poolEthAmount = grossEthAmount * (1 - normalizedFeePercent / 100);

if (
!Number.isFinite(grossEthAmount) ||
!Number.isFinite(poolEthAmount) ||
poolEthAmount <= 0
) {
return null;
}

const nextSqrtPrice =
(MEME_INITIAL_LIQUIDITY * MEME_INITIAL_SQRT_PRICE) /
(MEME_INITIAL_LIQUIDITY +
poolEthAmount * MEME_INITIAL_SQRT_PRICE);
const tokenAmount = Math.max(
0,
Math.min(
MEME_TOKEN_SUPPLY_WHOLE,
MEME_INITIAL_LIQUIDITY *
(MEME_INITIAL_SQRT_PRICE - nextSqrtPrice),
),
);

if (!Number.isFinite(tokenAmount) || tokenAmount <= 0) {
return null;
}

return {
grossEthAmount,
poolEthAmount,
tokenAmount,
supplyPercent: (tokenAmount / MEME_TOKEN_SUPPLY_WHOLE) * 100,
};
}

export function parseOptionalInitialBuyWei(value: string | null | undefined) {
const normalized = typeof value === "string" ? value.trim() : "";
if (
Expand Down
Loading