// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;
import "@openzeppelin/contracts/access/Ownable2Step.sol";
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import "@openzeppelin/contracts/utils/ReentrancyGuard.sol";
import "@openzeppelin/contracts/utils/math/Math.sol";
import "./ILaunchpadV2.sol";
import {
SafeLaunchClLib,
IUpClFactoryMinimal,
IUpNfpmMinimal,
ISafeLaunchClLocker
} from "./SafeLaunchClLib.sol";
/// @dev Clock In Card referral ledger. `punch` receives the StockBooster
/// remainder of a taxed trade with trader attribution; the card splits it
/// referrer / StockBooster per its own share knob. The pad calls it inside
/// try/catch — a broken card can never brick trading.
interface IClockInCard {
function punch(uint256 launchId, address trader, bytes32 code) external payable;
}
/// @dev Minimal fixed-supply token for the factory-minted launch path.
contract SafeLaunchToken is ERC20 {
constructor(string memory name_, string memory symbol_, uint256 supply_, address to)
ERC20(name_, symbol_)
{
_mint(to, supply_);
}
}
/// @title StonkSafeLaunchpad
/// @notice StonkBrokers Safe Launch — the public, permissionless generalization
/// of the WALL fair launch. One singleton hosts every launch (keyed by id):
///
/// - A creator registers a launch (factory-minted fixed-supply token — mined
/// to the vanity suffix — or a bring-your-own ERC-20, flagged
/// `externalToken`) with tunable economics inside owner-set bounds, then
/// ARMS it by loading the supply — the load IS the trigger: price snaps to
/// the start mcap and the decay clock runs.
/// - Buys are EOA-only and pay a tax that starts high (e.g. 99%) and decays
/// every minute. Sells are creator-optional per launch and pay the same
/// decaying tax on the ETH they take out, so an early dump costs what an
/// early snipe costs. Tax splits creator / protocol / StockBooster / LP
/// reserve (default 10 / 20 / 30 / 40). Creator and protocol push live
/// (the creator leg is push-tolerant: accrues + permissionless flush).
/// The StockBooster remainder routes through the Clock In Card referral
/// ledger when one is wired: referred wallets' taxed volume pays their
/// referrer a share, fully on-chain. The LP reserve escrows in the launch
/// and deepens the PERMANENTLY locked pools at bond — sniper taxes
/// literally buy the token its own liquidity.
/// - The curve CLOSES at the graduation mcap (in-buy or permissionless
/// `graduate`) or at timer zero; a separate permissionless retryable
/// `bond` then splits raise + LP reserve equally across the
/// creator-chosen quote legs (ETH and/or owner-listed swap assets:
/// STONKBROKER, tokenized stocks with depth), mints up. Slipstream CL
/// full-range positions at the closing curve price (CL, not v2, so
/// DexScreener and every indexer see the pools), and locks the position
/// NFTs PERMANENTLY in the Safety Deposit Box CL locker under the 80/20
/// yield mode. Lock NFTs (the 80% fee claim) go to the creator. Leftover
/// launch tokens burn.
///
/// Swap-leg floors: assets with a Chainlink feed are floored on-chain at
/// oracle mark −10% (the launcher `_legFloor` pattern, permissionless-safe);
/// feed-less assets (STONKBROKER) use an owner-pinned per-asset floor that
/// ops re-pins from live probes (the Directed Clock In pattern). A floored
/// fill reverts `bond` without touching curve state — re-pin and retry.
///
/// Safety inherited from the WALL audit: EOA gate (`tx.origin`), nonReentrant
/// everywhere, checks-effects-interactions, no owner path to any raise, abort
/// only while a launch has ZERO trades, stale-feed fail-closed (48h).
contract StonkSafeLaunchpad is Ownable2Step, ReentrancyGuard {
using SafeERC20 for IERC20;
uint256 private constant BPS = 10_000;
uint256 private constant MAX_PRICE_AGE = 48 hours;
uint256 private constant LEG_SLIPPAGE_BPS = 1_000; // oracle-leg floor: mark −10%
/// @dev After this long past curve close, if the primary bond() still has
/// not succeeded (dead swap route at any honest floor), anyone may bond
/// the full raise into the token/WETH pool alone — the raise can never be
/// stranded AND still can never be extracted.
/// @dev 120h (not 48h): the rescue rail must never become callable while
/// bond() is merely feed-stale, or anyone could discard the creator's
/// chosen stock leg. Chainlink classes every equity feed on this chain
/// us_equities_24/5, so they publish nothing from Friday close to Monday
/// 00:00 UTC while MAX_PRICE_AGE is only 48h. Measured across all 35
/// feeds' full history: worst gap 79.74h (AAPL, July 4 weekend), and a
/// quiet feed's 24h heartbeat slack plus a MONDAY market holiday puts the
/// worst case at ~96h. 120h clears that with margin.
uint256 public constant RESCUE_DELAY = 120 hours;
uint256 private constant MAX_LEGS = 2;
int24 private constant MAX_TICK = 887272;
address private constant DEAD = 0x000000000000000000000000000000000000dEaD;
// ------------------------------------------------------------ immutables
address public immutable stockBooster;
address public immutable treasury;
IAggregatorV3 public immutable ethUsdFeed;
IUpNfpmMinimal public immutable nfpm;
IUpClFactoryMinimal public immutable clFactory;
IWETH9Minimal public immutable weth;
ISafeLaunchClLocker public immutable boxLocker;
// ------------------------------------------------------- global config
struct QuoteLegConfig {
address router; // IStonkSwapRouter shape (ETH in, token out)
address feed; // Chainlink USD feed; 0 = fixed-floor asset
bool listed;
}
struct Bounds {
uint64 minStartMcapUsd8;
uint64 maxStartMcapUsd8;
uint64 minGradMcapUsd8;
uint64 maxGradMcapUsd8;
uint16 maxStartTaxBps;
uint32 minWindowSecs;
uint32 maxWindowSecs;
}
/// @dev Owner-listed swap-leg menu. address(0) (raw ETH -> token/WETH CL
/// pool) is always allowed and never listed here.
mapping(address => QuoteLegConfig) public quoteLegs;
/// @dev Fixed floors for feed-less swap assets, token wei per 1 ETH.
mapping(address => uint256) public fixedFloorPerEth;
Bounds public bounds;
uint16 public creatorFeeBps; // of the tax
uint16 public protocolFeeBps; // of the tax
uint16 public lpFeeBps; // of the tax, escrowed into the locked LP at bond; remainder -> StockBooster
uint256 public launchFeeWei; // flat create fee -> treasury (prohibitive value = factory closed)
/// @dev Clock In Card referral ledger; 0 = remainder goes straight to
/// StockBooster. Owner-repointable (a broken card fails soft either way).
address public clockInCard;
/// @dev Tick spacing for every bonded CL pool (owner-set; 200 = the
/// standard volatile class on up.).
int24 public clTickSpacing = 200;
/// @dev Factory-minted token addresses must satisfy
/// (address & vanityMask) == vanityValue. Default suffix: …5afe ("SAFE").
/// Mask 0 disables. Salts are mined off-chain against tokenInitCodeHash.
uint160 public vanityMask = 0xffff;
uint160 public vanityValue = 0x5afe;
// -------------------------------------------------------------- launches
struct Launch {
address token;
address creator;
uint64 startMcapUsd8;
uint64 gradMcapUsd8;
uint16 startTaxBps;
uint16 decayPerMinuteBps;
uint16 creatorFeeBpsSnap;
uint16 protocolFeeBpsSnap;
uint32 windowSecs;
uint64 startTime;
uint64 deadline;
bool externalToken;
bool sellsEnabled;
bool armed;
bool graduated;
bool bonded;
bool aborted;
uint256 loadedSupply;
uint256 vEth; // virtual ETH reserve (wei)
uint256 vToken; // virtual token reserve (token wei)
uint256 realEth; // net escrowed raise
uint256 buyCount; // buys + sells (any trade blocks abort)
}
Launch[] private _launches;
mapping(uint256 => address[]) private _legsOf; // address(0) = ETH leg
mapping(uint256 => address[]) private _poolsOf;
mapping(uint256 => uint256[]) private _lockIdsOf;
/// @dev One launch per token EVER — keeps every token balance in this
/// singleton exclusively attributable to one launch. 1-based; 0 = none.
mapping(address => uint256) public launchIdOfToken;
/// @dev Tolerant creator ETH legs (fee pushes that bounced).
mapping(uint256 => uint256) public creatorEthOwed;
/// @dev Per-launch LP fee snapshot (bps of the tax, taken at create) and
/// the accrued LP reserve it feeds.
mapping(uint256 => uint16) public lpFeeBpsSnapOf;
mapping(uint256 => uint256) public lpEthOf;
/// @dev Curve-close timestamp per launch — starts the rescue clock.
mapping(uint256 => uint64) public closedAt;
/// @dev Tokens each wallet bought from the curve, net of sells. Sells are
/// capped at this — creator overhang or transferred bags can NEVER be
/// dumped into the curve to extract the raise (the soft-rug vector).
mapping(uint256 => mapping(address => uint256)) public boughtOf;
/// @dev Sum of every open launch's escrowed raise + LP fee reserve. ETH
/// above this is unattributable donations, owner-rescuable to the
/// treasury — the owner can never sweep a raise or an LP reserve.
uint256 public totalEscrowed;
uint256 public totalOwed;
// ---------------------------------------------------------------- errors
error NotCreator();
error UnknownLaunch();
error TokenInUse();
error TokenIsQuoteAsset();
error BadLegs();
error BadEconomics();
error InsufficientLaunchFee();
error AlreadyArmed();
error NotArmed();
error BadParam();
error AlreadyGraduated();
error AlreadyBonded();
error NotClosed();
error AbortedLaunch();
error WindowClosed();
error NotEoa();
error ZeroTrade();
error SellsDisabled();
error SellExceedsCurve();
error SlippageExceeded();
error NotGraduatable();
error FloorNotSet();
error BuysPresent();
error StalePrice();
error BadAnswer();
error FeePushFailed();
error NothingOwed();
error PoolMispriced();
error RescueTooEarly();
error SaltNotYours();
error VanitySuffix();
error FeeOnTransferToken();
// ---------------------------------------------------------------- events
event QuoteLegListed(address indexed asset, address router, address feed);
event QuoteLegDelisted(address indexed asset);
event FixedFloorPinned(address indexed asset, uint256 perEth);
event BoundsSet(Bounds bounds);
event FeeSplitSet(uint16 creatorBps, uint16 protocolBps, uint16 lpBps);
event LaunchFeeSet(uint256 feeWei);
event ClockInCardSet(address card);
event TickSpacingSet(int24 spacing);
event VanitySuffixSet(uint160 mask, uint160 value);
event LaunchCreated(
uint256 indexed id, address indexed token, address indexed creator, bool externalToken, address[] legs
);
event LaunchArmed(uint256 indexed id, uint256 supply, uint256 vEth0, uint64 ethUsd8, uint64 deadline);
event SafeBuy(
uint256 indexed id,
address indexed buyer,
uint256 ethIn,
uint256 taxPaid,
uint256 taxBps,
uint256 tokensOut,
uint256 mcapUsd8
);
event SafeSell(
uint256 indexed id,
address indexed seller,
uint256 tokensIn,
uint256 taxPaid,
uint256 taxBps,
uint256 ethOut,
uint256 mcapUsd8
);
event CurveClosed(uint256 indexed id, uint256 raisedEth, uint256 finalPriceWei, uint256 mcapUsd8);
event LegBonded(uint256 indexed id, address indexed asset, address pool, uint256 lockTokenId, uint256 legEth);
event LaunchBonded(uint256 indexed id, uint256 raisedEth, uint256 burnedTokens);
/// @dev LP fee reserve settled at bond: `bondedWei` deepened the locked
/// pools; `excessWei` (uncoverable by the token reserve at the closing
/// price) went to StockBooster — fee money, never raise.
event LpFeeBonded(uint256 indexed id, uint256 bondedWei, uint256 excessWei);
event LaunchAborted(uint256 indexed id, uint256 tokensSwept);
event CreatorEthAccrued(uint256 indexed id, uint256 amount);
event CreatorEthFlushed(uint256 indexed id, uint256 amount);
constructor(
address stockBooster_,
address treasury_,
address ethUsdFeed_,
address nfpm_,
address weth_,
address boxLocker_,
uint256 launchFeeWei_
) Ownable(msg.sender) {
if (
stockBooster_ == address(0) || treasury_ == address(0) || ethUsdFeed_ == address(0)
|| nfpm_ == address(0) || weth_ == address(0) || boxLocker_ == address(0)
) revert BadParam();
stockBooster = stockBooster_;
treasury = treasury_;
ethUsdFeed = IAggregatorV3(ethUsdFeed_);
nfpm = IUpNfpmMinimal(nfpm_);
clFactory = IUpClFactoryMinimal(IUpNfpmMinimal(nfpm_).factory());
weth = IWETH9Minimal(weth_);
boxLocker = ISafeLaunchClLocker(boxLocker_);
launchFeeWei = launchFeeWei_;
// Launch-decision defaults (2026-08-15): 10% creator / 20% protocol /
// 30% StockBooster / 40% LP reserve — the biggest slice of every
// sniper tax deepens the launch's own permanently locked LP.
creatorFeeBps = 1_000;
protocolFeeBps = 2_000;
lpFeeBps = 4_000;
// Creator-tunable economics (2026-08-15, user decision): start mcap
// $1k–$1M, graduation $50k–$10M (grad > start enforced per launch),
// start tax up to 99%, window 10–99 minutes. With the standard
// 1%/minute decay the window minutes always equal the start tax %.
// UI defaults: 99% / 99 min / $1k start / $200k bond.
bounds = Bounds({
minStartMcapUsd8: 1_000e8, // $1k
maxStartMcapUsd8: 1_000_000e8, // $1M
minGradMcapUsd8: 50_000e8, // $50k
maxGradMcapUsd8: 10_000_000e8, // $10M
maxStartTaxBps: 9_900, // 99%
minWindowSecs: 10 minutes,
maxWindowSecs: 99 minutes
});
}
receive() external payable {}
/// @dev Position NFTs pass through here between NFPM mint and box lock.
function onERC721Received(address, address, uint256, bytes calldata) external pure returns (bytes4) {
return this.onERC721Received.selector;
}
// ----------------------------------------------------------------- admin
/// @notice List a swap-leg quote asset. `feed != 0` -> oracle-floored at
/// mark −10% (permissionless-safe); `feed == 0` -> requires a pinned
/// fixed floor (ops re-pins from live probes, Directed Clock In style).
function listQuoteLeg(address asset, address router, address feed) external onlyOwner {
if (asset == address(0) || router == address(0)) revert BadParam();
if (launchIdOfToken[asset] != 0) revert TokenInUse();
quoteLegs[asset] = QuoteLegConfig({router: router, feed: feed, listed: true});
emit QuoteLegListed(asset, router, feed);
}
/// @notice Delist blocks NEW launches only. Router/feed stay live so
/// already-created launches always keep a bond path (and stay
/// owner-repairable via re-listing with a fixed router).
function delistQuoteLeg(address asset) external onlyOwner {
quoteLegs[asset].listed = false;
emit QuoteLegDelisted(asset);
}
function setFixedFloor(address asset, uint256 perEth) external onlyOwner {
if (perEth == 0) revert BadParam();
fixedFloorPerEth[asset] = perEth;
emit FixedFloorPinned(asset, perEth);
}
function setBounds(Bounds calldata b) external onlyOwner {
if (b.minStartMcapUsd8 == 0 || b.maxStartMcapUsd8 < b.minStartMcapUsd8) revert BadParam();
if (b.minGradMcapUsd8 == 0 || b.maxGradMcapUsd8 < b.minGradMcapUsd8) revert BadParam();
if (b.maxStartTaxBps >= BPS || b.minWindowSecs == 0 || b.maxWindowSecs < b.minWindowSecs) revert BadParam();
bounds = b;
emit BoundsSet(b);
}
function setFeeSplit(uint16 creatorBps_, uint16 protocolBps_, uint16 lpBps_) external onlyOwner {
if (uint256(creatorBps_) + protocolBps_ + lpBps_ > BPS) revert BadParam();
creatorFeeBps = creatorBps_;
protocolFeeBps = protocolBps_;
lpFeeBps = lpBps_;
emit FeeSplitSet(creatorBps_, protocolBps_, lpBps_);
}
function setLaunchFee(uint256 feeWei) external onlyOwner {
launchFeeWei = feeWei;
emit LaunchFeeSet(feeWei);
}
function setClockInCard(address card) external onlyOwner {
clockInCard = card;
emit ClockInCardSet(card);
}
function setTickSpacing(int24 spacing) external onlyOwner {
if (spacing <= 0 || spacing > MAX_TICK) revert BadParam();
clTickSpacing = spacing;
emit TickSpacingSet(spacing);
}
function setVanitySuffix(uint160 mask, uint160 value) external onlyOwner {
if (value & mask != value) revert BadParam();
vanityMask = mask;
vanityValue = value;
emit VanitySuffixSet(mask, value);
}
/// @notice Sweep unattributable ETH donations (balance above escrow+owed).
function rescueExcessEth() external onlyOwner {
uint256 excess = address(this).balance - totalEscrowed - totalOwed;
if (excess == 0) revert NothingOwed();
(bool ok,) = treasury.call{value: excess}("");
if (!ok) revert BadParam();
}
/// @notice Sweep stray QUOTE-asset dust (CL mint refunds bounded by the
/// mint mins, donations). Launch tokens are exclusively attributable to
/// their launch and can never be swept.
function rescueQuoteToken(address asset) external onlyOwner {
if (launchIdOfToken[asset] != 0) revert TokenInUse();
uint256 bal = IERC20(asset).balanceOf(address(this));
if (bal == 0) revert NothingOwed();
IERC20(asset).safeTransfer(treasury, bal);
}
// ---------------------------------------------------------------- create
struct CreateParams {
address token; // 0 = mint a fresh fixed-supply token to the creator
string name; // minted path only
string symbol; // minted path only
uint256 supply; // minted path only (token wei)
bytes32 vanitySalt; // minted path only; first 20 bytes MUST be the creator
uint64 startMcapUsd8;
uint64 gradMcapUsd8;
uint16 startTaxBps;
uint16 taxDecayPerMinuteBps;
bool sellsEnabled;
address[] legs; // 1..2 distinct: address(0) = ETH; others owner-listed
}
/// @notice Mining target for the vanity salt: CREATE2 address =
/// keccak256(0xff ++ this ++ salt ++ tokenInitCodeHash(...))[12:].
function tokenInitCodeHash(string calldata name_, string calldata symbol_, uint256 supply_, address to)
external
pure
returns (bytes32)
{
return keccak256(abi.encodePacked(type(SafeLaunchToken).creationCode, abi.encode(name_, symbol_, supply_, to)));
}
function createLaunch(CreateParams calldata p) external payable nonReentrant returns (uint256 id, address token) {
if (msg.value < launchFeeWei) revert InsufficientLaunchFee();
// economics inside owner bounds
Bounds memory b = bounds;
if (
p.startMcapUsd8 < b.minStartMcapUsd8 || p.startMcapUsd8 > b.maxStartMcapUsd8
|| p.gradMcapUsd8 <= p.startMcapUsd8 || p.gradMcapUsd8 < b.minGradMcapUsd8
|| p.gradMcapUsd8 > b.maxGradMcapUsd8
) revert BadEconomics();
if (p.startTaxBps == 0 || p.startTaxBps > b.maxStartTaxBps) revert BadEconomics();
if (p.taxDecayPerMinuteBps == 0 || p.startTaxBps % p.taxDecayPerMinuteBps != 0) revert BadEconomics();
uint32 windowSecs = uint32((uint256(p.startTaxBps) / p.taxDecayPerMinuteBps) * 60);
if (windowSecs < b.minWindowSecs || windowSecs > b.maxWindowSecs) revert BadEconomics();
// token
bool external_;
if (p.token == address(0)) {
if (p.supply == 0 || bytes(p.name).length == 0 || bytes(p.symbol).length == 0) revert BadParam();
if (p.vanitySalt != bytes32(0)) {
if (address(bytes20(p.vanitySalt)) != msg.sender) revert SaltNotYours();
token = address(new SafeLaunchToken{salt: p.vanitySalt}(p.name, p.symbol, p.supply, msg.sender));
} else {
token = address(new SafeLaunchToken(p.name, p.symbol, p.supply, msg.sender));
}
if (vanityMask != 0 && (uint160(token) & vanityMask) != vanityValue) revert VanitySuffix();
} else {
token = p.token;
external_ = true;
}
if (launchIdOfToken[token] != 0) revert TokenInUse();
if (quoteLegs[token].listed) revert TokenIsQuoteAsset();
if (token == address(weth)) revert BadParam();
// legs
uint256 n = p.legs.length;
if (n == 0 || n > MAX_LEGS) revert BadLegs();
for (uint256 i = 0; i < n; i++) {
address leg = p.legs[i];
if (leg == token) revert BadLegs();
if (leg != address(0) && !quoteLegs[leg].listed) revert BadLegs();
for (uint256 j = i + 1; j < n; j++) {
if (p.legs[j] == leg) revert BadLegs();
}
}
_launches.push(
Launch({
token: token,
creator: msg.sender,
startMcapUsd8: p.startMcapUsd8,
gradMcapUsd8: p.gradMcapUsd8,
startTaxBps: p.startTaxBps,
decayPerMinuteBps: p.taxDecayPerMinuteBps,
creatorFeeBpsSnap: creatorFeeBps,
protocolFeeBpsSnap: protocolFeeBps,
windowSecs: windowSecs,
startTime: 0,
deadline: 0,
externalToken: external_,
sellsEnabled: p.sellsEnabled,
armed: false,
graduated: false,
bonded: false,
aborted: false,
loadedSupply: 0,
vEth: 0,
vToken: 0,
realEth: 0,
buyCount: 0
})
);
id = _launches.length; // 1-based
launchIdOfToken[token] = id;
_legsOf[id] = p.legs;
lpFeeBpsSnapOf[id] = lpFeeBps;
// fee -> treasury, excess refunded
if (launchFeeWei > 0) _pushEth(treasury, launchFeeWei);
uint256 refund = msg.value - launchFeeWei;
if (refund > 0) _pushEth(msg.sender, refund);
emit LaunchCreated(id, token, msg.sender, external_, p.legs);
}
/// @notice Load the supply and start the launch — the load IS the trigger.
/// Fee-on-transfer tokens are REJECTED here (the CL position mint cannot
/// tolerate them at bond; failing early keeps a raise from ever being
/// stranded behind an unbondable token).
function arm(uint256 id, uint256 supplyWei) external nonReentrant {
Launch storage l = _launch(id);
if (msg.sender != l.creator) revert NotCreator();
if (l.armed) revert AlreadyArmed();
if (l.aborted) revert AbortedLaunch();
if (supplyWei == 0) revert BadParam();
// Feed-less swap legs must have a pinned floor before the clock starts.
address[] memory legs = _legsOf[id];
for (uint256 i = 0; i < legs.length; i++) {
if (legs[i] != address(0) && quoteLegs[legs[i]].feed == address(0) && fixedFloorPerEth[legs[i]] == 0) {
revert FloorNotSet();
}
}
uint256 balBefore = IERC20(l.token).balanceOf(address(this));
IERC20(l.token).safeTransferFrom(msg.sender, address(this), supplyWei);
uint256 received = IERC20(l.token).balanceOf(address(this)) - balBefore;
if (received != supplyWei) revert FeeOnTransferToken();
// Pre-arm donations fold in benignly — virtual ETH is supply-independent.
uint256 got = IERC20(l.token).balanceOf(address(this));
uint64 usd8 = _ethUsd8();
l.loadedSupply = got;
l.vToken = got;
// start price * supply = startMcap: vEth0 = startMcapUsd8 * 1e18 / ethUsd8
l.vEth = Math.mulDiv(uint256(l.startMcapUsd8), 1e18, usd8);
if (l.vEth == 0) revert BadParam();
// Supply sanity vs the start mcap: cap the token/ETH virtual ratio so
// the CL sqrt-price math at bond keeps ample precision + overflow
// margin at every legal raise size. Still allows quadrillions of
// tokens at any in-bounds start mcap.
if (got / l.vEth > 1e16) revert BadEconomics();
l.armed = true;
l.startTime = uint64(block.timestamp);
l.deadline = uint64(block.timestamp) + l.windowSecs;
emit LaunchArmed(id, got, l.vEth, usd8, l.deadline);
}
/// @notice Creator cancel — ONLY while the launch has zero trades of any
/// kind. Sweeps the loaded supply home. Never touches anyone's ETH
/// (no trades means no raise exists).
function abort(uint256 id) external nonReentrant {
Launch storage l = _launch(id);
if (msg.sender != l.creator) revert NotCreator();
if (l.bonded) revert AlreadyBonded();
if (l.buyCount != 0) revert BuysPresent();
l.aborted = true;
// Zero trades means the token balance is untouched supply — sweep it
// home and free the token's slot so an aborted config can relaunch.
launchIdOfToken[l.token] = 0;
uint256 bal = IERC20(l.token).balanceOf(address(this));
if (bal > 0) IERC20(l.token).safeTransfer(l.creator, bal);
emit LaunchAborted(id, bal);
}
// ---------------------------------------------------------------- trades
function buy(uint256 id, uint256 minTokensOut, bytes32 ref)
external
payable
nonReentrant
returns (uint256 tokensOut)
{
Launch storage l = _launch(id);
_tradeGates(l);
if (msg.value == 0) revert ZeroTrade();
uint256 taxBps = currentTaxBps(id);
uint256 tax = (msg.value * taxBps) / BPS;
uint256 net = msg.value - tax;
if (net == 0) revert ZeroTrade();
tokensOut = Math.mulDiv(net, l.vToken, l.vEth + net);
if (tokensOut == 0 || tokensOut < minTokensOut) revert SlippageExceeded();
l.vEth += net;
l.vToken -= tokensOut;
l.realEth += net;
totalEscrowed += net;
l.buyCount += 1;
boughtOf[id][msg.sender] += tokensOut;
_splitTax(id, l, msg.sender, ref, tax);
IERC20(l.token).safeTransfer(msg.sender, tokensOut);
uint256 mcap = mcapUsd8(id);
emit SafeBuy(id, msg.sender, msg.value, tax, taxBps, tokensOut, mcap);
if (mcap >= l.gradMcapUsd8) _close(id, l);
}
/// @notice Sell back into the curve — creator-optional per launch. The
/// ETH taken out pays the SAME decaying tax a buy pays, so dumping into
/// the launch window costs what sniping it costs. A wallet can only ever
/// sell back what IT bought from this curve (`boughtOf`): un-loaded
/// creator overhang, externally minted supply, or a transferred bag can
/// never be dumped into the curve to extract the raise.
function sell(uint256 id, uint256 tokensIn, uint256 minEthOut, bytes32 ref)
external
nonReentrant
returns (uint256 ethOut)
{
Launch storage l = _launch(id);
_tradeGates(l);
if (!l.sellsEnabled) revert SellsDisabled();
if (tokensIn == 0) revert ZeroTrade();
IERC20 tok = IERC20(l.token);
uint256 balBefore = tok.balanceOf(address(this));
tok.safeTransferFrom(msg.sender, address(this), tokensIn);
uint256 got = tok.balanceOf(address(this)) - balBefore;
if (got == 0) revert ZeroTrade();
if (got > boughtOf[id][msg.sender]) revert SellExceedsCurve();
uint256 gross = Math.mulDiv(got, l.vEth, l.vToken + got);
uint256 taxBps = currentTaxBps(id);
uint256 tax = (gross * taxBps) / BPS;
ethOut = gross - tax;
if (ethOut == 0 || ethOut < minEthOut) revert SlippageExceeded();
boughtOf[id][msg.sender] -= got;
l.vToken += got;
l.vEth -= gross;
l.realEth -= gross;
totalEscrowed -= gross;
l.buyCount += 1;
_splitTax(id, l, msg.sender, ref, tax);
_pushEth(msg.sender, ethOut);
emit SafeSell(id, msg.sender, got, tax, taxBps, ethOut, mcapUsd8(id));
}
function _tradeGates(Launch storage l) private view {
if (!l.armed) revert NotArmed();
if (l.aborted) revert AbortedLaunch();
if (l.graduated) revert AlreadyGraduated();
if (block.timestamp >= l.deadline) revert WindowClosed();
if (msg.sender != tx.origin) revert NotEoa();
}
/// @dev Tax legs: creator (tolerant push) / protocol push live; the LP
/// slice escrows here (inside totalEscrowed, so it is owner-unrescuable)
/// and deepens the locked pools at bond; the StockBooster remainder
/// routes through the Clock In Card when one is wired (referral share to
/// the trader's referrer, rest to StockBooster — fails soft to a direct
/// StockBooster push, never bricking a trade).
function _splitTax(uint256 id, Launch storage l, address trader, bytes32 ref, uint256 tax) private {
if (tax == 0) return;
uint256 toCreator = (tax * l.creatorFeeBpsSnap) / BPS;
uint256 toProtocol = (tax * l.protocolFeeBpsSnap) / BPS;
uint256 toLp = (tax * lpFeeBpsSnapOf[id]) / BPS;
if (toLp > 0) {
lpEthOf[id] += toLp;
totalEscrowed += toLp;
}
_pushEth(treasury, toProtocol);
uint256 remainder = tax - toCreator - toProtocol - toLp;
if (remainder > 0) {
address card = clockInCard;
if (card != address(0)) {
try IClockInCard(card).punch{value: remainder, gas: 200_000}(id, trader, ref) {
remainder = 0;
} catch {}
}
if (remainder > 0) _pushEth(stockBooster, remainder);
}
_pushCreatorEth(id, l.creator, toCreator);
}
/// @notice Permissionless close: timer expiry, or the graduation mcap
/// reached (a feed move can cross the line without a buy).
function graduate(uint256 id) external nonReentrant {
Launch storage l = _launch(id);
if (!l.armed) revert NotArmed();
if (l.aborted) revert AbortedLaunch();
if (l.graduated) revert AlreadyGraduated();
if (block.timestamp < l.deadline && mcapUsd8(id) < l.gradMcapUsd8) revert NotGraduatable();
_close(id, l);
}
// ------------------------------------------------------------ graduation
function _close(uint256 id, Launch storage l) private {
l.graduated = true;
closedAt[id] = uint64(block.timestamp);
emit CurveClosed(id, l.realEth, l.vToken == 0 ? 0 : Math.mulDiv(l.vEth, 1e18, l.vToken), mcapUsd8(id));
}
/// @notice Bond the raise into the launch's locked up. CL pools.
/// Permissionless once closed; retryable — a floored swap leg reverts
/// here without touching curve state (re-pin the fixed floor / wait out
/// a stale feed and call again).
function bond(uint256 id) external nonReentrant {
Launch storage l = _launch(id);
if (!l.graduated) revert NotClosed();
if (l.bonded) revert AlreadyBonded();
if (l.aborted) revert AbortedLaunch();
l.bonded = true;
_doBond(id, l, _legsOf[id]);
}
/// @notice Emergency rescue — permissionless once RESCUE_DELAY has passed
/// since the curve closed and the primary bond() still hasn't succeeded
/// (a swap leg's route dead at any honest floor, a pre-seed grief nobody
/// pushed back into band, an unpinnable fixed floor with the operator
/// gone…). Bonds the FULL raise + LP reserve into the token/WETH pool
/// alone at the closing price (double depth on the ETH side), locked in
/// the box like any bond. Depends only on WETH, the up. CL stack and the
/// box locker, so the raise can never be stranded — and still can never
/// be extracted. The delay never gates a working bond().
function bondEthOnly(uint256 id) external nonReentrant {
Launch storage l = _launch(id);
if (!l.graduated) revert NotClosed();
if (l.bonded) revert AlreadyBonded();
if (l.aborted) revert AbortedLaunch();
if (block.timestamp < uint256(closedAt[id]) + RESCUE_DELAY) revert RescueTooEarly();
l.bonded = true;
_doBond(id, l, new address[](1)); // single address(0) = raw ETH leg
}
function _doBond(uint256 id, Launch storage l, address[] memory legs) private {
uint256 raise = l.realEth;
uint256 lpFee = lpEthOf[id];
lpEthOf[id] = 0;
totalEscrowed -= raise + lpFee;
IERC20 token = IERC20(l.token);
if (raise == 0) {
// Timer expired with zero net raise: supply home, no pool ever
// exists. Any sell-tax LP residue is fee money -> StockBooster.
if (lpFee > 0) _pushEth(stockBooster, lpFee);
uint256 home = token.balanceOf(address(this));
if (home > 0) token.safeTransfer(l.creator, home);
emit LaunchBonded(id, 0, 0);
return;
}
// Closing curve price as the exact vEth/vToken ratio — never a
// quantized wei-per-1e18 intermediate (which rounds to ZERO above
// ~vEth*1e18 tokens and would brick both bond rails forever).
uint256 vE = l.vEth;
uint256 vT = l.vToken;
uint256 tokenBal = token.balanceOf(address(this));
// The LP fee reserve deepens the pools at the SAME closing price,
// paired with tokens that would otherwise burn — capped at what the
// unsold reserve supports so the pools always open exactly at the
// curve close. Uncoverable excess is fee money (never raise) and
// falls through to StockBooster.
uint256 bondEth = raise + lpFee;
if (lpFee > 0) {
uint256 lpUsed = lpFee;
if (Math.mulDiv(bondEth, vT, vE) > tokenBal) {
uint256 supportable = Math.mulDiv(tokenBal, vE, vT);
lpUsed = supportable > raise ? supportable - raise : 0;
_pushEth(stockBooster, lpFee - lpUsed);
bondEth = raise + lpUsed;
}
emit LpFeeBonded(id, lpUsed, lpFee - lpUsed);
}
uint256 n = legs.length;
uint256 perLegEth = bondEth / n;
// Equal-value token side per leg at the closing curve price; clamp
// defensively to the unsold reserve (rounding).
uint256 tokensPerLeg = Math.mulDiv(perLegEth, vT, vE);
if (tokensPerLeg * n > tokenBal) tokensPerLeg = tokenBal / n;
uint256 ethSpent;
for (uint256 i = 0; i < n; i++) {
address asset = legs[i];
uint256 legEth = i == n - 1 ? bondEth - ethSpent : perLegEth;
ethSpent += legEth;
address pairAsset;
uint256 pairAmount;
if (asset == address(0)) {
weth.deposit{value: legEth}();
pairAsset = address(weth);
pairAmount = legEth;
} else {
pairAsset = asset;
pairAmount = IStonkSwapRouter(quoteLegs[asset].router).swapExactETHForTokens{value: legEth}(
asset, _legFloor(asset, legEth), address(this), block.timestamp
);
}
(address pool, uint256 lockId) = SafeLaunchClLib.mintLockedClPool(
nfpm, clFactory, boxLocker, clTickSpacing, token, pairAsset, pairAmount, tokensPerLeg, l.creator
);
_poolsOf[id].push(pool);
_lockIdsOf[id].push(lockId);
emit LegBonded(id, asset, pool, lockId, legEth);
}
// Leftover launch tokens burn (exclusively this launch's balance).
uint256 burned = token.balanceOf(address(this));
if (burned > 0) token.safeTransfer(DEAD, burned);
emit LaunchBonded(id, raise, burned);
}
/// @dev Swap-leg minOut. Feed assets: oracle mark −10% (fail-closed on
/// stale feeds). Feed-less assets: the owner-pinned fixed floor.
function _legFloor(address asset, uint256 ethIn) private view returns (uint256) {
QuoteLegConfig memory cfg = quoteLegs[asset];
if (cfg.feed != address(0)) {
uint256 assetUsd8 = _feedUsd8(IAggregatorV3(cfg.feed));
uint8 dec = IERC20DecimalsMinimal(asset).decimals();
uint256 expected = Math.mulDiv(ethIn * _ethUsd8(), 10 ** dec, 1e18 * assetUsd8);
return (expected * (BPS - LEG_SLIPPAGE_BPS)) / BPS;
}
uint256 floor = fixedFloorPerEth[asset];
if (floor == 0) revert FloorNotSet();
return Math.mulDiv(ethIn, floor, 1e18);
}
// ------------------------------------------------------ creator ETH legs
/// @dev Creator-bound ETH is push-tolerant: a reverting creator address
/// accrues instead of bricking trades or bond. Permissionless flush.
function _pushCreatorEth(uint256 id, address to, uint256 amount) private {
if (amount == 0) return;
(bool ok,) = to.call{value: amount, gas: 100_000}("");
if (ok) return;
creatorEthOwed[id] += amount;
totalOwed += amount;
emit CreatorEthAccrued(id, amount);
}
function flushCreatorEth(uint256 id) external nonReentrant {
Launch storage l = _launch(id);
uint256 owed = creatorEthOwed[id];
if (owed == 0) revert NothingOwed();
creatorEthOwed[id] = 0;
totalOwed -= owed;
(bool ok,) = l.creator.call{value: owed}("");
if (!ok) revert FeePushFailed();
emit CreatorEthFlushed(id, owed);
}
// ----------------------------------------------------------------- views
function launchCount() external view returns (uint256) {
return _launches.length;
}
function getLaunch(uint256 id) external view returns (Launch memory) {
if (id == 0 || id > _launches.length) revert UnknownLaunch();
return _launches[id - 1];
}
function legsOf(uint256 id) external view returns (address[] memory) {
return _legsOf[id];
}
function poolsOf(uint256 id) external view returns (address[] memory pools, uint256[] memory lockIds) {
return (_poolsOf[id], _lockIdsOf[id]);
}
function currentTaxBps(uint256 id) public view returns (uint256) {
Launch storage l = _launch(id);
if (!l.armed || l.graduated) return 0;
uint256 minutesElapsed = (block.timestamp - l.startTime) / 60;
uint256 decayed = minutesElapsed * l.decayPerMinuteBps;
return decayed >= l.startTaxBps ? 0 : l.startTaxBps - decayed;
}
function mcapUsd8(uint256 id) public view returns (uint256) {
Launch storage l = _launch(id);
if (!l.armed || l.vToken == 0) return 0;
uint256 mcapWei = Math.mulDiv(l.vEth, l.loadedSupply, l.vToken);
return Math.mulDiv(mcapWei, _ethUsd8(), 1e18);
}
/// @notice Curve price, wei per 1e18 token units.
function priceWei(uint256 id) external view returns (uint256) {
Launch storage l = _launch(id);
if (!l.armed || l.vToken == 0) return 0;
return Math.mulDiv(l.vEth, 1e18, l.vToken);
}
function quoteBuy(uint256 id, uint256 ethIn) external view returns (uint256 tokensOut, uint256 taxBps) {
Launch storage l = _launch(id);
taxBps = currentTaxBps(id);
uint256 net = ethIn - (ethIn * taxBps) / BPS;
if (!l.armed || l.graduated || net == 0) return (0, taxBps);
tokensOut = Math.mulDiv(net, l.vToken, l.vEth + net);
}
function quoteSell(uint256 id, address seller, uint256 tokensIn)
external
view
returns (uint256 ethOut, uint256 taxBps)
{
Launch storage l = _launch(id);
taxBps = currentTaxBps(id);
if (!l.armed || l.graduated || !l.sellsEnabled || tokensIn == 0) return (0, taxBps);
if (tokensIn > boughtOf[id][seller]) return (0, taxBps);
uint256 gross = Math.mulDiv(tokensIn, l.vEth, l.vToken + tokensIn);
ethOut = gross - (gross * taxBps) / BPS;
}
/// Everything a UI needs for one launch in ONE eth_call. Unlike the
/// transactional paths this NEVER reverts on a stale/broken ETH feed
/// (weekend feed gaps are normal on this chain): mcap reports 0 with
/// oracleFresh=false and the frontend renders "market closed" instead
/// of a blank grid.
struct LaunchView {
uint256 id;
Launch core;
uint256 taxBps;
uint256 mcapUsd8Now; // 0 when the ETH/USD feed is stale
uint256 tokensSold;
bool oracleFresh;
address[] legs;
address[] pools;
uint256[] lockIds;
uint256 lpEth; // accrued LP fee reserve (deepens locked LP at bond)
uint16 lpFeeBpsSnap;
uint64 closedAtTs; // 0 until the curve closes; starts the rescue clock
}
function viewLaunch(uint256 id) public view returns (LaunchView memory v) {
Launch storage l = _launch(id);
v.id = id;
v.core = l;
v.taxBps = currentTaxBps(id);
(uint256 usd8, bool fresh) = ethUsdView();
v.oracleFresh = fresh;
if (l.armed && l.vToken != 0 && fresh) {
v.mcapUsd8Now = Math.mulDiv(Math.mulDiv(l.vEth, l.loadedSupply, l.vToken), usd8, 1e18);
}
v.tokensSold = l.armed ? l.loadedSupply - l.vToken : 0;
v.legs = _legsOf[id];
v.pools = _poolsOf[id];
v.lockIds = _lockIdsOf[id];
v.lpEth = lpEthOf[id];
v.lpFeeBpsSnap = lpFeeBpsSnapOf[id];
v.closedAtTs = closedAt[id];
}
/// Newest-first page for the grid: startId 0 (or out of range) starts at
/// the latest launch; walks down `count` launches.
function viewLaunches(uint256 startId, uint256 count) external view returns (LaunchView[] memory out) {
uint256 n = _launches.length;
if (n == 0 || count == 0) return new LaunchView[](0);
uint256 from = (startId == 0 || startId > n) ? n : startId;
uint256 len = from < count ? from : count;
out = new LaunchView[](len);
for (uint256 i; i < len; ++i) {
out[i] = viewLaunch(from - i);
}
}
/// Non-reverting ETH/USD read for display surfaces (USD conversion,
/// "market closed" banners). Transactional paths keep the strict
/// reverting `_ethUsd8`.
function ethUsdView() public view returns (uint256 usd8, bool fresh) {
try ethUsdFeed.latestRoundData() returns (uint80, int256 answer, uint256, uint256 updatedAt, uint80) {
if (answer <= 0 || block.timestamp - updatedAt > MAX_PRICE_AGE) return (0, false);
uint8 dec = ethUsdFeed.decimals();
usd8 = dec == 8 ? uint256(answer) : Math.mulDiv(uint256(answer), 1e8, 10 ** dec);
fresh = usd8 > 0;
} catch {
return (0, false);
}
}
// ------------------------------------------------------------- internals
function _launch(uint256 id) private view returns (Launch storage) {
if (id == 0 || id > _launches.length) revert UnknownLaunch();
return _launches[id - 1];
}
function _ethUsd8() private view returns (uint64) {
uint256 usd8 = _feedUsd8(ethUsdFeed);
if (usd8 > type(uint64).max) revert BadParam();
return uint64(usd8);
}
function _feedUsd8(IAggregatorV3 feed) private view returns (uint256 usd8) {
(, int256 answer,, uint256 updatedAt,) = feed.latestRoundData();
if (answer <= 0) revert BadAnswer();
if (block.timestamp - updatedAt > MAX_PRICE_AGE) revert StalePrice();
uint8 dec = feed.decimals();
usd8 = dec == 8 ? uint256(answer) : Math.mulDiv(uint256(answer), 1e8, 10 ** dec);
if (usd8 == 0) revert BadParam();
}
function _pushEth(address to, uint256 amount) private {
if (amount == 0) return;
(bool ok,) = to.call{value: amount}("");
if (!ok) revert FeePushFailed();
}
}[
{
"type": "constructor",
"inputs": [
{
"name": "name_",
"type": "string",
"internalType": "string"
},
{
"name": "symbol_",
"type": "string",
"internalType": "string"
},
{
"name": "supply_",
"type": "uint256",
"internalType": "uint256"
},
{
"name": "to",
"type": "address",
"internalType": "address"
}
],
"stateMutability": "nonpayable"
},
{
"name": "ERC20InsufficientAllowance",
"type": "error",
"inputs": [
{
"name": "spender",
"type": "address",
"internalType": "address"
},
{
"name": "allowance",
"type": "uint256",
"internalType": "uint256"
},
{
"name": "needed",
"type": "uint256",
"internalType": "uint256"
}
]
},
{
"name": "ERC20InsufficientBalance",
"type": "error",
"inputs": [
{
"name": "sender",
"type": "address",
"internalType": "address"
},
{
"name": "balance",
"type": "uint256",
"internalType": "uint256"
},
{
"name": "needed",
"type": "uint256",
"internalType": "uint256"
}
]
},
{
"name": "ERC20InvalidApprover",
"type": "error",
"inputs": [
{
"name": "approver",
"type": "address",
"internalType": "address"
}
]
},
{
"name": "ERC20InvalidReceiver",
"type": "error",
"inputs": [
{
"name": "receiver",
"type": "address",
"internalType": "address"
}
]
},
{
"name": "ERC20InvalidSender",
"type": "error",
"inputs": [
{
"name": "sender",
"type": "address",
"internalType": "address"
}
]
},
{
"name": "ERC20InvalidSpender",
"type": "error",
"inputs": [
{
"name": "spender",
"type": "address",
"internalType": "address"
}
]
},
{
"name": "Approval",
"type": "event",
"inputs": [
{
"name": "owner",
"type": "address",
"indexed": true,
"internalType": "address"
},
{
"name": "spender",
"type": "address",
"indexed": true,
"internalType": "address"
},
{
"name": "value",
"type": "uint256",
"indexed": false,
"internalType": "uint256"
}
],
"anonymous": false
},
{
"name": "Transfer",
"type": "event",
"inputs": [
{
"name": "from",
"type": "address",
"indexed": true,
"internalType": "address"
},
{
"name": "to",
"type": "address",
"indexed": true,
"internalType": "address"
},
{
"name": "value",
"type": "uint256",
"indexed": false,
"internalType": "uint256"
}
],
"anonymous": false
},
{
"name": "allowance",
"type": "function",
"inputs": [
{
"name": "owner",
"type": "address",
"internalType": "address"
},
{
"name": "spender",
"type": "address",
"internalType": "address"
}
],
"outputs": [
{
"name": "",
"type": "uint256",
"internalType": "uint256"
}
],
"stateMutability": "view"
},
{
"name": "approve",
"type": "function",
"inputs": [
{
"name": "spender",
"type": "address",
"internalType": "address"
},
{
"name": "value",
"type": "uint256",
"internalType": "uint256"
}
],
"outputs": [
{
"name": "",
"type": "bool",
"internalType": "bool"
}
],
"stateMutability": "nonpayable"
},
{
"name": "balanceOf",
"type": "function",
"inputs": [
{
"name": "account",
"type": "address",
"internalType": "address"
}
],
"outputs": [
{
"name": "",
"type": "uint256",
"internalType": "uint256"
}
],
"stateMutability": "view"
},
{
"name": "decimals",
"type": "function",
"inputs": [],
"outputs": [
{
"name": "",
"type": "uint8",
"internalType": "uint8"
}
],
"stateMutability": "view"
},
{
"name": "name",
"type": "function",
"inputs": [],
"outputs": [
{
"name": "",
"type": "string",
"internalType": "string"
}
],
"stateMutability": "view"
},
{
"name": "symbol",
"type": "function",
"inputs": [],
"outputs": [
{
"name": "",
"type": "string",
"internalType": "string"
}
],
"stateMutability": "view"
},
{
"name": "totalSupply",
"type": "function",
"inputs": [],
"outputs": [
{
"name": "",
"type": "uint256",
"internalType": "uint256"
}
],
"stateMutability": "view"
},
{
"name": "transfer",
"type": "function",
"inputs": [
{
"name": "to",
"type": "address",
"internalType": "address"
},
{
"name": "value",
"type": "uint256",
"internalType": "uint256"
}
],
"outputs": [
{
"name": "",
"type": "bool",
"internalType": "bool"
}
],
"stateMutability": "nonpayable"
},
{
"name": "transferFrom",
"type": "function",
"inputs": [
{
"name": "from",
"type": "address",
"internalType": "address"
},
{
"name": "to",
"type": "address",
"internalType": "address"
},
{
"name": "value",
"type": "uint256",
"internalType": "uint256"
}
],
"outputs": [
{
"name": "",
"type": "bool",
"internalType": "bool"
}
],
"stateMutability": "nonpayable"
}
]0x608060408181526004918236101561001657600080fd5b600092833560e01c91826306fdde031461041e57508163095ea7b31461037057816318160ddd1461035157816323b872dd1461025a578163313ce5671461023e57816370a082311461020757816395d89b411461010357508063a9059cbb146100d35763dd62ed3e1461008857600080fd5b346100cf57806003193601126100cf57806020926100a4610524565b6100ac61053f565b6001600160a01b0391821683526001865283832091168252845220549051908152f35b5080fd5b50346100cf57806003193601126100cf576020906100fc6100f2610524565b6024359033610555565b5160018152f35b8383346100cf57816003193601126100cf5780519082845460018160011c90600183169283156101fd575b60209384841081146101ea578388529081156101ce5750600114610196575b505050829003601f01601f191682019267ffffffffffffffff841183851017610183575082918261017f9252826104db565b0390f35b634e487b7160e01b815260418552602490fd5b919250868652828620918387935b8385106101ba575050505083010185808061014d565b8054888601830152930192849082016101a4565b60ff1916878501525050151560051b840101905085808061014d565b634e487b7160e01b895260228a52602489fd5b91607f169161012e565b5050346100cf5760203660031901126100cf5760209181906001600160a01b0361022f610524565b16815280845220549051908152f35b5050346100cf57816003193601126100cf576020905160128152f35b9050823461034e57606036600319011261034e57610276610524565b61027e61053f565b916044359360018060a01b0383168083526001602052868320338452602052868320549160001983106102ba575b6020886100fc898989610555565b86831061032257811561030b5733156102f4575082526001602090815286832033845281529186902090859003905582906100fc876102ac565b8751634a1406b160e11b8152908101849052602490fd5b875163e602df0560e01b8152908101849052602490fd5b8751637dc7a0d960e11b8152339181019182526020820193909352604081018790528291506060010390fd5b80fd5b5050346100cf57816003193601126100cf576020906002549051908152f35b90503461041a578160031936011261041a5761038a610524565b602435903315610403576001600160a01b03169182156103ec57508083602095338152600187528181208582528752205582519081527f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925843392a35160018152f35b8351634a1406b160e11b8152908101859052602490fd5b835163e602df0560e01b8152808401869052602490fd5b8280fd5b8490843461041a578260031936011261041a578260035460018160011c90600183169283156104d1575b60209384841081146101ea578388529081156101ce575060011461049857505050829003601f01601f191682019267ffffffffffffffff841183851017610183575082918261017f9252826104db565b91925060038652828620918387935b8385106104bd575050505083010185808061014d565b8054888601830152930192849082016104a7565b91607f1691610448565b6020808252825181830181905290939260005b82811061051057505060409293506000838284010152601f8019910116010190565b8181018601518482016040015285016104ee565b600435906001600160a01b038216820361053a57565b600080fd5b602435906001600160a01b038216820361053a57565b916001600160a01b0380841692831561061a571692831561060157600090838252816020526040822054908382106105cf575091604082827fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef958760209652828652038282205586815220818154019055604051908152a3565b60405163391434e360e21b81526001600160a01b03919091166004820152602481019190915260448101839052606490fd5b60405163ec442f0560e01b815260006004820152602490fd5b604051634b637e8f60e11b815260006004820152602490fdfea2646970667358221220c28421c841edd10a185c2caa3bedc4c23b36b89c91d6aeb6f6f6b9ed19d7f9f764736f6c63430008180033
| Token | Symbol | Balance | Price | Value |
|---|---|---|---|---|
| no token holdings | ||||
| Type | Age | Block | Details |
|---|---|---|---|
| no cross-chain L1↔L2 transactions for this address | |||
| Transaction Hash | Method | Block | Age | From | To | Type | Item | ||
|---|---|---|---|---|---|---|---|---|---|
| no NFT transfers for this address yet | |||||||||
| Txn Hash | Age | Event | Topics / Data |
|---|---|---|---|
| 0x3675f0…30a571 | 6 hrs agoTue, 18 Aug 2026 03:14:43 UTC | Transfer | [0] 0x000000000000…5d1bea41 [1] 0x000000000000…fd0fb6a1 data: 0x000000000000000000…7cb5a3d0 |
| 0x3516cd…258a12 | 7 hrs agoTue, 18 Aug 2026 03:06:17 UTC | Transfer | [0] 0x000000000000…a9f8e4bf [1] 0x000000000000…5d1bea41 data: 0x000000000000000000…5b2bf5c0 |
| 0x3516cd…258a12 | 7 hrs agoTue, 18 Aug 2026 03:06:17 UTC | Approval | [0] 0x000000000000…a9f8e4bf [1] 0x000000000000…b3ab9415 data: 0x000000000000000000…5b2bf5c0 |
| 0x3516cd…258a12 | 7 hrs agoTue, 18 Aug 2026 03:06:17 UTC | Transfer | [0] 0x000000000000…ce459273 [1] 0x000000000000…a9f8e4bf data: 0x000000000000000000…5b2bf5c0 |
| 0x8c05dd…178842 | 7 hrs agoTue, 18 Aug 2026 03:01:23 UTC | Transfer | [0] 0x000000000000…ec3f9923 [1] 0x000000000000…5d1bea41 data: 0x000000000000000000…99a223c3 |
| 0xe91874…cff372 | 7 hrs agoTue, 18 Aug 2026 03:01:10 UTC | Approval | [0] 0x000000000000…ec3f9923 [1] 0x000000000000…b3ab9415 data: 0x000000000000000000…99a223c3 |
| 0xb13a97…9031b2 | 7 hrs agoTue, 18 Aug 2026 03:00:49 UTC | Approval | [0] 0x000000000000…ce459273 [1] 0x000000000000…a9f8e4bf data: 0xffffffffffffffffff…ffffffff |
| 0xdbe1cc…165d79 | 7 hrs agoTue, 18 Aug 2026 03:00:29 UTC | Transfer | [0] 0x000000000000…5d1bea41 [1] 0x000000000000…ec3f9923 data: 0x000000000000000000…99a223c3 |
| 0x67afe5…d9b333 | 7 hrs agoTue, 18 Aug 2026 02:59:50 UTC | Transfer | [0] 0x000000000000…016613d3 [1] 0x000000000000…5d1bea41 data: 0x000000000000000000…94a318dd |
| 0x3b3463…3ca822 | 7 hrs agoTue, 18 Aug 2026 02:59:50 UTC | Approval | [0] 0x000000000000…016613d3 [1] 0x000000000000…b3ab9415 data: 0xffffffffffffffffff…ffffffff |
| 0xd42872…aa21c7 | 7 hrs agoTue, 18 Aug 2026 02:58:37 UTC | Transfer | [0] 0x000000000000…a91d71f9 [1] 0x000000000000…0000dead data: 0x000000000000000000…3ce58bac |
| 0xd42872…aa21c7 | 7 hrs agoTue, 18 Aug 2026 02:58:37 UTC | Transfer | [0] 0x000000000000…a91d71f9 [1] 0x000000000000…5d1bea41 data: 0x000000000000000000…47883827 |
| 0xd42872…aa21c7 | 7 hrs agoTue, 18 Aug 2026 02:58:37 UTC | Approval | [0] 0x000000000000…a91d71f9 [1] 0x000000000000…f0eedcef data: 0x000000000000000000…47884fc8 |
| 0xd712cf…0955f3 | 7 hrs agoTue, 18 Aug 2026 02:53:03 UTC | Transfer | [0] 0x000000000000…a91d71f9 [1] 0x000000000000…ce459273 data: 0x000000000000000000…5b2bf5c0 |
| 0x64a1c2…5b03d0 | 7 hrs agoTue, 18 Aug 2026 02:45:02 UTC | Transfer | [0] 0x000000000000…43a75ba9 [1] 0x000000000000…43e40951 data: 0x000000000000000000…d9f472a3 |
| 0x650077…9fb949 | 7 hrs agoTue, 18 Aug 2026 02:44:56 UTC | Approval | [0] 0x000000000000…43a75ba9 [1] 0x000000000000…3ac78ba3 data: 0xffffffffffffffffff…ffffffff |
| 0x040958…7e3a1e | 7 hrs agoTue, 18 Aug 2026 02:44:46 UTC | Transfer | [0] 0x000000000000…a91d71f9 [1] 0x000000000000…43a75ba9 data: 0x000000000000000000…e36a9960 |
| 0xcffc27…aa85a7 | 7 hrs agoTue, 18 Aug 2026 02:43:38 UTC | Transfer | [0] 0x000000000000…a91d71f9 [1] 0x000000000000…8c595f8d data: 0x000000000000000000…b29ef166 |
| 0x88dcb4…6301a1 | 8 hrs agoTue, 18 Aug 2026 01:25:07 UTC | Transfer | [0] 0x000000000000…a91d71f9 [1] 0x000000000000…c66edd28 data: 0x000000000000000000…ddb9a2ca |
| 0xd30a8a…2c84d6 | 8 hrs agoTue, 18 Aug 2026 01:20:19 UTC | Transfer | [0] 0x000000000000…a91d71f9 [1] 0x000000000000…016613d3 data: 0x000000000000000000…94a318dd |
| 0x08921c…fcfb5b | 8 hrs agoTue, 18 Aug 2026 01:19:18 UTC | Transfer | [0] 0x000000000000…87d4374e [1] 0x000000000000…a91d71f9 data: 0x000000000000000000…e8000000 |
| 0x627690…8cbb46 | 8 hrs agoTue, 18 Aug 2026 01:18:52 UTC | Approval | [0] 0x000000000000…87d4374e [1] 0x000000000000…a91d71f9 data: 0x000000000000000000…e8000000 |
| 0x378fff…7a29c5 | 8 hrs agoTue, 18 Aug 2026 01:18:18 UTC | Transfer | [0] 0x000000000000…00000000 [1] 0x000000000000…87d4374e data: 0x000000000000000000…e8000000 |
| Transaction Hash | Method | Block | Age | From | To | Amount | Token | ||
|---|---|---|---|---|---|---|---|---|---|
| no token transfers for this address yet | |||||||||
| Transaction Hash | Method ? | Block | Age | From | To | Amount | Txn Fee ? | ||
|---|---|---|---|---|---|---|---|---|---|
| 0xe91874…cff372 | Approve | 39,359,064 | 7 hrs agoTue, 18 Aug 2026 03:01:10 UTC | 0x7762…9923 | IN | MR STONK | $0.000 ETH | 0.00000094 | |
| 0xb13a97…9031b2 | Approve | 39,358,856 | 7 hrs agoTue, 18 Aug 2026 03:00:49 UTC | 0xc44f…9273 | IN | MR STONK | $0.000 ETH | 0.00000093 | |
| 0x3b3463…3ca822 | Approve | 39,358,274 | 7 hrs agoTue, 18 Aug 2026 02:59:50 UTC | 0x9a1e…13d3 | IN | MR STONK | $0.000 ETH | 0.00000094 | |
| 0x650077…9fb949 | Approve | 39,349,356 | 7 hrs agoTue, 18 Aug 2026 02:44:56 UTC | 0x6b6f…5ba9 | IN | MR STONK | $0.000 ETH | 0.00000092 | |
| 0x627690…8cbb46 | Approve | 39,297,846 | 8 hrs agoTue, 18 Aug 2026 01:18:52 UTC | 0x6aee…374e | IN | MR STONK | $0.000 ETH | 0.00000092 |
| Block | Age | Parent Transaction Hash | Type | Method | From | To | Value | |
|---|---|---|---|---|---|---|---|---|
| 39,297,510 | 8 hrs agoTue, 18 Aug 2026 01:18:18 UTC | 0x378fff…7a29c5 | CREATE2 | createLaunch | 0xeca5…71f9 | IN | 0x8723…700c | 0 ETH |