// SPDX-License-Identifier: MIT
pragma solidity 0.8.24;
// Seeded launch for Robinhood Chain — LaunchHood-style "free" launches where the
// creator provides NO liquidity ETH. Instead, the ENTIRE fixed 1B supply is
// deposited into a V3-style concentrated-liquidity pool (1% fee tier) as a
// single-sided position at a fixed ~1.35 ETH starting market cap. Buyers' ETH
// fills the pool as the price climbs.
//
// VENUE-AGNOSTIC: the factory speaks only the canonical V3 interfaces (position
// manager + pool), so the SAME source deploys against Uniswap V3 or SushiSwap
// V3 (CLAMM) — the position manager passed to the constructor decides the
// venue. The dev buy trades DIRECTLY against the pool via the standard
// uniswapV3SwapCallback (Sushi's CLAMM pools are byte-identical V3 pools), so
// no venue swap-router is needed at all — Sushi has no SwapRouter02 deployment
// on Robinhood Chain, swaps there route through their RouteProcessor.
//
// Safety by construction:
// - SeededToken is a plain ERC-20: fixed supply, no owner, no tax, no hooks,
// no blacklist/pause/mint — nothing for scanners to flag.
// - The LP NFT is locked FOREVER in LpLocker, which has no function to remove
// liquidity or transfer the position out. It can only collect() swap fees.
// - Every trade pays the pool's 1% fee, which accrues to the locked position.
// collect() pays fees 100% to the token's creator wallet, with WETH
// unwrapped to native ETH. Only the creator can trigger their own collect(),
// and the payout address is fixed at launch — it cannot be changed.
// - Tokens deploy via CREATE2 off a caller-random salt so their addresses are
// unpredictable in advance: nobody can pre-initialize a token's Uniswap
// pool at a hostile price to grief the single-sided mint (and the probe
// loop walks past any taken candidate, so launches can never be bricked).
//
// launchSeeded() does everything in ONE transaction: deploy token -> create +
// initialize the V3 pool at the fixed starting price -> mint the full-supply
// single-sided position -> lock the NFT -> optional dev buy for the creator ->
// refund dust. The factory holds no funds after the call.
interface IERC20Minimal {
function transfer(address to, uint256 amount) external returns (bool);
function balanceOf(address account) external view returns (uint256);
}
interface IWETH9 {
function withdraw(uint256 amount) external;
function deposit() external payable;
}
interface IV3PoolMinimal {
function swap(
address recipient,
bool zeroForOne,
int256 amountSpecified,
uint160 sqrtPriceLimitX96,
bytes calldata data
) external returns (int256 amount0, int256 amount1);
}
interface INonfungiblePositionManager {
struct MintParams {
address token0;
address token1;
uint24 fee;
int24 tickLower;
int24 tickUpper;
uint256 amount0Desired;
uint256 amount1Desired;
uint256 amount0Min;
uint256 amount1Min;
address recipient;
uint256 deadline;
}
struct CollectParams {
uint256 tokenId;
address recipient;
uint128 amount0Max;
uint128 amount1Max;
}
function createAndInitializePoolIfNecessary(
address token0,
address token1,
uint24 fee,
uint160 sqrtPriceX96
) external payable returns (address pool);
function mint(MintParams calldata params)
external
payable
returns (uint256 tokenId, uint128 liquidity, uint256 amount0, uint256 amount1);
function collect(CollectParams calldata params)
external
payable
returns (uint256 amount0, uint256 amount1);
function positions(uint256 tokenId)
external
view
returns (
uint96 nonce,
address operator,
address token0,
address token1,
uint24 fee,
int24 tickLower,
int24 tickUpper,
uint128 liquidity,
uint256 feeGrowthInside0LastX128,
uint256 feeGrowthInside1LastX128,
uint128 tokensOwed0,
uint128 tokensOwed1
);
function safeTransferFrom(address from, address to, uint256 tokenId, bytes calldata data) external;
}
interface IPeripheryImmutableState {
function factory() external view returns (address);
}
interface IUniswapV3FactoryMinimal {
function getPool(address tokenA, address tokenB, uint24 fee) external view returns (address);
}
/// Plain ERC-20 with a fixed 1B supply minted to the deployer (the factory) and
/// an immutable on-chain metadata link. No owner, no tax, no transfer logic —
/// V3 pools reject fee-on-transfer tokens, and scanners have nothing to flag.
contract SeededToken {
string private _name;
string private _symbol;
uint8 public constant decimals = 18;
uint256 public constant totalSupply = 1_000_000_000 * 10 ** 18;
// Permanent link (ipfs://… or https://…) to a JSON holding the logo,
// description and socials — set once at deploy, never changeable.
string public metadataURI;
mapping(address => uint256) private _balances;
mapping(address => mapping(address => uint256)) private _allowances;
event Transfer(address indexed from, address indexed to, uint256 value);
event Approval(address indexed owner, address indexed spender, uint256 value);
constructor(string memory name_, string memory symbol_, string memory metadataURI_) {
_name = name_;
_symbol = symbol_;
metadataURI = metadataURI_;
_balances[msg.sender] = totalSupply;
emit Transfer(address(0), msg.sender, totalSupply);
}
function name() external view returns (string memory) {
return _name;
}
function symbol() external view returns (string memory) {
return _symbol;
}
function balanceOf(address account) external view returns (uint256) {
return _balances[account];
}
function allowance(address holder, address spender) external view returns (uint256) {
return _allowances[holder][spender];
}
function approve(address spender, uint256 amount) external returns (bool) {
_approve(msg.sender, spender, amount);
return true;
}
function transfer(address to, uint256 amount) external returns (bool) {
_transfer(msg.sender, to, amount);
return true;
}
function transferFrom(address from, address to, uint256 amount) external returns (bool) {
uint256 allowed = _allowances[from][msg.sender];
if (allowed != type(uint256).max) {
require(allowed >= amount, "Insufficient allowance");
_approve(from, msg.sender, allowed - amount);
}
_transfer(from, to, amount);
return true;
}
function _approve(address holder, address spender, uint256 amount) private {
require(holder != address(0) && spender != address(0), "Zero address");
_allowances[holder][spender] = amount;
emit Approval(holder, spender, amount);
}
function _transfer(address from, address to, uint256 amount) private {
require(from != address(0) && to != address(0), "Zero address");
require(_balances[from] >= amount, "Insufficient balance");
_balances[from] -= amount;
_balances[to] += amount;
emit Transfer(from, to, amount);
}
}
/// Holds seeded-launch LP NFTs forever. There is deliberately NO function that
/// can decrease liquidity or move a position out — principal is locked for good.
/// collect() forwards accrued swap fees 100% to the position's creator,
/// unwrapping WETH to native ETH, and only the creator can call it.
contract LpLocker {
INonfungiblePositionManager public immutable positionManager;
address public immutable weth;
address public immutable protocol;
uint256 public constant CREATOR_BPS = 10000; // 100% to the creator
// tokenId => the launch creator who earns the fee share.
mapping(uint256 => address) public creatorOf;
// Optional fee redirect: tokenId => wallet that receives the creator's fee share
// instead of the creator. 0 = pay the creator directly. Only the creator can
// set/change it (setFeeRecipient), anytime. The charity share is unaffected.
mapping(uint256 => address) public feeRecipientOf;
// Opt-in charity donation, chosen at launch and IMMUTABLE. On every collect(),
// the creator's chosen share (basis points, applied to the WETH/ETH side of the
// fee only) is forwarded to the charity treasury; the token-side fee always
// stays 100% the creator's. charityOf/donateBpsOf are 0 for tokens that didn't
// opt in. The treasury bridges to mainnet and donates to the chosen charity
// (id -> charity is off-chain and published).
address public constant charityTreasury = 0x03718934fED399C02adDccf6dcEb19999e13E524;
mapping(uint256 => uint8) public charityOf;
mapping(uint256 => uint16) public donateBpsOf;
// Charity ETH whose forward failed (e.g. treasury paused/reverting) — held in
// the locker and retriable via flushCharity(), so a bad treasury can NEVER
// brick a creator's own fee claim.
uint256 public charityPending;
bool private _entered;
event Locked(uint256 indexed tokenId, address indexed creator);
event FeesClaimed(uint256 indexed tokenId, uint256 amount0, uint256 amount1);
event CharityFeeDonated(uint256 indexed tokenId, uint8 indexed charityId, uint256 ethAmount);
event CharityFlushed(uint256 ethAmount);
event FeeRecipientSet(uint256 indexed tokenId, address indexed recipient);
constructor(address positionManager_, address weth_, address protocol_) {
require(positionManager_ != address(0) && weth_ != address(0), "Zero address");
require(protocol_ != address(0), "Zero protocol");
positionManager = INonfungiblePositionManager(positionManager_);
weth = weth_;
protocol = protocol_;
}
/// Accepts LP NFTs from the position manager only. The creator address rides
/// in `data`, set by the factory during launchSeeded().
function onERC721Received(address, address, uint256 tokenId, bytes calldata data)
external
returns (bytes4)
{
require(msg.sender == address(positionManager), "Only position manager");
(address creator, uint8 charityId, uint16 donateBps) = abi.decode(data, (address, uint8, uint16));
require(creator != address(0), "Zero creator");
require(donateBps <= 10000, "Bps > 100%");
creatorOf[tokenId] = creator;
// record a donation only when the creator actually opted in
if (charityId != 0 && donateBps > 0) {
charityOf[tokenId] = charityId;
donateBpsOf[tokenId] = donateBps;
}
emit Locked(tokenId, creator);
return this.onERC721Received.selector;
}
/// Collect all fees the position has earned and pay the creator's share to the
/// fee recipient (the creator, or a wallet they redirected to via setFeeRecipient),
/// WETH unwrapped to native ETH, minus the optional charity share. Callable only
/// by the creator or the current recipient — no unrelated third party can move
/// anything from the locked position.
function collect(uint256 tokenId) external returns (uint256 amount0, uint256 amount1) {
require(!_entered, "Reentrant");
_entered = true;
address creator = creatorOf[tokenId];
require(creator != address(0), "Unknown position");
// the creator's share goes to the redirect wallet if one is set, else the creator
address recipient = feeRecipientOf[tokenId];
if (recipient == address(0)) recipient = creator;
// callable by the creator OR the current recipient (e.g. a treasury they redirected to)
require(msg.sender == creator || msg.sender == recipient, "Not authorized");
(, , address token0, address token1, , , , , , , , ) = positionManager.positions(tokenId);
(amount0, amount1) = positionManager.collect(
INonfungiblePositionManager.CollectParams(
tokenId, address(this), type(uint128).max, type(uint128).max
)
);
// Opt-in charity split (ETH-side only): the creator's chosen share of the
// WETH fee is forwarded to the charity treasury; the token-side fee stays
// 100% the creator's (paid to `recipient`). bps is 0 for tokens that never opted in.
uint16 bps = donateBpsOf[tokenId];
uint256 donated = _payout(token0, amount0, recipient, token0 == weth ? bps : 0)
+ _payout(token1, amount1, recipient, token1 == weth ? bps : 0);
emit FeesClaimed(tokenId, amount0, amount1);
if (donated > 0) emit CharityFeeDonated(tokenId, charityOf[tokenId], donated);
_entered = false;
}
/// Redirect this position's creator fee share to any wallet. Only the launch
/// creator can set or change it, anytime (set it to your own address to reset).
/// The charity share, if any, is unaffected — it still goes to the charity. Does
/// not touch already-accrued fees, only where future collect()s pay out.
function setFeeRecipient(uint256 tokenId, address recipient) external {
require(msg.sender == creatorOf[tokenId], "Only creator");
require(recipient != address(0), "Zero recipient");
feeRecipientOf[tokenId] = recipient;
emit FeeRecipientSet(tokenId, recipient);
}
/// Retry any charity ETH whose forward failed during collect() (treasury was
/// paused/reverting at the time). Permissionless so the donation can never be
/// stalled by a single party, and it can only ever move funds to the fixed
/// treasury constant.
function flushCharity() external {
uint256 amt = charityPending;
require(amt > 0, "None pending");
charityPending = 0;
(bool ok, ) = payable(charityTreasury).call{value: amt}("");
require(ok, "Charity send failed");
emit CharityFlushed(amt);
}
/// Splits one token's collected fee: protocol cut (currently 0), then the
/// creator's chosen charity share carved from the creator's portion, the rest
/// to the creator. Returns the ETH sent to charity (nonzero only for the WETH
/// side), for the CharityFeeDonated ledger.
function _payout(address token, uint256 amount, address recipient, uint16 charityBps)
private
returns (uint256 ethToCharity)
{
if (amount == 0) return 0;
uint256 creatorGross = (amount * CREATOR_BPS) / 10000;
uint256 protocolAmt = amount - creatorGross;
uint256 charityAmt = charityBps > 0 ? (creatorGross * charityBps) / 10000 : 0;
uint256 creatorAmt = creatorGross - charityAmt; // the creator's share, paid to `recipient`
if (token == weth) {
IWETH9(weth).withdraw(amount);
_sendEth(recipient, creatorAmt);
_sendEth(protocol, protocolAmt);
if (charityAmt > 0) {
// Best-effort: a reverting or paused treasury must NEVER revert the
// creator's payout. On failure the ETH stays in the locker as
// charityPending, retriable by anyone via flushCharity().
(bool sent, ) = payable(charityTreasury).call{value: charityAmt}("");
if (sent) ethToCharity = charityAmt;
else charityPending += charityAmt;
}
} else {
if (creatorAmt > 0) require(IERC20Minimal(token).transfer(recipient, creatorAmt), "Token transfer failed");
if (protocolAmt > 0) require(IERC20Minimal(token).transfer(protocol, protocolAmt), "Token transfer failed");
// INVARIANT: charity applies to the WETH/ETH side only, so callers pass
// charityBps=0 here and charityAmt is always 0 (this line never runs). If
// that ever changes, make this best-effort like the WETH branch above so
// a failing transfer can't brick the creator's collect().
if (charityAmt > 0) require(IERC20Minimal(token).transfer(charityTreasury, charityAmt), "Token transfer failed");
}
}
function _sendEth(address to, uint256 amount) private {
if (amount == 0) return;
(bool ok, ) = payable(to).call{value: amount}("");
require(ok, "ETH send failed");
}
// Receives ETH from WETH.withdraw() during collect().
receive() external payable {}
}
/// One-transaction seeded launch: the creator pays only gas (plus an optional
/// dev buy). All 1B tokens become single-sided V3 liquidity at a fixed ~1.35 ETH
/// starting market cap, and the LP NFT is locked forever in the LpLocker.
contract SeededFactory {
uint24 public constant POOL_FEE = 10000; // 1% tier, tick spacing 200
address public constant DEAD = 0x000000000000000000000000000000000000dEaD;
// Starting price for 1B tokens at ~1.35 ETH market cap, precomputed for both
// token/WETH address orderings (price ratio 1.35e18 / 1e27 and its inverse).
uint160 private constant SQRT_PRICE_TOKEN0 = 2911028571273737778800966;
uint160 private constant SQRT_PRICE_TOKEN1 = 2156317460202768725037752981024508;
// Single-sided range: from the first usable tick above the starting price up
// to the max usable tick (mirrored when the token is token1). First fill
// lands at ~1.356 ETH market cap and the price can run unbounded, the same
// structure ape.store uses for its Robinhood launches (their observed range
// is [-208200, 887200] on the 1% tier). A capped band would put a hard
// ceiling on the token's price once its tokens sold out, so we match the
// open-ended range instead.
int24 private constant TICK_LOWER_TOKEN0 = -204200;
int24 private constant TICK_UPPER_TOKEN0 = 887200;
int24 private constant TICK_LOWER_TOKEN1 = -887200;
int24 private constant TICK_UPPER_TOKEN1 = 204200;
// How many CREATE2 candidates launchSeeded() probes before giving up. In
// normal operation the first candidate is clean; the loop only walks under
// an active griefing attack (see launchSeeded).
uint256 private constant MAX_SALT_TRIES = 64;
// Global V3 sqrt price bounds (TickMath.MIN_SQRT_RATIO / MAX_SQRT_RATIO),
// used as "no limit" bounds for the dev-buy swap.
uint160 private constant MIN_SQRT_RATIO = 4295128739;
uint160 private constant MAX_SQRT_RATIO = 1461446703485210103287273052203988822378723970342;
INonfungiblePositionManager public immutable positionManager;
address public immutable weth;
address public immutable v3Factory;
LpLocker public immutable locker;
// Set only while the dev-buy swap is in flight so the pool callback can be
// authenticated. Always cleared before launchSeeded returns.
address private _devBuyPool;
struct LaunchInfo {
address pool;
uint256 tokenId;
address creator;
}
// token address => its pool, locked position id, and creator.
mapping(address => LaunchInfo) public launches;
event SeededLaunched(
address indexed token,
address indexed creator,
address pool,
uint256 tokenId,
uint256 devBuyEth,
uint256 devTokens
);
constructor(
address positionManager_,
address weth_,
address protocolFeeRecipient_
) {
require(positionManager_ != address(0), "Zero address");
require(weth_ != address(0), "Zero WETH");
positionManager = INonfungiblePositionManager(positionManager_);
weth = weth_;
v3Factory = IPeripheryImmutableState(positionManager_).factory();
locker = new LpLocker(positionManager_, weth_, protocolFeeRecipient_);
}
/// Launch a seeded token. msg.value covers only the optional dev buy — no
/// liquidity ETH is needed. Any excess is refunded.
///
/// `salt`: pass fresh RANDOM bytes each call. The token deploys via CREATE2
/// off this salt, which makes its address unpredictable until the tx is
/// public. That matters because Uniswap lets anyone create + initialize a
/// pool for a token that does not exist yet: with a plain CREATE deploy the
/// factory's next token address is a pure function of its nonce, so a
/// griefer could pre-initialize that pool at a hostile price and make the
/// single-sided mint revert — and since the revert rolls the nonce back,
/// the SAME poisoned address would be retried forever, bricking every
/// future launch for the cost of one transaction. With a random salt the
/// address can't be pre-poisoned; if a candidate is somehow taken, the
/// probe loop walks to the next free one, and in the worst case the caller
/// just retries with a new salt. (Defense adapted from PotatoPad, MIT.)
/// `charityId` / `donateBps`: opt-in charity donation. charityId 0 (or bps 0) =
/// off. When set, donateBps (basis points, max 10000) of the WETH fee is
/// forwarded to the charity treasury on every collect() — immutable once locked.
function launchSeeded(
string calldata name,
string calldata symbol,
string calldata metadataURI,
uint256 devBuyEth,
bytes32 salt,
uint8 charityId,
uint16 donateBps
) external payable returns (address token) {
require(msg.value >= devBuyEth, "Insufficient ETH for dev buy");
require(donateBps <= 10000, "Bps > 100%");
// 1. Find a CREATE2 address with no pre-existing pool and no code, then
// deploy the token there — full supply lands on this factory.
SeededToken t = new SeededToken{salt: _findSalt(name, symbol, metadataURI, salt)}(
name, symbol, metadataURI
);
token = address(t);
// 2 + 3. Create + initialize the pool at the fixed starting price and
// mint the full supply as a single-sided position. The candidate was
// vetted in _findSalt, so the pool is fresh and only we initialize it.
(address pool, uint256 tokenId) = _seed(t);
// 4. Lock the position forever; the locker records the creator so fee
// claims pay out 100% to the creator.
positionManager.safeTransferFrom(address(this), address(locker), tokenId, abi.encode(msg.sender, charityId, donateBps));
// Rounding dust the position couldn't take is burned so the pool backs
// effectively 100% of supply.
uint256 dust = t.balanceOf(address(this));
if (dust > 0) t.transfer(DEAD, dust);
// 5. Optional dev buy — the creator gets the guaranteed first fill.
uint256 devTokens = devBuyEth > 0 ? _devBuy(token, pool, devBuyEth) : 0;
launches[token] = LaunchInfo(pool, tokenId, msg.sender);
// 6. Refund any leftover ETH — the factory keeps nothing.
uint256 leftover = address(this).balance;
if (leftover > 0) {
(bool ok, ) = payable(msg.sender).call{value: leftover}("");
require(ok, "Refund failed");
}
emit SeededLaunched(token, msg.sender, pool, tokenId, devBuyEth, devTokens);
}
/// Walk CREATE2 candidates (seeded from the caller + their random salt)
/// until one has no Uniswap pool and no code — the address the token will
/// actually deploy to. Reverts only if a griefer has poisoned all
/// MAX_SALT_TRIES candidates for this exact salt; a fresh random salt gives
/// an entirely new candidate set, so no launch can be permanently bricked.
function _findSalt(
string calldata name,
string calldata symbol,
string calldata metadataURI,
bytes32 salt
) private view returns (bytes32) {
bytes32 initCodeHash = keccak256(
abi.encodePacked(type(SeededToken).creationCode, abi.encode(name, symbol, metadataURI))
);
uint256 seed = uint256(keccak256(abi.encode(msg.sender, salt)));
for (uint256 tries = 0; tries < MAX_SALT_TRIES; ) {
address predicted = address(
uint160(uint256(keccak256(abi.encodePacked(bytes1(0xff), address(this), bytes32(seed), initCodeHash))))
);
if (
IUniswapV3FactoryMinimal(v3Factory).getPool(predicted, weth, POOL_FEE) == address(0)
&& predicted.code.length == 0
) return bytes32(seed);
unchecked {
++tries;
++seed;
}
}
revert("Launch griefed, retry with a new salt");
}
function _seed(SeededToken t) private returns (address pool, uint256 tokenId) {
uint256 supply = t.totalSupply();
bool tokenIs0 = address(t) < weth;
(address token0, address token1) = tokenIs0 ? (address(t), weth) : (weth, address(t));
pool = positionManager.createAndInitializePoolIfNecessary(
token0, token1, POOL_FEE, tokenIs0 ? SQRT_PRICE_TOKEN0 : SQRT_PRICE_TOKEN1
);
t.approve(address(positionManager), supply);
uint128 liquidity;
uint256 amount0;
uint256 amount1;
(tokenId, liquidity, amount0, amount1) = positionManager.mint(
INonfungiblePositionManager.MintParams({
token0: token0,
token1: token1,
fee: POOL_FEE,
tickLower: tokenIs0 ? TICK_LOWER_TOKEN0 : TICK_LOWER_TOKEN1,
tickUpper: tokenIs0 ? TICK_UPPER_TOKEN0 : TICK_UPPER_TOKEN1,
amount0Desired: tokenIs0 ? supply : 0,
amount1Desired: tokenIs0 ? 0 : supply,
amount0Min: 0,
amount1Min: 0,
recipient: address(this),
deadline: block.timestamp
})
);
// Defense-in-depth: the seed must be pure token — zero WETH consumed,
// real liquidity, and effectively the whole supply deployed. The CREATE2
// vetting above means a poisoned pool can't reach here, but if one ever
// did, revert rather than produce a broken launch.
uint256 wethUsed = tokenIs0 ? amount1 : amount0;
uint256 tokenUsed = tokenIs0 ? amount0 : amount1;
require(wethUsed == 0, "Not single-sided");
require(liquidity > 0 && tokenUsed >= supply - supply / 1000, "Seed failed");
}
/// Dev buy DIRECTLY against the fresh pool (exact-input WETH -> token, paid
/// via the swap callback). No venue router involved, so the same factory
/// works on any V3-style venue — including SushiSwap CLAMM, which has no
/// SwapRouter02 on Robinhood Chain.
function _devBuy(address token, address pool, uint256 devBuyEth) private returns (uint256 devTokens) {
IWETH9(weth).deposit{value: devBuyEth}();
bool zeroForOne = weth < token; // paying WETH in, taking the token out
_devBuyPool = pool;
(int256 amount0, int256 amount1) = IV3PoolMinimal(pool).swap(
msg.sender,
zeroForOne,
int256(devBuyEth),
zeroForOne ? MIN_SQRT_RATIO + 1 : MAX_SQRT_RATIO - 1,
""
);
_devBuyPool = address(0);
devTokens = uint256(-(zeroForOne ? amount1 : amount0));
// A partial fill can't happen against the full-supply position, but if
// any WETH dust were ever left it is unwrapped so the refund sweeps it.
uint256 wethLeft = IERC20Minimal(weth).balanceOf(address(this));
if (wethLeft > 0) IWETH9(weth).withdraw(wethLeft);
}
/// Pays the dev-buy swap. Only callable by the pool the factory is actively
/// swapping against — the transient `_devBuyPool` gate means this can never
/// be used to pull funds outside a launch.
function uniswapV3SwapCallback(int256 amount0Delta, int256 amount1Delta, bytes calldata) external {
require(msg.sender == _devBuyPool && _devBuyPool != address(0), "Bad callback");
uint256 owed = uint256(amount0Delta > 0 ? amount0Delta : amount1Delta);
require(IERC20Minimal(weth).transfer(msg.sender, owed), "Pay failed");
}
receive() external payable {}
}[
{
"type": "constructor",
"inputs": [
{
"name": "name_",
"type": "string",
"internalType": "string"
},
{
"name": "symbol_",
"type": "string",
"internalType": "string"
},
{
"name": "metadataURI_",
"type": "string",
"internalType": "string"
}
],
"stateMutability": "nonpayable"
},
{
"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": "holder",
"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": "amount",
"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": "metadataURI",
"type": "function",
"inputs": [],
"outputs": [
{
"name": "",
"type": "string",
"internalType": "string"
}
],
"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": "amount",
"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": "amount",
"type": "uint256",
"internalType": "uint256"
}
],
"outputs": [
{
"name": "",
"type": "bool",
"internalType": "bool"
}
],
"stateMutability": "nonpayable"
}
]0x608060405234801561000f575f80fd5b506004361061009b575f3560e01c8063313ce56711610063578063313ce5671461011c57806370a082311461013657806395d89b411461015e578063a9059cbb14610166578063dd62ed3e14610179575f80fd5b806303ee438c1461009f57806306fdde03146100bd578063095ea7b3146100c557806318160ddd146100e857806323b872dd14610109575b5f80fd5b6100a76101b1565b6040516100b491906105b4565b60405180910390f35b6100a761023d565b6100d86100d336600461061b565b6102cc565b60405190151581526020016100b4565b6100fb6b033b2e3c9fd0803ce800000081565b6040519081526020016100b4565b6100d8610117366004610643565b6102e2565b610124601281565b60405160ff90911681526020016100b4565b6100fb61014436600461067c565b6001600160a01b03165f9081526003602052604090205490565b6100a7610385565b6100d861017436600461061b565b610394565b6100fb61018736600461069c565b6001600160a01b039182165f90815260046020908152604080832093909416825291909152205490565b600280546101be906106cd565b80601f01602080910402602001604051908101604052809291908181526020018280546101ea906106cd565b80156102355780601f1061020c57610100808354040283529160200191610235565b820191905f5260205f20905b81548152906001019060200180831161021857829003601f168201915b505050505081565b60605f805461024b906106cd565b80601f0160208091040260200160405190810160405280929190818152602001828054610277906106cd565b80156102c25780601f10610299576101008083540402835291602001916102c2565b820191905f5260205f20905b8154815290600101906020018083116102a557829003601f168201915b5050505050905090565b5f6102d83384846103a0565b5060015b92915050565b6001600160a01b0383165f9081526004602090815260408083203384529091528120545f19811461036f578281101561035b5760405162461bcd60e51b8152602060048201526016602482015275496e73756666696369656e7420616c6c6f77616e636560501b60448201526064015b60405180910390fd5b61036f853361036a8685610719565b6103a0565b61037a85858561045c565b506001949350505050565b60606001805461024b906106cd565b5f6102d833848461045c565b6001600160a01b038316158015906103c057506001600160a01b03821615155b6103fb5760405162461bcd60e51b815260206004820152600c60248201526b5a65726f206164647265737360a01b6044820152606401610352565b6001600160a01b038381165f8181526004602090815260408083209487168084529482529182902085905590518481527f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b92591015b60405180910390a3505050565b6001600160a01b0383161580159061047c57506001600160a01b03821615155b6104b75760405162461bcd60e51b815260206004820152600c60248201526b5a65726f206164647265737360a01b6044820152606401610352565b6001600160a01b0383165f908152600360205260409020548111156105155760405162461bcd60e51b8152602060048201526014602482015273496e73756666696369656e742062616c616e636560601b6044820152606401610352565b6001600160a01b0383165f908152600360205260408120805483929061053c908490610719565b90915550506001600160a01b0382165f908152600360205260408120805483929061056890849061072c565b92505081905550816001600160a01b0316836001600160a01b03167fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef8360405161044f91815260200190565b5f602080835283518060208501525f5b818110156105e0578581018301518582016040015282016105c4565b505f604082860101526040601f19601f8301168501019250505092915050565b80356001600160a01b0381168114610616575f80fd5b919050565b5f806040838503121561062c575f80fd5b61063583610600565b946020939093013593505050565b5f805f60608486031215610655575f80fd5b61065e84610600565b925061066c60208501610600565b9150604084013590509250925092565b5f6020828403121561068c575f80fd5b61069582610600565b9392505050565b5f80604083850312156106ad575f80fd5b6106b683610600565b91506106c460208401610600565b90509250929050565b600181811c908216806106e157607f821691505b6020821081036106ff57634e487b7160e01b5f52602260045260245ffd5b50919050565b634e487b7160e01b5f52601160045260245ffd5b818103818111156102dc576102dc610705565b808201808211156102dc576102dc61070556fea2646970667358221220d1c838eee2ab3ec40708e41e5cbc2fbd93478d9d90f17f237d411b073314315764736f6c63430008180033
| 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 | |||||||||
| Transaction Hash | Method | Block | Age | From | To | Amount | Token | ||
|---|---|---|---|---|---|---|---|---|---|
| no token transfers for this address yet | |||||||||
| Txn Hash | Age | Event | Topics / Data |
|---|---|---|---|
| 0xc069c1…7a794c | 18 days agoWed, 29 Jul 2026 20:39:38 UTC | Transfer | [0] 0x000000000000…9f355379 [1] 0x000000000000…a86a39e8 data: 0x000000000000000000…efadd046 |
| 0xc069c1…7a794c | 18 days agoWed, 29 Jul 2026 20:39:38 UTC | Transfer | [0] 0x000000000000…4c64e5e9 [1] 0x000000000000…9f355379 data: 0x000000000000000000…efadd047 |
| 0xc069c1…7a794c | 18 days agoWed, 29 Jul 2026 20:39:38 UTC | Approval | [0] 0x000000000000…4c64e5e9 [1] 0x000000000000…3ec0e98a data: 0x000000000000000000…00000000 |
| 0x0ef62b…07cb81 | 18 days agoWed, 29 Jul 2026 20:39:29 UTC | Approval | [0] 0x000000000000…4c64e5e9 [1] 0x000000000000…3ec0e98a data: 0x000000000000000000…efadd047 |
| 0x37bb48…f02f6a | 19 days agoWed, 29 Jul 2026 12:16:13 UTC | Transfer | [0] 0x000000000000…a86a39e8 [1] 0x000000000000…4c64e5e9 data: 0x000000000000000000…efadd047 |
| 0x37bb48…f02f6a | 19 days agoWed, 29 Jul 2026 12:16:13 UTC | Transfer | [0] 0x000000000000…2f97c588 [1] 0x000000000000…0000dead data: 0x000000000000000000…00002fe8 |
| 0x37bb48…f02f6a | 19 days agoWed, 29 Jul 2026 12:16:13 UTC | Transfer | [0] 0x000000000000…2f97c588 [1] 0x000000000000…a86a39e8 data: 0x000000000000000000…e7ffd018 |
| 0x37bb48…f02f6a | 19 days agoWed, 29 Jul 2026 12:16:13 UTC | Approval | [0] 0x000000000000…2f97c588 [1] 0x000000000000…e7816107 data: 0x000000000000000000…00002fe8 |
| 0x37bb48…f02f6a | 19 days agoWed, 29 Jul 2026 12:16:13 UTC | Approval | [0] 0x000000000000…2f97c588 [1] 0x000000000000…e7816107 data: 0x000000000000000000…e8000000 |
| 0x37bb48…f02f6a | 19 days agoWed, 29 Jul 2026 12:16:13 UTC | Transfer | [0] 0x000000000000…00000000 [1] 0x000000000000…2f97c588 data: 0x000000000000000000…e8000000 |
| Transaction Hash | Method ? | Block | Age | From | To | Amount | Txn Fee ? | ||
|---|---|---|---|---|---|---|---|---|---|
| 0x0ef62b…07cb81 | Approve | 22,747,666 | 18 days agoWed, 29 Jul 2026 20:39:29 UTC | 0xd82d…e5e9 | IN | Robinhood ARTCOIN | $0.000 ETH | 0.00000100 |
| Block | Age | Parent Transaction Hash | Type | Method | From | To | Value | |
|---|---|---|---|---|---|---|---|---|
| 22,446,264 | 19 days agoWed, 29 Jul 2026 12:16:13 UTC | 0x37bb48…f02f6a | CREATE2 | launchSeeded | 0x8c5a…c588 | IN | 0x99c5…bbe9 | 0 ETH |