LongLauncher
0x22e99278308b393ea1260859b181ad7e78f5eeed
Verification
Verified
v0.8.26+commit.8a97fa7a
Type
Contract
5,826 bytes
ABI entries
42
10 read · 7 write
License
none
Contract information
- Address
- 0x22e99278308b393ea1260859b181ad7e78f5eeed
- Chain
- Robinhood Chain (4663)
- Compiler
- v0.8.26+commit.8a97fa7a
- Optimization
- Enabled
- Creator
- 0x1Ae51740cE…1bdAAe5305
- Creation tx
- 0x717af93c07…0732947cf9
Token
Not a token
This contract does not expose ERC-20 metadata.
Read contract (10)
AIRLOCK() → address
RESERVATION_DURATION() → uint48
TRUSTED_TOKEN_FACTORY() → address
getTickerRecord(string) → tuple
isTickerAvailable(string) → bool
owner() → address
paused() → bool
pendingOwner() → address
renounceOwnership()
tickerKey(string) → bytes32
Events (7)
ERC20SweptLaunchCreatedNativeSweptOwnershipTransferStartedOwnershipTransferredPausedUnpaused
ABI
[
{
"inputs": [
{
"internalType": "address",
"name": "airlock_",
"type": "address"
},
{
"internalType": "address",
"name": "trustedTokenFactory_",
"type": "address"
},
{
"internalType": "address",
"name": "initialOwner",
"type": "address"
}
],
"stateMutability": "nonpayable",
"type": "constructor"
},
{
"inputs": [
{
"internalType": "bytes32",
"name": "submittedKey",
"type": "bytes32"
},
{
"internalType": "bytes32",
"name": "deployedKey",
"type": "bytes32"
}
],
"name": "DeployedSymbolMismatch",
"type": "error"
},
{
"inputs": [],
"name": "EnforcedPause",
"type": "error"
},
{
"inputs": [],
"name": "ExpectedPause",
"type": "error"
},
{
"inputs": [
{
"internalType": "uint256",
"name": "index",
"type": "uint256"
},
{
"internalType": "bytes1",
"name": "character",
"type": "bytes1"
}
],
"name": "InvalidTickerCharacter",
"type": "error"
},
{
"inputs": [
{
"internalType": "uint256",
"name": "length",
"type": "uint256"
}
],
"name": "InvalidTickerLength",
"type": "error"
},
{
"inputs": [],
"name": "MalformedTokenData",
"type": "error"
},
{
"inputs": [],
"name": "NativeTransferFailed",
"type": "error"
},
{
"inputs": [
{
"internalType": "address",
"name": "owner",
"type": "address"
}
],
"name": "OwnableInvalidOwner",
"type": "error"
},
{
"inputs": [
{
"internalType": "address",
"name": "account",
"type": "address"
}
],
"name": "OwnableUnauthorizedAccount",
"type": "error"
},
{
"inputs": [],
"name": "OwnershipRenunciationDisabled",
"type": "error"
},
{
"inputs": [],
"name": "ReentrancyGuardReentrantCall",
"type": "error"
},
{
"inputs": [
{
"internalType": "address",
"name": "token",
"type": "address"
}
],
"name": "SafeERC20FailedOperation",
"type": "error"
},
{
"inputs": [
{
"internalType": "bytes32",
"name": "tickerKey",
"type": "bytes32"
},
{
"internalType": "address",
"name": "token",
"type": "address"
},
{
"internalType": "uint48",
"name": "reservedUntil",
"type": "uint48"
}
],
"name": "TickerReserved",
"type": "error"
},
{
"inputs": [],
"name": "TimestampOverflow",
"type": "error"
},
{
"inputs": [
{
"internalType": "address",
"name": "supplied",
"type": "address"
},
{
"internalType": "address",
"name": "expected",
"type": "address"
}
],
"name": "UnsupportedTokenFactory",
"type": "error"
},
{
"inputs": [],
"name": "ZeroAddress",
"type": "error"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "address",
"name": "token",
"type": "address"
},
{
"indexed": true,
"internalType": "address",
"name": "recipient",
"type": "address"
},
{
"indexed": false,
"internalType": "uint256",
"name": "amount",
"type": "uint256"
}
],
"name": "ERC20Swept",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "address",
"name": "poolOrHook",
"type": "address"
},
{
"indexed": true,
"internalType": "address",
"name": "asset",
"type": "address"
},
{
"indexed": true,
"internalType": "address",
"name": "numeraire",
"type": "address"
},
{
"indexed": false,
"internalType": "address",
"name": "poolInitializer",
"type": "address"
},
{
"indexed": false,
"internalType": "address",
"name": "launcher",
"type": "address"
},
{
"indexed": false,
"internalType": "bytes32",
"name": "tickerKey",
"type": "bytes32"
},
{
"indexed": false,
"internalType": "uint48",
"name": "deployedAt",
"type": "uint48"
},
{
"indexed": false,
"internalType": "uint48",
"name": "reservedUntil",
"type": "uint48"
},
{
"indexed": false,
"internalType": "string",
"name": "normalizedTicker",
"type": "string"
}
],
"name": "LaunchCreated",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "address",
"name": "recipient",
"type": "address"
},
{
"indexed": false,
"internalType": "uint256",
"name": "amount",
"type": "uint256"
}
],
"name": "NativeSwept",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "address",
"name": "previousOwner",
"type": "address"
},
{
"indexed": true,
"internalType": "address",
"name": "newOwner",
"type": "address"
}
],
"name": "OwnershipTransferStarted",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "address",
"name": "previousOwner",
"type": "address"
},
{
"indexed": true,
"internalType": "address",
"name": "newOwner",
"type": "address"
}
],
"name": "OwnershipTransferred",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": false,
"internalType": "address",
"name": "account",
"type": "address"
}
],
"name": "Paused",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": false,
"internalType": "address",
"name": "account",
"type": "address"
}
],
"name": "Unpaused",
"type": "event"
},
{
"inputs": [],
"name": "AIRLOCK",
"outputs": [
{
"internalType": "contract IAirlock",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "RESERVATION_DURATION",
"outputs": [
{
"internalType": "uint48",
"name": "",
"type": "uint48"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "TRUSTED_TOKEN_FACTORY",
"outputs": [
{
"internalType": "address",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "acceptOwnership",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"components": [
{
"internalType": "uint256",
"name": "initialSupply",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "numTokensToSell",
"type": "uint256"
},
{
"internalType": "address",
"name": "numeraire",
"type": "address"
},
{
"internalType": "address",
"name": "tokenFactory",
"type": "address"
},
{
"internalType": "bytes",
"name": "tokenFactoryData",
"type": "bytes"
},
{
"internalType": "address",
"name": "governanceFactory",
"type": "address"
},
{
"internalType": "bytes",
"name": "governanceFactoryData",
"type": "bytes"
},
{
"internalType": "address",
"name": "poolInitializer",
"type": "address"
},
{
"internalType": "bytes",
"name": "poolInitializerData",
"type": "bytes"
},
{
"internalType": "address",
"name": "liquidityMigrator",
"type": "address"
},
{
"internalType": "bytes",
"name": "liquidityMigratorData",
"type": "bytes"
},
{
"internalType": "address",
"name": "integrator",
"type": "address"
},
{
"internalType": "bytes32",
"name": "salt",
"type": "bytes32"
}
],
"internalType": "struct IAirlock.CreateParams",
"name": "data",
"type": "tuple"
}
],
"name": "create",
"outputs": [
{
"internalType": "address",
"name": "asset",
"type": "address"
},
{
"internalType": "address",
"name": "pool",
"type": "address"
},
{
"internalType": "address",
"name": "governance",
"type": "address"
},
{
"internalType": "address",
"name": "timelock",
"type": "address"
},
{
"internalType": "address",
"name": "migrationPool",
"type": "address"
}
],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "string",
"name": "ticker",
"type": "string"
}
],
"name": "getTickerRecord",
"outputs": [
{
"components": [
{
"internalType": "address",
"name": "token",
"type": "address"
},
{
"internalType": "uint48",
"name": "deployedAt",
"type": "uint48"
},
{
"internalType": "uint48",
"name": "reservedUntil",
"type": "uint48"
}
],
"internalType": "struct LongLauncher.TickerRecord",
"name": "",
"type": "tuple"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "string",
"name": "ticker",
"type": "string"
}
],
"name": "isTickerAvailable",
"outputs": [
{
"internalType": "bool",
"name": "",
"type": "bool"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "owner",
"outputs": [
{
"internalType": "address",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "pause",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [],
"name": "paused",
"outputs": [
{
"internalType": "bool",
"name": "",
"type": "bool"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "pendingOwner",
"outputs": [
{
"internalType": "address",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "renounceOwnership",
"outputs": [],
"stateMutability": "pure",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "token",
"type": "address"
},
{
"internalType": "address",
"name": "recipient",
"type": "address"
},
{
"internalType": "uint256",
"name": "amount",
"type": "uint256"
}
],
"name": "sweepERC20",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "address payable",
"name": "recipient",
"type": "address"
},
{
"internalType": "uint256",
"name": "amount",
"type": "uint256"
}
],
"name": "sweepNative",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "string",
"name": "ticker",
"type": "string"
}
],
"name": "tickerKey",
"outputs": [
{
"internalType": "bytes32",
"name": "key",
"type": "bytes32"
}
],
"stateMutability": "pure",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "newOwner",
"type": "address"
}
],
"name": "transferOwnership",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [],
"name": "unpause",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"stateMutability": "payable",
"type": "receive"
}
]Source code
// SPDX-License-Identifier: BUSL-1.1
// Copyright (c) 2026 long.xyz. All rights reserved.
/*=====================================================================================================
*
* _ ___ _ _ ____ _ _ _ _ _ _ ____ _ _ _____ ____
* | | / _ \ | \ | | / ___| | | / \ | | | | | \ | | / ___| | | | | | ____| | _ \
* | | | | | | | \| | | | _ | | / _ \ | | | | | \| | | | | |_| | | _| | |_) |
* | |___ | |_| | | |\ | | |_| | | |___ / ___ \ | |_| | | |\ | | |___ | _ | | |___ | _ <
* |_____| \___/ |_| \_| \____| |_____| /_/ \_\ \___/ |_| \_| \____| |_| |_| |_____| |_| \_\
*
* T O K E N I Z E D S T O C K S / / O N C H A I N
*
* [ AIRLOCK ROUTER ]------[ 24H TICKER LOCK ]------[ ROBINHOOD CHAIN ]
*
* PRICE
* ^ | | /\
* | | -+- | -+- /\ /\ / \ /\
* | -+- | -+- | ____/ \____/ \__/ \________/ \____>
* | | |
* +---------------------------------------------------------------------------------> TIME
*
* P L A Y
*
* L .--------. .--------. N N GGG
* L / \__/ \ NN N G
* L \ / \ / N N N G GG
* LLLLL '--------' '--------' N NN GGG
*
* T E R M G A M E S .
*
*====================================================================================================*/
pragma solidity ^0.8.26;
import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol";
import {Ownable2Step} from "@openzeppelin/contracts/access/Ownable2Step.sol";
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import {IERC20Metadata} from "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol";
import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import {Pausable} from "@openzeppelin/contracts/utils/Pausable.sol";
import {ReentrancyGuard} from "@openzeppelin/contracts/utils/ReentrancyGuard.sol";
import {IAirlock} from "./interfaces/IAirlock.sol";
/// @title LongLauncher
/// @author @natan_benish
/// @notice Airlock-compatible launch router that reserves normalized token tickers for 24 hours.
contract LongLauncher is Ownable2Step, Pausable, ReentrancyGuard {
using SafeERC20 for IERC20;
/// @dev Timestamp-based wall-clock duration; independent of Robinhood's ~100 ms block cadence.
uint48 public constant RESERVATION_DURATION = 24 hours;
uint256 internal constant DOPPLER_ERC20_V1_TOKEN_DATA_HEAD_SIZE = 11 * 32;
uint256 internal constant MAX_TICKER_LENGTH = 15;
IAirlock public immutable AIRLOCK;
address public immutable TRUSTED_TOKEN_FACTORY;
struct TickerRecord {
address token;
uint48 deployedAt;
uint48 reservedUntil;
}
mapping(bytes32 tickerKey => TickerRecord record) private _records;
error ZeroAddress();
error UnsupportedTokenFactory(address supplied, address expected);
error MalformedTokenData();
error InvalidTickerLength(uint256 length);
error InvalidTickerCharacter(uint256 index, bytes1 character);
error TickerReserved(bytes32 tickerKey, address token, uint48 reservedUntil);
error DeployedSymbolMismatch(bytes32 submittedKey, bytes32 deployedKey);
error TimestampOverflow();
error NativeTransferFailed();
error OwnershipRenunciationDisabled();
event LaunchCreated(
address indexed poolOrHook,
address indexed asset,
address indexed numeraire,
address poolInitializer,
address launcher,
bytes32 tickerKey,
uint48 deployedAt,
uint48 reservedUntil,
string normalizedTicker
);
event NativeSwept(address indexed recipient, uint256 amount);
event ERC20Swept(address indexed token, address indexed recipient, uint256 amount);
constructor(address airlock_, address trustedTokenFactory_, address initialOwner) Ownable(initialOwner) {
if (airlock_ == address(0) || trustedTokenFactory_ == address(0)) revert ZeroAddress();
AIRLOCK = IAirlock(airlock_);
TRUSTED_TOKEN_FACTORY = trustedTokenFactory_;
}
receive() external payable {}
/// @notice Forwards an unchanged Airlock create request after enforcing ticker availability.
function create(IAirlock.CreateParams calldata data)
external
whenNotPaused
nonReentrant
returns (address asset, address pool, address governance, address timelock, address migrationPool)
{
if (data.tokenFactory != TRUSTED_TOKEN_FACTORY) {
revert UnsupportedTokenFactory(data.tokenFactory, TRUSTED_TOKEN_FACTORY);
}
(bytes32 key, string memory normalizedTicker) = _tickerFromTokenFactoryData(data.tokenFactoryData);
TickerRecord memory current = _records[key];
if (block.timestamp < current.reservedUntil) {
revert TickerReserved(key, current.token, current.reservedUntil);
}
(asset, pool, governance, timelock, migrationPool) = AIRLOCK.create(data);
_registerSuccessfulLaunch(data, asset, pool, key, normalizedTicker, msg.sender);
}
function tickerKey(string calldata ticker) external pure returns (bytes32 key) {
(key,) = _normalizeTicker(bytes(ticker));
}
function getTickerRecord(string calldata ticker) external view returns (TickerRecord memory) {
(bytes32 key,) = _normalizeTicker(bytes(ticker));
return _records[key];
}
function isTickerAvailable(string calldata ticker) external view returns (bool) {
(bytes32 key,) = _normalizeTicker(bytes(ticker));
return block.timestamp >= _records[key].reservedUntil;
}
function pause() external onlyOwner {
_pause();
}
function unpause() external onlyOwner {
_unpause();
}
function sweepNative(address payable recipient, uint256 amount) external onlyOwner nonReentrant {
if (recipient == address(0)) revert ZeroAddress();
(bool success,) = recipient.call{value: amount}("");
if (!success) revert NativeTransferFailed();
emit NativeSwept(recipient, amount);
}
function sweepERC20(address token, address recipient, uint256 amount) external onlyOwner nonReentrant {
if (token == address(0) || recipient == address(0)) revert ZeroAddress();
IERC20(token).safeTransfer(recipient, amount);
emit ERC20Swept(token, recipient, amount);
}
function renounceOwnership() public pure override {
revert OwnershipRenunciationDisabled();
}
function _registerSuccessfulLaunch(
IAirlock.CreateParams calldata data,
address asset,
address pool,
bytes32 key,
string memory normalizedTicker,
address launcher
) internal {
(bytes32 deployedKey,) = _normalizeTicker(bytes(IERC20Metadata(asset).symbol()));
if (deployedKey != key) revert DeployedSymbolMismatch(key, deployedKey);
uint256 reservedUntil256 = block.timestamp + RESERVATION_DURATION;
if (reservedUntil256 > type(uint48).max) revert TimestampOverflow();
uint48 deployedAt = uint48(block.timestamp);
uint48 reservedUntil = uint48(reservedUntil256);
_records[key] = TickerRecord({token: asset, deployedAt: deployedAt, reservedUntil: reservedUntil});
emit LaunchCreated(
pool,
asset,
data.numeraire,
data.poolInitializer,
launcher,
key,
deployedAt,
reservedUntil,
normalizedTicker
);
}
/// @dev The V1 payload is abi.encode(name, symbol, schedules, beneficiaries, scheduleIds,
/// amounts, tokenURI, maxBalanceLimit, balanceLimitEnd, controller, exclusions).
function _tickerFromTokenFactoryData(bytes calldata tokenData)
internal
pure
returns (bytes32 key, string memory normalizedTicker)
{
if (tokenData.length < DOPPLER_ERC20_V1_TOKEN_DATA_HEAD_SIZE) revert MalformedTokenData();
uint256 symbolOffset;
assembly ("memory-safe") {
symbolOffset := calldataload(add(tokenData.offset, 0x20))
}
if (
symbolOffset < DOPPLER_ERC20_V1_TOKEN_DATA_HEAD_SIZE || symbolOffset & 31 != 0
|| symbolOffset > tokenData.length - 32
) {
revert MalformedTokenData();
}
uint256 length;
assembly ("memory-safe") {
length := calldataload(add(tokenData.offset, symbolOffset))
}
uint256 contentOffset = symbolOffset + 32;
if (contentOffset > tokenData.length || length > tokenData.length - contentOffset) {
revert MalformedTokenData();
}
if (length == 0 || length > MAX_TICKER_LENGTH) revert InvalidTickerLength(length);
bytes memory ticker = new bytes(length);
assembly ("memory-safe") {
calldatacopy(add(ticker, 0x20), add(tokenData.offset, contentOffset), length)
}
return _normalizeTicker(ticker);
}
function _normalizeTicker(bytes memory ticker)
internal
pure
returns (bytes32 key, string memory normalizedTicker)
{
uint256 length = ticker.length;
if (length == 0 || length > MAX_TICKER_LENGTH) revert InvalidTickerLength(length);
for (uint256 i; i < length; ++i) {
uint8 character = uint8(ticker[i]);
if (character >= 97 && character <= 122) {
ticker[i] = bytes1(character - 32);
} else if (character < 65 || character > 90) {
revert InvalidTickerCharacter(i, ticker[i]);
}
}
key = keccak256(ticker);
normalizedTicker = string(ticker);
}
}
Chain explorer1800msChain node78ms