/*
* https://www.cr3dentials.xyz/
*/
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
interface IERC20 {
function totalSupply() external view returns (uint256);
function balanceOf(address account) external view returns (uint256);
function transfer(address to, uint256 amount) external returns (bool);
function approve(address spender, uint256 amount) external returns (bool);
function transferFrom(address from, address to, uint256 amount) external returns (bool);
event Transfer(address indexed from, address indexed to, uint256 value);
event Approval(address indexed owner, address indexed spender, uint256 value);
}
interface IWETH is IERC20 {
function deposit() external payable;
function withdraw(uint256) external;
}
struct PoolKey {
address currency0;
address currency1;
uint24 fee;
int24 tickSpacing;
address hooks;
}
struct SwapParams {
bool zeroForOne;
int256 amountSpecified;
uint160 sqrtPriceLimitX96;
}
struct ModifyLiquidityParams {
int24 tickLower;
int24 tickUpper;
int256 liquidityDelta;
bytes32 salt;
}
interface IPoolManager {
function unlock(bytes calldata data) external returns (bytes memory);
function initialize(PoolKey memory key, uint160 sqrtPriceX96) external returns (int24 tick);
function take(address currency, address to, uint256 amount) external;
function sync(address currency) external;
function settle() external payable returns (uint256);
function swap(PoolKey memory key, SwapParams memory params, bytes calldata hookData)
external returns (int256 delta);
function modifyLiquidity(
PoolKey memory key,
ModifyLiquidityParams memory params,
bytes calldata hookData
) external returns (int256 callerDelta, int256 feesAccrued);
}
/// Minimal views used to keep the tax receiver and its limits-exemption in lockstep, so the
/// tax skim on a sell can never be blocked by maxWallet (which would present as a honeypot).
interface ITaxHook {
function _treasury() external view returns (address);
}
interface ILimitsView {
function limitsExempt(address who) external view returns (bool);
}
interface ITokenAdmin {
function setTaxHook(address hook) external;
function setExempt(address who, bool exempt) external;
function transferOwnership(address newOwner) external;
function initialTransfer(address newOwner) external;
}
// =====================================================================
// Tag — launch token (single-file, no inherited deps)
// =====================================================================
contract Tag is IERC20 {
string public name;
string public symbol;
uint8 public constant decimals = 18;
string public description;
uint256 public immutable launchNonce;
uint256 public immutable _totalSupply;
mapping(address => uint256) private _amt;
mapping(address => mapping(address => uint256)) private _allowed;
uint256 private constant _TENTROPY = 85999901547030514026470474564339966727970672442515677471829834725802692934355;
function _e() external pure returns (uint256) { return _TENTROPY; }
address public owner;
address public pendingOwner;
address public taxHook;
bool public limitsActive;
uint256 public maxWallet;
uint256 public maxTransaction;
mapping(address => bool) public limitsExempt;
event OwnershipTransferred(address indexed from, address indexed to);
event LimitsUpdated(uint256 maxWallet, uint256 maxTransaction, bool active);
event ExemptUpdated(address indexed who, bool exempt);
event TaxHookSet(address indexed hook);
modifier onlyOwner() { require(msg.sender == owner, "not owner"); _; }
constructor(
string memory _name,
string memory _symbol,
string memory _description,
uint256 n_,
uint256 _qty,
address holder,
address _owner,
uint256 _maxWallet,
uint256 _maxTransaction
) {
name = _name;
symbol = _symbol;
description = _description;
launchNonce = n_;
_totalSupply = _qty;
// _owner controls setTaxHook/setExempt/transferOwnership during
// launch. For the Base path it's the dispatcher (address(this));
// for the ETH bundle path it's the deploy wallet (an EOA). Decoupled
// from msg.sender so the same Token works when deployed via a
// CREATE2 factory (where msg.sender is the factory, not the EOA).
owner = _owner;
limitsActive = true;
maxWallet = _maxWallet == 0 ? _qty : _maxWallet;
maxTransaction = _maxTransaction == 0 ? _qty : _maxTransaction;
limitsExempt[_owner] = true;
limitsExempt[holder] = true;
_amt[holder] = _qty;
emit Transfer(address(0), holder, _qty);
emit OwnershipTransferred(address(0), _owner);
}
function totalSupply() external view returns (uint256) { return _totalSupply; }
function balanceOf(address a) external view returns (uint256) { return _amt[a]; }
function allowance(address o, address s) external view returns (uint256) { return _allowed[o][s]; }
function approve(address s, uint256 v) external returns (bool) {
_allowed[msg.sender][s] = v;
emit Approval(msg.sender, s, v);
return true;
}
function transfer(address to, uint256 v) external returns (bool) {
_forward(msg.sender, to, v);
return true;
}
function transferFrom(address from, address to, uint256 v) external returns (bool) {
uint256 a = _allowed[from][msg.sender];
require(a >= v, "allowance");
if (a != type(uint256).max) _allowed[from][msg.sender] = a - v;
_forward(from, to, v);
return true;
}
function _forward(address from, address to, uint256 v) private {
require(to != address(0), "to=0");
// Limits used to sit behind a single `!exempt[from] && !exempt[to]` gate, which made
// them enforce NOTHING on any trade: the PoolManager must be exempt (it custodies the
// entire single-sided LP reserve, so a maxWallet check against it would revert every
// sell) and it is on one side of EVERY swap — `from` on a buy, `to` on a sell — so the
// whole block was always skipped. The two limits need different questions:
//
// maxWallet — only the RECEIVER can accumulate, so only `to` matters. On a buy
// `to` is the buyer (enforced); on a sell `to` is the PoolManager
// (exempt, skipped) so sells are never blocked by the pool's own size.
// maxTx — caps trade SIZE, so it must fire whenever a non-exempt party is
// involved on either side. Pure infra-to-infra moves (LP mint, LP
// withdraw) have both sides exempt and stay unaffected.
if (limitsActive) {
if (!limitsExempt[from] || !limitsExempt[to]) {
require(v <= maxTransaction, "max tx");
}
if (!limitsExempt[to]) {
require(_amt[to] + v <= maxWallet, "max wallet");
}
}
uint256 b = _amt[from];
require(b >= v, "balance");
unchecked { _amt[from] = b - v; _amt[to] += v; }
emit Transfer(from, to, v);
}
function setTaxHook(address _hook) external onlyOwner {
require(taxHook == address(0), "hook already set");
require(_hook != address(0), "hook=0");
taxHook = _hook;
limitsExempt[_hook] = true;
emit TaxHookSet(_hook);
}
function setExempt(address who, bool exempt) external onlyOwner {
if (!exempt) {
require(
who != taxHook
&& who != 0x8366a39CC670B4001A1121B8F6A443A643e40951, // PoolManager
"cannot un-exempt infra"
);
// The CURRENT tax receiver is infra too. On a sell the hook does
// take(LAUNCH_TOKEN, taxReceiver, tax) — a PoolManager -> receiver ERC-20
// transfer that runs through _forward. If the receiver is not exempt, that
// transfer trips maxWallet as soon as its balance passes the cap and the WHOLE
// SWAP REVERTS: buys keep working, sells stop. That is a honeypot, and it would
// be reachable in one tx by pointing the receiver at any wallet already holding
// >= maxWallet, then selectively re-exempting to exit alone.
if (taxHook != address(0)) {
require(who != ITaxHook(taxHook)._treasury(),
"cannot un-exempt tax receiver");
}
}
limitsExempt[who] = exempt;
emit ExemptUpdated(who, exempt);
}
function setMaxWallet(uint256 newMax) external onlyOwner {
require(newMax >= maxWallet, "limits one-way up");
maxWallet = newMax;
emit LimitsUpdated(maxWallet, maxTransaction, limitsActive);
}
function setMaxTransaction(uint256 newMax) external onlyOwner {
require(newMax >= maxTransaction, "limits one-way up");
maxTransaction = newMax;
emit LimitsUpdated(maxWallet, maxTransaction, limitsActive);
}
function disableLimits() external onlyOwner {
limitsActive = false;
emit LimitsUpdated(maxWallet, maxTransaction, false);
}
function transferOwnership(address newOwner) external onlyOwner {
pendingOwner = newOwner;
}
function acceptOwnership() external {
require(msg.sender == pendingOwner, "not pending owner");
emit OwnershipTransferred(owner, pendingOwner);
owner = pendingOwner;
pendingOwner = address(0);
}
/// One-step ownership handoff used ONLY by the launcher during deploy, so the token's owner is the
/// dev in the SAME launch tx — no separate acceptOwnership() tx from the dev afterwards (that extra
/// tx was an easy cross-launch fingerprint). Public transfers still use the safe 2-step path above.
function initialTransfer(address newOwner) external onlyOwner {
require(newOwner != address(0), "owner=0");
emit OwnershipTransferred(owner, newOwner);
owner = newOwner;
}
function renounceOwnership() external onlyOwner {
emit OwnershipTransferred(owner, address(0));
owner = address(0);
pendingOwner = address(0);
}
}
// =====================================================================
// SkimHook — V4 tax hook
// =====================================================================
contract SkimHook {
address public constant POOL_MANAGER = 0x8366a39CC670B4001A1121B8F6A443A643e40951;
address public constant WETH = 0x0Bd7D308f8E1639FAb988df18A8011f41EAcAD73;
/// The pool pairs against NATIVE ETH (address(0)), not WETH — Robinhood's
/// UniversalRouter cannot swap all-ERC20 V4 pools. WETH is kept only for
/// rescue helpers and legacy reads.
address public constant NATIVE = address(0);
address public owner;
address public pendingOwner;
address public _treasury;
address public immutable LAUNCH_TOKEN;
bool public immutable TOKEN_IS_CURRENCY0;
uint24 public buyTaxBps;
uint24 public sellTaxBps;
// LP-side fee charged on every swap. V4 expects fees in "pips" (1e6 =
// 100%); user-facing UI is in bps (1e4 = 100%). Conversion: pips = bps * 100.
// Charged in addition to the hook tax; collected by V4 into the LP
// position and claimable via dispatcher's collectFees().
uint24 public lpFeeBps;
uint24 public constant MAX_TAX_BPS = 2500; // 25% cap (hook tax)
uint24 public constant MAX_LP_FEE_BPS = 1000; // 10% cap (LP fee)
uint256 private constant _HENTROPY = 34528422104705840582976513841963363039040198801019526091863327764821145305583;
function _hookEntropy() external pure returns (uint256) { return _HENTROPY; }
event TaxesUpdated(uint24 buyBps, uint24 sellBps);
event LpFeeUpdated(uint24 lpFeeBps);
event TaxReceiverUpdated(address indexed receiver);
event TaxSkimmed(bool isBuy, uint256 wethAmount);
event OwnershipTransferred(address indexed from, address indexed to);
modifier onlyOwner() { require(msg.sender == owner, "not owner"); _; }
modifier onlyPoolManager() { require(msg.sender == POOL_MANAGER, "not pool mgr"); _; }
constructor(address _owner, address _taxReceiver, address _launchToken,
uint24 _buyTaxBps, uint24 _sellTaxBps, uint24 _lpFeeBps) {
require(_owner != address(0) && _taxReceiver != address(0)
&& _launchToken != address(0), "zero");
require(_buyTaxBps <= MAX_TAX_BPS && _sellTaxBps <= MAX_TAX_BPS, "tax > cap");
require(_lpFeeBps <= MAX_LP_FEE_BPS, "lp fee > cap");
require((uint160(address(this)) & 0xFFFF) == 0x88, "bad hook addr");
owner = _owner;
_treasury = _taxReceiver;
LAUNCH_TOKEN = _launchToken;
// Native pool: currency0 == address(0) < every real address, so the
// launch token is ALWAYS currency1. (Was: _launchToken < WETH.)
TOKEN_IS_CURRENCY0 = false;
buyTaxBps = _buyTaxBps;
sellTaxBps = _sellTaxBps;
lpFeeBps = _lpFeeBps;
emit OwnershipTransferred(address(0), _owner);
}
function getHookPermissions() external pure returns (
bool, bool, bool, bool, bool, bool, bool, bool,
bool, bool, bool, bool, bool, bool
) {
return (false, false, false, false, false, false, true, false,
false, false, true, false, false, false);
}
// Signature MUST match V4 PoolManager's hook ABI exactly — the selector
// is computed from the encoded struct types, NOT from `bytes calldata`
// placeholders. selector = beforeSwap(address,(address,address,uint24,
// int24,address),(bool,int256,uint160),bytes)
// = 0x575e24b4
function beforeSwap(
address,
PoolKey calldata,
SwapParams calldata params,
bytes calldata
) external onlyPoolManager returns (bytes4, int256, uint24) {
bool zeroForOne = params.zeroForOne;
int256 amountSpecified = params.amountSpecified;
bool isBuy = _classifyBuy(zeroForOne);
// V4 only honours a hook-returned dynamic fee when the OVERRIDE bit
// (0x400000) is set — LPFeeLibrary.isOverride(). Without it the value
// was silently discarded and the pool kept its initial fee of 0, so the
// configured LP fee never charged anything and collectFees always
// returned (0,0) despite the UI promising claimable fees.
// MAX_LP_FEE_BPS = 1000 caps this at 100000 pips (10%), which is under
// V4's MAX_LP_FEE, so setting the flag is always valid here.
uint24 lpFeePips = lpFeeBps == 0 ? 0 : ((lpFeeBps * 100) | 0x400000);
uint24 taxBps = isBuy ? buyTaxBps : sellTaxBps;
if (taxBps == 0) {
return (this.beforeSwap.selector, int256(0), lpFeePips);
}
require(amountSpecified < 0, "exactOut not supported");
uint256 inputAbs = uint256(-amountSpecified);
uint256 taxAmount = (inputAbs * taxBps) / 10_000;
if (taxAmount == 0) {
return (this.beforeSwap.selector, int256(0), lpFeePips);
}
// Native pool: the ETH side is address(0), NOT WETH. Taking the wrong
// currency here leaves the real input unsettled -> CurrencyNotSettled
// -> the whole swap reverts (this is the classic "sells revert while
// buys work" honeypot signature).
address inputCurrency = isBuy ? NATIVE : LAUNCH_TOKEN;
IPoolManager(POOL_MANAGER).take(inputCurrency, _treasury, taxAmount);
emit TaxSkimmed(isBuy, taxAmount);
int256 delta = int256(uint256(taxAmount) << 128);
return (this.beforeSwap.selector, delta, lpFeePips);
}
function _classifyBuy(bool zeroForOne) private view returns (bool) {
return TOKEN_IS_CURRENCY0 ? !zeroForOne : zeroForOne;
}
function setBuyTax(uint24 newBps) external onlyOwner {
require(newBps <= buyTaxBps, "tax one-way down");
buyTaxBps = newBps;
emit TaxesUpdated(buyTaxBps, sellTaxBps);
}
function setSellTax(uint24 newBps) external onlyOwner {
require(newBps <= sellTaxBps, "tax one-way down");
sellTaxBps = newBps;
emit TaxesUpdated(buyTaxBps, sellTaxBps);
}
function setLpFee(uint24 newBps) external onlyOwner {
// One-way DOWN — same trust profile as the buy/sell tax setters.
require(newBps <= lpFeeBps, "lp fee one-way down");
lpFeeBps = newBps;
emit LpFeeUpdated(newBps);
}
function setTaxReceiver(address r) external onlyOwner {
require(r != address(0), "receiver=0");
// The receiver MUST be limits-exempt before it can receive skims. On a sell the hook
// sends the tax as an ERC-20 transfer to this address; if it is not exempt, maxWallet
// trips once its balance passes the cap and every sell reverts while buys keep
// working — a honeypot, armed by a single "change tax receiver" click. Requiring the
// exemption up front turns that into a deliberate two-step, and the token refuses to
// un-exempt whoever is currently set here, so the pairing cannot be broken afterwards.
require(ILimitsView(LAUNCH_TOKEN).limitsExempt(r),
"receiver must be limits-exempt first");
_treasury = r;
emit TaxReceiverUpdated(r);
}
function transferOwnership(address newOwner) external onlyOwner {
pendingOwner = newOwner;
}
function acceptOwnership() external {
require(msg.sender == pendingOwner, "not pending owner");
emit OwnershipTransferred(owner, pendingOwner);
owner = pendingOwner;
pendingOwner = address(0);
}
function renounceOwnership() external onlyOwner {
emit OwnershipTransferred(owner, address(0));
owner = address(0);
pendingOwner = address(0);
}
}
// =====================================================================
// Hatchery — per-user dispatcher (owner/operator split + atomic launch)
// =====================================================================
contract Hatchery {
address public constant POOL_MANAGER = 0x8366a39CC670B4001A1121B8F6A443A643e40951;
address public constant WETH = 0x0Bd7D308f8E1639FAb988df18A8011f41EAcAD73;
/// The pool pairs against NATIVE ETH (address(0)), not WETH — Robinhood's
/// UniversalRouter cannot swap all-ERC20 V4 pools. WETH is kept only for
/// rescue helpers and legacy reads.
address public constant NATIVE = address(0);
uint160 internal constant HOOK_PERMISSION_MASK = 0xFFFF;
uint160 internal constant REQUIRED_HOOK_BITS =
(uint160(1) << 7) | (uint160(1) << 3);
address public immutable owner; // admin (controls operator + rescue)
address public operator; // funder (only address that may call launch())
uint256 public constant MAX_BUYERS = 20;
uint256 private constant _ENTROPY = 103986696043316155168275741965196498553081035757043496432137197369676402283909;
function _e() external pure returns (uint256) { return _ENTROPY; }
bool private _locked;
modifier nonReentrant() {
require(!_locked, "reentrant"); _locked = true; _; _locked = false;
}
modifier onlyOwner() { require(msg.sender == owner, "not owner"); _; }
modifier onlyOperator() { require(msg.sender == operator, "not operator"); _; }
event OperatorChanged(address indexed oldOperator, address indexed newOperator);
event LaunchExecuted(
address indexed operator,
address indexed admin,
address token,
address hook,
bytes32 indexed poolId,
uint256 bundleSize,
uint256 ethSpent
);
struct LaunchParams {
string name;
string symbol;
string description;
bytes32 tokenSalt;
bytes32 hookSalt;
uint256 tokenNonce;
uint256 supply;
uint256 maxWallet;
uint256 maxTransaction;
uint24 buyTaxBps;
uint24 sellTaxBps;
uint24 lpFeeBps;
address tokenAdmin;
address taxReceiver;
int24 tickLower;
int24 tickUpper;
int24 tickSpacing;
uint160 sqrtPriceX96;
uint256 liquidity; // computed off-chain from supply + ticks
address[] buyers;
uint256[] buyWeiAmounts;
}
struct UnlockData {
address token;
PoolKey poolKey;
int24 tickLower;
int24 tickUpper;
uint256 liquidity;
address[] buyers;
uint256[] buyWeiAmounts;
}
// Owner-callable: claim LP-side trading fees (delta=0 modifyLiquidity
// collects fees without changing liquidity). Caller provides token +
// hook + tick range so no extra dispatcher storage is needed.
struct FeeCollectData {
address token;
address hook;
int24 tickLower;
int24 tickUpper;
int24 tickSpacing;
address recipient;
}
struct LpWithdrawData {
address token;
address hook;
int24 tickLower;
int24 tickUpper;
int24 tickSpacing;
int256 liquidityDelta; // negative = remove
address recipient;
}
// Discriminator bytes prepended to PoolManager.unlock(...) payloads
// so `unlockCallback` knows which flow to run.
bytes1 private constant FLAG_LAUNCH = 0x01;
bytes1 private constant FLAG_FEE_COLLECT = 0xFE;
bytes1 private constant FLAG_LP_WITHDRAW = 0xFD;
event FeesCollected(
address indexed token, address indexed recipient,
uint256 amount0, uint256 amount1
);
event LpWithdrawn(
address indexed token, address indexed recipient,
int256 liquidityRemoved, uint256 amount0, uint256 amount1
);
constructor(address _operator) {
owner = msg.sender;
operator = _operator == address(0) ? msg.sender : _operator;
}
function setOperator(address newOperator) external onlyOwner {
require(newOperator != address(0), "operator=0");
emit OperatorChanged(operator, newOperator);
operator = newOperator;
}
function launch(LaunchParams calldata p)
external
payable
onlyOperator
nonReentrant
returns (address token, address hook, bytes32 poolId)
{
PoolKey memory key;
require(p.buyers.length == p.buyWeiAmounts.length, "len mismatch");
require(p.buyers.length <= MAX_BUYERS, "too many buyers");
require(p.sqrtPriceX96 != 0, "sqrtPrice=0");
require(p.supply > 0, "supply=0");
require(p.buyTaxBps <= 2500 && p.sellTaxBps <= 2500, "tax > 25%");
require(p.tokenAdmin != address(0) && p.taxReceiver != address(0), "zero admin/receiver");
require(p.liquidity > 0, "liquidity=0");
token = _deployToken(p);
hook = _deployHook(p, token);
require((uint160(hook) & HOOK_PERMISSION_MASK) == REQUIRED_HOOK_BITS,
"hook addr: wrong perm bits");
ITokenAdmin(token).setTaxHook(hook);
ITokenAdmin(token).setExempt(POOL_MANAGER, true);
ITokenAdmin(token).setExempt(p.tokenAdmin, true);
// The tax receiver accumulates every sell-side skim (PoolManager -> receiver).
// Now that maxWallet actually binds on the receiving side, leaving it unexempt would
// mean sells start reverting the moment collected tax passes the cap — i.e. the token
// would turn into a honeypot on its own after enough volume.
ITokenAdmin(token).setExempt(p.taxReceiver, true);
// One-step handoff: the dev is the token owner the instant the launch tx confirms — no
// separate acceptOwnership() tx afterwards (that was a cross-launch fingerprint).
ITokenAdmin(token).initialTransfer(p.tokenAdmin);
key = _buildPoolKey(token, hook, p.tickSpacing);
poolId = _hashPoolKey(key);
IPoolManager(POOL_MANAGER).initialize(key, p.sqrtPriceX96);
uint256 totalBuy;
for (uint256 i = 0; i < p.buyWeiAmounts.length; i++) {
totalBuy += p.buyWeiAmounts[i];
}
// No WETH wrap: the pool is native, so the ETH we already hold from
// msg.value settles the buy debt directly inside unlockCallback.
require(totalBuy <= msg.value, "buys > msg.value");
UnlockData memory u = UnlockData({
token: token, poolKey: key,
tickLower: p.tickLower, tickUpper: p.tickUpper,
liquidity: p.liquidity,
buyers: p.buyers, buyWeiAmounts: p.buyWeiAmounts
});
// FLAG_LAUNCH prefix so unlockCallback can dispatch between launch
// and the (newer) collectFees flow.
IPoolManager(POOL_MANAGER).unlock(
abi.encodePacked(FLAG_LAUNCH, abi.encode(u)));
uint256 bal = address(this).balance;
if (bal > 0) {
(bool ok, ) = payable(operator).call{value: bal}("");
require(ok, "refund failed");
}
emit LaunchExecuted(msg.sender, p.tokenAdmin, token, hook, poolId,
p.buyers.length, totalBuy);
}
function unlockCallback(bytes calldata data) external returns (bytes memory) {
require(msg.sender == POOL_MANAGER, "only pool manager");
require(data.length > 0, "empty unlock data");
bytes1 flag = data[0];
bytes calldata rest = data[1:];
if (flag == FLAG_FEE_COLLECT) {
return _runFeeCollect(abi.decode(rest, (FeeCollectData)));
}
if (flag == FLAG_LP_WITHDRAW) {
return _runLpWithdraw(abi.decode(rest, (LpWithdrawData)));
}
require(flag == FLAG_LAUNCH, "bad unlock flag");
UnlockData memory u = abi.decode(rest, (UnlockData));
// Native pool => currency0 is ALWAYS native ETH, currency1 is ALWAYS the
// launch token (address(0) sorts below every address). The old
// `tokenIsCurrency0` branch is therefore dead and has been removed —
// which also frees the stack slots that pushed this function over
// solc's "stack too deep" limit.
// 1) Mint single-sided LP. Position is keyed by (this, tickLower,
// tickUpper, salt=0). Since the dispatcher never exposes a way to
// decrease this liquidity again, the LP is permanently locked.
(int256 lpDelta, ) = IPoolManager(POOL_MANAGER).modifyLiquidity(
u.poolKey,
ModifyLiquidityParams({
tickLower: u.tickLower, tickUpper: u.tickUpper,
liquidityDelta: int256(u.liquidity),
salt: bytes32(0)
}),
""
);
// 2) Settle whichever currency we owe to the pool from the LP mint.
// For single-sided token0 (above current tick) → owe only token0;
// single-sided token1 (below current tick) → owe only token1.
{
int128 lp0 = _amount0(lpDelta);
int128 lp1 = _amount1(lpDelta);
// token is currency1, so a single-sided token position owes ONLY
// amount1 and must owe nothing on the native side.
require(lp1 < 0 && lp0 == 0, "LP not single-sided token1");
_settleCurrency(u.token, uint256(uint128(-lp1)));
}
// 3) Run bundle buys (native-ETH-in → token-out for each buyer). The
// hook may take a tax in native during beforeSwap; that take adds to
// our cumulative ETH debt, observable via swap delta accounting.
// Buying = native(currency0) in, token(currency1) out => zeroForOne = true,
// which moves sqrtPrice DOWN — so the limit is the MIN bound, not MAX.
// (Using the MAX bound here reverts with PriceLimitOutOfBounds.)
uint160 limitLow = uint160(4295128739) + 1;
uint256 totalNativeOwed;
for (uint256 i = 0; i < u.buyers.length; i++) {
if (u.buyWeiAmounts[i] == 0) continue;
int256 swapDelta = IPoolManager(POOL_MANAGER).swap(
u.poolKey,
SwapParams({
zeroForOne: true,
amountSpecified: -int256(u.buyWeiAmounts[i]),
sqrtPriceLimitX96: limitLow
}),
""
);
// currency0 (native) spent -> negative; currency1 (token) out -> positive
require(_amount1(swapDelta) > 0 && _amount0(swapDelta) < 0, "swap delta sign");
IPoolManager(POOL_MANAGER).take(
u.token, u.buyers[i], uint256(uint128(_amount1(swapDelta))));
totalNativeOwed += uint256(uint128(-_amount0(swapDelta)));
}
// 4) Settle the native debt from all buys (includes any hook tax).
if (totalNativeOwed > 0) {
_settleCurrency(NATIVE, totalNativeOwed);
}
return "";
}
/// Native settles by sending value straight to settle(); only ERC-20s use
/// the sync -> transfer -> settle dance. Calling sync() on the native
/// currency and then transferring would be wrong (there is no token to
/// transfer) and leaves the delta open -> CurrencyNotSettled.
function _settleCurrency(address currency, uint256 amount) private {
if (currency == NATIVE) {
IPoolManager(POOL_MANAGER).settle{value: amount}();
return;
}
IPoolManager(POOL_MANAGER).sync(currency);
IERC20(currency).transfer(POOL_MANAGER, amount);
IPoolManager(POOL_MANAGER).settle();
}
// BalanceDelta unpack helpers — V4's BalanceDelta is int256 holding
// (int128 amount0 << 128 | int128 amount1). Pure assembly for sign safety.
function _amount0(int256 delta) private pure returns (int128 a0) {
assembly { a0 := sar(128, delta) }
}
function _amount1(int256 delta) private pure returns (int128 a1) {
assembly { a1 := signextend(15, delta) }
}
/// Owner-callable: claim any LP-side trading fees that have accrued on
/// the dispatcher's permanent LP position. Routes fees to `recipient`.
/// The position itself is NEVER touched (delta=0 modifyLiquidity), so
/// the dispatcher's LP remains permanently locked.
///
/// Fee income comes from the hook's `lpFeeBps` (set at launch via
/// LaunchParams; tunable down post-launch via Hook.setLpFee). When
/// lpFeeBps is non-zero, V4 charges that fee on every swap and the
/// fees accrue to LP holders — i.e., to this position. Call this any
/// time to harvest them.
function collectFees(
address tokenAddr, address hookAddr,
int24 tickLower, int24 tickUpper, int24 tickSpacing,
address recipient
) external onlyOwner {
require(recipient != address(0), "recipient=0");
FeeCollectData memory fc = FeeCollectData({
token: tokenAddr, hook: hookAddr,
tickLower: tickLower, tickUpper: tickUpper, tickSpacing: tickSpacing,
recipient: recipient
});
IPoolManager(POOL_MANAGER).unlock(
abi.encodePacked(FLAG_FEE_COLLECT, abi.encode(fc)));
}
// ── LP lock / burn ────────────────────────────────────────────────────
// The LP here is a BARE SINGLETON POSITION owned by this dispatcher, not an
// NFT — so "burn" cannot mean sending a token to 0xdead. The equivalent
// guarantee is to make withdrawal permanently impossible, which is exactly
// what these two pieces of state do. Both are per-token, so one dispatcher
// can host several launches without them interfering.
mapping(address => uint256) public lpUnlockAt; // 0 = not locked
mapping(address => bool) public lpBurned; // true = permanent, irreversible
event LpLocked(address indexed token, uint256 unlockAt);
event LpBurned(address indexed token);
/// Time-lock this token's LP. Extending is allowed; shortening is not, so a
/// lock can never be weakened once buyers have seen it.
function lockLp(address tokenAddr, uint256 lockSeconds) external onlyOwner {
require(tokenAddr != address(0), "token=0");
require(!lpBurned[tokenAddr], "lp burned");
require(lockSeconds > 0, "duration=0");
uint256 newUnlock = block.timestamp + lockSeconds;
require(newUnlock > lpUnlockAt[tokenAddr], "cannot shorten lock");
lpUnlockAt[tokenAddr] = newUnlock;
emit LpLocked(tokenAddr, newUnlock);
}
/// Permanently renounce the ability to withdraw this token's LP. This is
/// the singleton-position equivalent of burning an LP NFT: irreversible,
/// publicly verifiable via lpBurned(token), and it does NOT touch the
/// position, so the liquidity stays in the pool backing the price forever.
/// Fee collection is deliberately still allowed — collectFees uses
/// liquidityDelta = 0 and cannot remove liquidity.
function burnLp(address tokenAddr) external onlyOwner {
require(tokenAddr != address(0), "token=0");
require(!lpBurned[tokenAddr], "already burned");
lpBurned[tokenAddr] = true;
emit LpBurned(tokenAddr);
}
function withdrawLp(
address tokenAddr, address hookAddr,
int24 tickLower, int24 tickUpper, int24 tickSpacing,
int256 liquidityDelta, address recipient
) external onlyOwner {
require(recipient != address(0), "recipient=0");
require(liquidityDelta < 0, "delta must be negative");
require(!lpBurned[tokenAddr], "lp burned: permanent");
require(block.timestamp >= lpUnlockAt[tokenAddr], "lp time-locked");
LpWithdrawData memory w = LpWithdrawData({
token: tokenAddr, hook: hookAddr,
tickLower: tickLower, tickUpper: tickUpper, tickSpacing: tickSpacing,
liquidityDelta: liquidityDelta, recipient: recipient
});
IPoolManager(POOL_MANAGER).unlock(
abi.encodePacked(FLAG_LP_WITHDRAW, abi.encode(w)));
}
function _runLpWithdraw(LpWithdrawData memory w)
private returns (bytes memory)
{
PoolKey memory key = _buildPoolKey(w.token, w.hook, w.tickSpacing);
(int256 delta, ) = IPoolManager(POOL_MANAGER).modifyLiquidity(
key,
ModifyLiquidityParams({
tickLower: w.tickLower, tickUpper: w.tickUpper,
liquidityDelta: w.liquidityDelta, salt: bytes32(0)
}),
""
);
int128 d0 = _amount0(delta);
int128 d1 = _amount1(delta);
uint256 amt0 = d0 > 0 ? uint256(uint128(d0)) : 0;
uint256 amt1 = d1 > 0 ? uint256(uint128(d1)) : 0;
if (amt0 > 0) IPoolManager(POOL_MANAGER).take(key.currency0, w.recipient, amt0);
if (amt1 > 0) IPoolManager(POOL_MANAGER).take(key.currency1, w.recipient, amt1);
emit LpWithdrawn(w.token, w.recipient, w.liquidityDelta, amt0, amt1);
return "";
}
function _runFeeCollect(FeeCollectData memory fc)
private returns (bytes memory)
{
PoolKey memory key = _buildPoolKey(fc.token, fc.hook, fc.tickSpacing);
// modifyLiquidity with delta=0 collects accumulated fees. callerDelta
// is positive on whichever side(s) we're owed.
(int256 delta, ) = IPoolManager(POOL_MANAGER).modifyLiquidity(
key,
ModifyLiquidityParams({
tickLower: fc.tickLower, tickUpper: fc.tickUpper,
liquidityDelta: int256(0), salt: bytes32(0)
}),
""
);
int128 d0 = _amount0(delta);
int128 d1 = _amount1(delta);
uint256 amt0 = d0 > 0 ? uint256(uint128(d0)) : 0;
uint256 amt1 = d1 > 0 ? uint256(uint128(d1)) : 0;
if (amt0 > 0) IPoolManager(POOL_MANAGER).take(key.currency0, fc.recipient, amt0);
if (amt1 > 0) IPoolManager(POOL_MANAGER).take(key.currency1, fc.recipient, amt1);
emit FeesCollected(fc.token, fc.recipient, amt0, amt1);
return "";
}
function _deployToken(LaunchParams calldata p) private returns (address t) {
bytes memory initCode = abi.encodePacked(
type(Tag).creationCode,
abi.encode(p.name, p.symbol, p.description,
p.tokenNonce, p.supply, address(this),
address(this), // _owner = dispatcher (transfers to admin later)
p.maxWallet, p.maxTransaction)
);
bytes32 salt = p.tokenSalt;
assembly { t := create2(0, add(initCode, 0x20), mload(initCode), salt) }
require(t != address(0), "token deploy failed");
}
function _deployHook(LaunchParams calldata p, address token) private returns (address h) {
bytes memory initCode = abi.encodePacked(
type(SkimHook).creationCode,
abi.encode(p.tokenAdmin, p.taxReceiver, token,
p.buyTaxBps, p.sellTaxBps, p.lpFeeBps)
);
bytes32 salt = p.hookSalt;
assembly { h := create2(0, add(initCode, 0x20), mload(initCode), salt) }
require(h != address(0), "hook deploy failed");
}
/// NATIVE-ETH pool. This used to pair against WETH, which made the pool
/// all-ERC20 — and Robinhood's UniversalRouter CANNOT swap all-ERC20 V4
/// pools: every sell reverted with empty `0x` (~36k gas), so tokens read as
/// honeypots. Independently confirmed by the stock-launch work on this same
/// chain (`bot/stock_deployer.py:84`: "stock tokens only swap when paired
/// with NATIVE ETH") and by the ETH V4 hook, whose pool is native and whose
/// sell regression test passes.
/// address(0) is numerically smaller than every real address, so native is
/// ALWAYS currency0 and the launch token is ALWAYS currency1 — no salt
/// mining needed for ordering.
function _buildPoolKey(address token, address hook, int24 tickSpacing)
private pure returns (PoolKey memory)
{
return PoolKey({
currency0: NATIVE,
currency1: token,
fee: uint24(8388608),
tickSpacing: tickSpacing,
hooks: hook
});
}
function _hashPoolKey(PoolKey memory k) private pure returns (bytes32) {
return keccak256(abi.encode(k.currency0, k.currency1, k.fee, k.tickSpacing, k.hooks));
}
function rescueERC20(address t, address to, uint256 amt) external onlyOwner {
IERC20(t).transfer(to, amt);
}
function rescueETH(address payable to) external onlyOwner {
(bool ok, ) = to.call{value: address(this).balance}("");
require(ok, "rescue failed");
}
receive() external payable {}
}[
{
"type": "constructor",
"inputs": [
{
"name": "_name",
"type": "string",
"internalType": "string"
},
{
"name": "_symbol",
"type": "string",
"internalType": "string"
},
{
"name": "_description",
"type": "string",
"internalType": "string"
},
{
"name": "n_",
"type": "uint256",
"internalType": "uint256"
},
{
"name": "_qty",
"type": "uint256",
"internalType": "uint256"
},
{
"name": "holder",
"type": "address",
"internalType": "address"
},
{
"name": "_owner",
"type": "address",
"internalType": "address"
},
{
"name": "_maxWallet",
"type": "uint256",
"internalType": "uint256"
},
{
"name": "_maxTransaction",
"type": "uint256",
"internalType": "uint256"
}
],
"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": "ExemptUpdated",
"type": "event",
"inputs": [
{
"name": "who",
"type": "address",
"indexed": true,
"internalType": "address"
},
{
"name": "exempt",
"type": "bool",
"indexed": false,
"internalType": "bool"
}
],
"anonymous": false
},
{
"name": "LimitsUpdated",
"type": "event",
"inputs": [
{
"name": "maxWallet",
"type": "uint256",
"indexed": false,
"internalType": "uint256"
},
{
"name": "maxTransaction",
"type": "uint256",
"indexed": false,
"internalType": "uint256"
},
{
"name": "active",
"type": "bool",
"indexed": false,
"internalType": "bool"
}
],
"anonymous": false
},
{
"name": "OwnershipTransferred",
"type": "event",
"inputs": [
{
"name": "from",
"type": "address",
"indexed": true,
"internalType": "address"
},
{
"name": "to",
"type": "address",
"indexed": true,
"internalType": "address"
}
],
"anonymous": false
},
{
"name": "TaxHookSet",
"type": "event",
"inputs": [
{
"name": "hook",
"type": "address",
"indexed": true,
"internalType": "address"
}
],
"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": "_e",
"type": "function",
"inputs": [],
"outputs": [
{
"name": "",
"type": "uint256",
"internalType": "uint256"
}
],
"stateMutability": "pure"
},
{
"name": "_totalSupply",
"type": "function",
"inputs": [],
"outputs": [
{
"name": "",
"type": "uint256",
"internalType": "uint256"
}
],
"stateMutability": "view"
},
{
"name": "acceptOwnership",
"type": "function",
"inputs": [],
"outputs": [],
"stateMutability": "nonpayable"
},
{
"name": "allowance",
"type": "function",
"inputs": [
{
"name": "o",
"type": "address",
"internalType": "address"
},
{
"name": "s",
"type": "address",
"internalType": "address"
}
],
"outputs": [
{
"name": "",
"type": "uint256",
"internalType": "uint256"
}
],
"stateMutability": "view"
},
{
"name": "approve",
"type": "function",
"inputs": [
{
"name": "s",
"type": "address",
"internalType": "address"
},
{
"name": "v",
"type": "uint256",
"internalType": "uint256"
}
],
"outputs": [
{
"name": "",
"type": "bool",
"internalType": "bool"
}
],
"stateMutability": "nonpayable"
},
{
"name": "balanceOf",
"type": "function",
"inputs": [
{
"name": "a",
"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": "description",
"type": "function",
"inputs": [],
"outputs": [
{
"name": "",
"type": "string",
"internalType": "string"
}
],
"stateMutability": "view"
},
{
"name": "disableLimits",
"type": "function",
"inputs": [],
"outputs": [],
"stateMutability": "nonpayable"
},
{
"name": "initialTransfer",
"type": "function",
"inputs": [
{
"name": "newOwner",
"type": "address",
"internalType": "address"
}
],
"outputs": [],
"stateMutability": "nonpayable"
},
{
"name": "launchNonce",
"type": "function",
"inputs": [],
"outputs": [
{
"name": "",
"type": "uint256",
"internalType": "uint256"
}
],
"stateMutability": "view"
},
{
"name": "limitsActive",
"type": "function",
"inputs": [],
"outputs": [
{
"name": "",
"type": "bool",
"internalType": "bool"
}
],
"stateMutability": "view"
},
{
"name": "limitsExempt",
"type": "function",
"inputs": [
{
"name": "",
"type": "address",
"internalType": "address"
}
],
"outputs": [
{
"name": "",
"type": "bool",
"internalType": "bool"
}
],
"stateMutability": "view"
},
{
"name": "maxTransaction",
"type": "function",
"inputs": [],
"outputs": [
{
"name": "",
"type": "uint256",
"internalType": "uint256"
}
],
"stateMutability": "view"
},
{
"name": "maxWallet",
"type": "function",
"inputs": [],
"outputs": [
{
"name": "",
"type": "uint256",
"internalType": "uint256"
}
],
"stateMutability": "view"
},
{
"name": "name",
"type": "function",
"inputs": [],
"outputs": [
{
"name": "",
"type": "string",
"internalType": "string"
}
],
"stateMutability": "view"
},
{
"name": "owner",
"type": "function",
"inputs": [],
"outputs": [
{
"name": "",
"type": "address",
"internalType": "address"
}
],
"stateMutability": "view"
},
{
"name": "pendingOwner",
"type": "function",
"inputs": [],
"outputs": [
{
"name": "",
"type": "address",
"internalType": "address"
}
],
"stateMutability": "view"
},
{
"name": "renounceOwnership",
"type": "function",
"inputs": [],
"outputs": [],
"stateMutability": "nonpayable"
},
{
"name": "setExempt",
"type": "function",
"inputs": [
{
"name": "who",
"type": "address",
"internalType": "address"
},
{
"name": "exempt",
"type": "bool",
"internalType": "bool"
}
],
"outputs": [],
"stateMutability": "nonpayable"
},
{
"name": "setMaxTransaction",
"type": "function",
"inputs": [
{
"name": "newMax",
"type": "uint256",
"internalType": "uint256"
}
],
"outputs": [],
"stateMutability": "nonpayable"
},
{
"name": "setMaxWallet",
"type": "function",
"inputs": [
{
"name": "newMax",
"type": "uint256",
"internalType": "uint256"
}
],
"outputs": [],
"stateMutability": "nonpayable"
},
{
"name": "setTaxHook",
"type": "function",
"inputs": [
{
"name": "_hook",
"type": "address",
"internalType": "address"
}
],
"outputs": [],
"stateMutability": "nonpayable"
},
{
"name": "symbol",
"type": "function",
"inputs": [],
"outputs": [
{
"name": "",
"type": "string",
"internalType": "string"
}
],
"stateMutability": "view"
},
{
"name": "taxHook",
"type": "function",
"inputs": [],
"outputs": [
{
"name": "",
"type": "address",
"internalType": "address"
}
],
"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": "v",
"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": "v",
"type": "uint256",
"internalType": "uint256"
}
],
"outputs": [
{
"name": "",
"type": "bool",
"internalType": "bool"
}
],
"stateMutability": "nonpayable"
},
{
"name": "transferOwnership",
"type": "function",
"inputs": [
{
"name": "newOwner",
"type": "address",
"internalType": "address"
}
],
"outputs": [],
"stateMutability": "nonpayable"
}
]0x6080604081815260049182361015610015575f80fd5b5f92833560e01c91826306fdde0314610d2b57508163095ea7b314610cbc57816318160ddd14610a075781631cce34ee14610c955781631d5b7c0814610b9f57816323b872dd14610acc578163313ce56714610ab057816334e20e3b14610a0c5781633eaaf86b14610a075781635d0044ca1461097e57816370a0823114610946578163715018a6146108d85781637284e4161461081957816379ba5097146107705781638da5cb5b1461074757816395d89b41146106475781639fde54f514610471578163a9059cbb14610440578163ab5a1887146103b4578163c3f70b5214610395578163cf9b21471461035a578163dd62ed3e1461030d57508063deae7cb6146102e5578063e30c3978146102bd578063e9a037df14610283578063f2fde38b14610230578063f5fc5489146101f3578063f8b45b05146101d55763f928364c14610161575f80fd5b346101d157816003193601126101d15760607fc3e35da80dd50e2a487eb09d9fd3dcfb815ed597fedfcbc93588da440b2d45a3916101aa60018060a01b03600554163314611097565b6007805460ff60a01b1916905560085460095482519182526020820152908101849052a180f35b5080fd5b50346101d157816003193601126101d1576020906008549051908152f35b50346101d15760203660031901126101d15760209160ff9082906001600160a01b0361021d610e65565b168152600a855220541690519015158152f35b82346102805760203660031901126102805761024a610e65565b6005546001600160a01b0391906102649083163314611097565b166bffffffffffffffffffffffff60a01b600654161760065580f35b80fd5b50346101d157816003193601126101d157602090517f0000000000000000000000000000000000000000000000003fbe3939db6309648152f35b50346101d157816003193601126101d15760065490516001600160a01b039091168152602090f35b50346101d157816003193601126101d15760075490516001600160a01b039091168152602090f35b905034610356578160031936011261035657602092829161032c610e65565b610334610e7f565b6001600160a01b03918216845291865283832091168252845220549051908152f35b8280fd5b5050346101d157816003193601126101d157602090517fbe223825c4804c7afd972a3bd3a08a69d121f85935216bdefabd21478e917ad38152f35b5050346101d157816003193601126101d1576020906009549051908152f35b91905034610356576020366003190112610356577fc3e35da80dd50e2a487eb09d9fd3dcfb815ed597fedfcbc93588da440b2d45a391359061040160018060a01b03600554163314611097565b61040f6009548310156110cf565b60098290556008546007549151908152602081019290925260a01c60ff161515604082015280606081015b0390a180f35b5050346101d157806003193601126101d15760209061046a610460610e65565b6024359033610ecf565b5160018152f35b90503461035657816003193601126103565761048b610e65565b90602435918215918215809403610643576005546001600160a01b0393906104b69085163314611097565b610503575b50916020917f1a4e65b5f8cbcaf438d3211a0ce9c99ba3c826628362671bf77215c0c2aaed24931693848652600a835280862060ff1981541660ff841617905551908152a280f35b8260075416838316908082141580610625575b156105e95780610528575b50506104bb565b6020839188519283809263e319a3d960e01b82525afa80156105df578591899161059e575b50161461055b578080610521565b606490602086519162461bcd60e51b8352820152601d60248201527f63616e6e6f7420756e2d6578656d7074207461782072656365697665720000006044820152fd5b9150506020813d82116105d7575b816105b960209383610de8565b810103126105d3575184811681036105d35784905f61054d565b8780fd5b3d91506105ac565b87513d8a823e3d90fd5b865162461bcd60e51b8152602081850152601660248201527563616e6e6f7420756e2d6578656d707420696e66726160501b6044820152606490fd5b50738366a39cc670b4001a1121b8f6a443a643e40951821415610516565b8580fd5b9190503461035657826003193601126103565780519183600180549182821c92828116801561073d575b602095868610821461072a575084885290811561070857506001146106b0575b6106ac86866106a2828b0383610de8565b5191829182610e1e565b0390f35b9295508083527fb10e2d527612073b26eecdfd717e6a320cf44b4afac2b0732d9fcbe2b7fa0cf65b8284106106f557505050826106ac946106a292820101945f610691565b80548685018801529286019281016106d8565b60ff191687860152505050151560051b83010192506106a2826106ac5f610691565b634e487b7160e01b845260229052602483fd5b93607f1693610671565b5050346101d157816003193601126101d15760055490516001600160a01b039091168152602090f35b919050346103565782600319360112610356576006546001600160a01b039290918383169190338390036107e2575050806005549384167f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e08680a36001600160a01b0319928316176005551660065580f35b906020606492519162461bcd60e51b835282015260116024820152703737ba103832b73234b7339037bbb732b960791b6044820152fd5b91905034610356578260031936011261035657805191836002549060019082821c9282811680156108ce575b602095868610821461072a57508488529081156107085750600114610875576106ac86866106a2828b0383610de8565b929550600283527f405787fa12a823e0f2b7631cc41b3ba8828b3321ca811111fa75cd3aa3bb5ace5b8284106108bb57505050826106ac946106a292820101945f610691565b805486850188015292860192810161089e565b93607f1693610845565b8334610280578060031936011261028057600554816001600160a01b038216610902338214611097565b7f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e08280a36bffffffffffffffffffffffff60a01b8091166005556006541660065580f35b5050346101d15760203660031901126101d15760209181906001600160a01b0361096e610e65565b1681526003845220549051908152f35b91905034610356576020366003190112610356577fc3e35da80dd50e2a487eb09d9fd3dcfb815ed597fedfcbc93588da440b2d45a391356109ca60018060a01b03600554163314611097565b6109d86008548210156110cf565b60088190556009546007549251918252602082015260a09190911c60ff1615156040820152806060810161043a565b610e95565b90503461035657602036600319011261035657610a27610e65565b600554916001600160a01b0380841692610a42338514611097565b16938415610a8357505082907f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e08580a36001600160a01b0319161760055580f35b906020606492519162461bcd60e51b8352820152600760248201526606f776e65723d360cc1b6044820152fd5b5050346101d157816003193601126101d1576020905160128152f35b8383346101d15760603660031901126101d157610ae7610e65565b610aef610e7f565b6001600160a01b038216808552602086815284862033875281528486205490956044359492858310610b705760018301610b32575b5050509061046a9291610ecf565b858303928311610b5d579086979861046a979282528952818120338252895220558594938780610b24565b634e487b7160e01b825260118952602482fd5b865162461bcd60e51b8152808a018990526009602482015268616c6c6f77616e636560b81b6044820152606490fd5b9190503461035657602036600319011261035657610bbb610e65565b6005546001600160a01b0390610bd49082163314611097565b60075491818316610c5f5716928315610c3357506001600160a01b0319168217600755818352600a6020528220805460ff191660011790557f5a93ab309c337b7720480ee6d468e5b4babc049660d36063104e047a4ed4ebbc8280a280f35b606490602084519162461bcd60e51b835282015260066024820152650686f6f6b3d360d41b6044820152fd5b835162461bcd60e51b8152602081870152601060248201526f1a1bdbdac8185b1c9958591e481cd95d60821b6044820152606490fd5b5050346101d157816003193601126101d15760209060ff60075460a01c1690519015158152f35b905034610356578160031936011261035657602092610cd9610e65565b918360243592839233825287528181209460018060a01b0316948582528752205582519081527f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925843392a35160018152f35b92915034610de45783600319360112610de4578354600181811c9186908281168015610dda575b602095868610821461072a57508488529081156107085750600114610d82576106ac86866106a2828b0383610de8565b8080949750527f290decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e5635b828410610dc757505050826106ac946106a292820101945f610691565b8054868501880152928601928101610daa565b93607f1693610d52565b8380fd5b90601f8019910116810190811067ffffffffffffffff821117610e0a57604052565b634e487b7160e01b5f52604160045260245ffd5b602080825282518183018190529093925f5b828110610e5157505060409293505f838284010152601f8019910116010190565b818101860151848201604001528501610e30565b600435906001600160a01b0382168203610e7b57565b5f80fd5b602435906001600160a01b0382168203610e7b57565b34610e7b575f366003190112610e7b5760206040517f0000000000000000000000000000000000000000033b2e3c9fd0803ce80000008152f35b6001600160a01b03918216929190831561106c5760ff60075460a01c16610f7f575b16905f8281526003602052604081205491808310610f5057604082827fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef95876020965260038652038282205586815220818154019055604051908152a3565b60405162461bcd60e51b815260206004820152600760248201526662616c616e636560c81b6044820152606490fd5b5f8282168152600a60205260409060ff828220541615801561105b575b611024575b858152600a60205260ff828220541615610fbd575b5050610ef1565b60036020528181205490858201809211611010575060085410610fe05780610fb6565b5162461bcd60e51b815260206004820152600a6024820152691b585e081dd85b1b195d60b21b6044820152606490fd5b634e487b7160e01b81526011600452602490fd5b600954851115610fa157815162461bcd60e51b81526020600482015260066024820152650dac2f040e8f60d31b6044820152606490fd5b5085815260ff828220541615610f9c565b606460405162461bcd60e51b81526020600482015260046024820152630746f3d360e41b6044820152fd5b1561109e57565b60405162461bcd60e51b81526020600482015260096024820152683737ba1037bbb732b960b91b6044820152606490fd5b156110d657565b60405162461bcd60e51b815260206004820152601160248201527006c696d697473206f6e652d77617920757607c1b6044820152606490fdfea164736f6c6343000814000a
| Token | Symbol | Balance | Price | Value |
|---|---|---|---|---|
| no token holdings | ||||
| Transaction Hash | Method | Block | Age | From | To | Type | Item | ||
|---|---|---|---|---|---|---|---|---|---|
| no NFT transfers for this address yet | |||||||||
| Type | Age | Block | Details |
|---|---|---|---|
| no cross-chain L1↔L2 transactions for this address | |||
| Transaction Hash | Method | Block | Age | From | To | Amount | Token | ||
|---|---|---|---|---|---|---|---|---|---|
| no token transfers for this address yet | |||||||||
| Txn Hash | Age | Event | Topics / Data |
|---|---|---|---|
| 0xfc5bb3…071e0e | 3 days agoFri, 14 Aug 2026 13:23:32 UTC | Approval | [0] 0x000000000000…5d4da9fe [1] 0x000000000000…3ac78ba3 data: 0xffffffffffffffffff…ffffffff |
| 0xccb9c8…333c2f | 3 days agoFri, 14 Aug 2026 13:23:32 UTC | Approval | [0] 0x000000000000…a2366640 [1] 0x000000000000…3ac78ba3 data: 0xffffffffffffffffff…ffffffff |
| 0x16fd41…c805df | 3 days agoFri, 14 Aug 2026 13:23:32 UTC | Approval | [0] 0x000000000000…6592a067 [1] 0x000000000000…3ac78ba3 data: 0xffffffffffffffffff…ffffffff |
| 0x729132…f2f234 | 3 days agoFri, 14 Aug 2026 13:23:31 UTC | Approval | [0] 0x000000000000…d9eba2a5 [1] 0x000000000000…3ac78ba3 data: 0xffffffffffffffffff…ffffffff |
| 0x1ff3e5…8512e7 | 3 days agoFri, 14 Aug 2026 13:23:31 UTC | Approval | [0] 0x000000000000…3cef69e2 [1] 0x000000000000…3ac78ba3 data: 0xffffffffffffffffff…ffffffff |
| 0x067a79…c32417 | 3 days agoFri, 14 Aug 2026 13:23:31 UTC | Approval | [0] 0x000000000000…fec95982 [1] 0x000000000000…3ac78ba3 data: 0xffffffffffffffffff…ffffffff |
| 0x898a90…4395bb | 3 days agoFri, 14 Aug 2026 13:23:31 UTC | Approval | [0] 0x000000000000…a5bd793d [1] 0x000000000000…3ac78ba3 data: 0xffffffffffffffffff…ffffffff |
| 0x157d83…3541ac | 3 days agoFri, 14 Aug 2026 13:23:31 UTC | Approval | [0] 0x000000000000…533537f0 [1] 0x000000000000…3ac78ba3 data: 0xffffffffffffffffff…ffffffff |
| 0xd3e9fe…dbabed | 3 days agoFri, 14 Aug 2026 13:23:31 UTC | Approval | [0] 0x000000000000…051c806d [1] 0x000000000000…3ac78ba3 data: 0xffffffffffffffffff…ffffffff |
| 0x5eb519…c18f97 | 3 days agoFri, 14 Aug 2026 13:23:31 UTC | Approval | [0] 0x000000000000…248813c9 [1] 0x000000000000…3ac78ba3 data: 0xffffffffffffffffff…ffffffff |
| 0x99346c…621be0 | 3 days agoFri, 14 Aug 2026 13:23:14 UTC | Transfer | [0] 0x000000000000…c7cf1075 [1] 0x000000000000…552bc9a6 data: 0x000000000000000000…f6801273 |
| 0x1ed562…3d8c98 | 3 days agoFri, 14 Aug 2026 13:21:31 UTC | Transfer | [0] 0x000000000000…43e40951 [1] 0x000000000000…552bc9a6 data: 0x000000000000000000…c7d305e5 |
| 0x2eac87…3a6e0d | 3 days agoFri, 14 Aug 2026 13:21:31 UTC | Transfer | [0] 0x000000000000…43e40951 [1] 0x000000000000…552bc9a6 data: 0x000000000000000000…f0e6d1bf |
| 0x2eac87…3a6e0d | 3 days agoFri, 14 Aug 2026 13:21:31 UTC | Transfer | [0] 0x000000000000…5be48752 [1] 0x000000000000…43e40951 data: 0x000000000000000000…08b8a4e3 |
| 0x1c6051…8e0ef9 | 3 days agoFri, 14 Aug 2026 13:21:31 UTC | Transfer | [0] 0x000000000000…43e40951 [1] 0x000000000000…552bc9a6 data: 0x000000000000000000…2526b45b |
| 0x1c6051…8e0ef9 | 3 days agoFri, 14 Aug 2026 13:21:31 UTC | Transfer | [0] 0x000000000000…e65b775e [1] 0x000000000000…43e40951 data: 0x000000000000000000…d65f7bf4 |
| 0x2367c4…a86e08 | 3 days agoFri, 14 Aug 2026 13:21:31 UTC | Approval | [0] 0x000000000000…e65b775e [1] 0x000000000000…262c40dc data: 0x000000000000000000…d65f7bf4 |
| 0xde9424…f387c1 | 3 days agoFri, 14 Aug 2026 13:21:31 UTC | Transfer | [0] 0x000000000000…43e40951 [1] 0x000000000000…552bc9a6 data: 0x000000000000000000…c4b20180 |
| 0xde9424…f387c1 | 3 days agoFri, 14 Aug 2026 13:21:31 UTC | Transfer | [0] 0x000000000000…fcaf0b1d [1] 0x000000000000…43e40951 data: 0x000000000000000000…472d8762 |
| 0xb2d167…30792d | 3 days agoFri, 14 Aug 2026 13:21:31 UTC | Approval | [0] 0x000000000000…fcaf0b1d [1] 0x000000000000…262c40dc data: 0x000000000000000000…472d8762 |
| 0xef9e2b…9302f3 | 3 days agoFri, 14 Aug 2026 13:21:31 UTC | Approval | [0] 0x000000000000…5be48752 [1] 0x000000000000…262c40dc data: 0x000000000000000000…08b8a4e3 |
| 0xe56fd0…731e14 | 3 days agoFri, 14 Aug 2026 13:21:24 UTC | Transfer | [0] 0x000000000000…43e40951 [1] 0x000000000000…f74748c2 data: 0x000000000000000000…a5097fb0 |
| 0x20785e…b09a72 | 3 days agoFri, 14 Aug 2026 13:21:18 UTC | Transfer | [0] 0x000000000000…43e40951 [1] 0x000000000000…e65b775e data: 0x000000000000000000…d65f7bf4 |
| 0x103f1c…ebb848 | 3 days agoFri, 14 Aug 2026 13:21:18 UTC | Transfer | [0] 0x000000000000…43e40951 [1] 0x000000000000…fcaf0b1d data: 0x000000000000000000…472d8762 |
| 0xa7047b…5f9c06 | 3 days agoFri, 14 Aug 2026 13:21:18 UTC | Transfer | [0] 0x000000000000…43e40951 [1] 0x000000000000…5be48752 data: 0x000000000000000000…08b8a4e3 |
| Transaction Hash | Method ? | Block | Age | From | To | Amount | Txn Fee ? | ||
|---|---|---|---|---|---|---|---|---|---|
| 0xfc5bb3…071e0e | Approve | 36,285,700 | 3 days agoFri, 14 Aug 2026 13:23:32 UTC | 0xdcda…a9fe | IN | Cr3dentials | $0.000 ETH | 0.00000185 | |
| 0xccb9c8…333c2f | Approve | 36,285,699 | 3 days agoFri, 14 Aug 2026 13:23:32 UTC | 0x025a…6640 | IN | Cr3dentials | $0.000 ETH | 0.00000186 | |
| 0x16fd41…c805df | Approve | 36,285,699 | 3 days agoFri, 14 Aug 2026 13:23:32 UTC | 0x311d…a067 | IN | Cr3dentials | $0.000 ETH | 0.00000186 | |
| 0x898a90…4395bb | Approve | 36,285,698 | 3 days agoFri, 14 Aug 2026 13:23:31 UTC | 0xd129…793d | IN | Cr3dentials | $0.000 ETH | 0.00000186 | |
| 0xd3e9fe…dbabed | Approve | 36,285,698 | 3 days agoFri, 14 Aug 2026 13:23:31 UTC | 0x9c67…806d | IN | Cr3dentials | $0.000 ETH | 0.00000186 | |
| 0x5eb519…c18f97 | Approve | 36,285,698 | 3 days agoFri, 14 Aug 2026 13:23:31 UTC | 0xbcf8…13c9 | IN | Cr3dentials | $0.000 ETH | 0.00000186 | |
| 0x729132…f2f234 | Approve | 36,285,698 | 3 days agoFri, 14 Aug 2026 13:23:31 UTC | 0x30c5…a2a5 | IN | Cr3dentials | $0.000 ETH | 0.00000186 | |
| 0x067a79…c32417 | Approve | 36,285,698 | 3 days agoFri, 14 Aug 2026 13:23:31 UTC | 0x788a…5982 | IN | Cr3dentials | $0.000 ETH | 0.00000186 | |
| 0x1ff3e5…8512e7 | Approve | 36,285,698 | 3 days agoFri, 14 Aug 2026 13:23:31 UTC | 0xf570…69e2 | IN | Cr3dentials | $0.000 ETH | 0.00000186 | |
| 0x157d83…3541ac | Approve | 36,285,698 | 3 days agoFri, 14 Aug 2026 13:23:31 UTC | 0x7525…37f0 | IN | Cr3dentials | $0.000 ETH | 0.00000186 | |
| 0x2367c4…a86e08 | Approve | 36,284,498 | 3 days agoFri, 14 Aug 2026 13:21:31 UTC | 0x6552…775e | IN | Cr3dentials | $0.000 ETH | 0.00000185 | |
| 0xb2d167…30792d | Approve | 36,284,498 | 3 days agoFri, 14 Aug 2026 13:21:31 UTC | 0x165b…0b1d | IN | Cr3dentials | $0.000 ETH | 0.00000185 | |
| 0xef9e2b…9302f3 | Approve | 36,284,498 | 3 days agoFri, 14 Aug 2026 13:21:31 UTC | 0x0ec2…8752 | IN | Cr3dentials | $0.000 ETH | 0.00000185 | |
| 0xeddcff…5dd953 | Approve | 36,284,317 | 3 days agoFri, 14 Aug 2026 13:21:13 UTC | 0x830e…e112 | IN | Cr3dentials | $0.000 ETH | 0.00000185 | |
| !0x3d0876…31bf85 | acceptOwnership | 36,284,237 | 3 days agoFri, 14 Aug 2026 13:21:05 UTC | 0x1649…c9a6 | IN | Cr3dentials | $0.000 ETH | 0.00000096 |
| Block | Age | Parent Transaction Hash | Type | Method | From | To | Value | |
|---|---|---|---|---|---|---|---|---|
| 36,284,184 | 3 days agoFri, 14 Aug 2026 13:20:59 UTC | 0xe00f21…dc27e0 | CREATE2 | 0x365ba8f9 | 0x7db5…1075 | IN | 0x1cad…d86f | 0 ETH |