RawbinFunV3

0x48fb55ee52914523773d619eb0fdfc5a687e0999

Verification
Verified
v0.8.24+commit.e11b9ed9
Type
Contract
17,058 bytes
ABI entries
38
20 read · 8 write
License
none

Contract information

Address
0x48fb55ee52914523773d619eb0fdfc5a687e0999
Chain
Robinhood Chain (4663)
Compiler
v0.8.24+commit.e11b9ed9
Optimization
Enabled
Creation tx
0x82c82b3cd7…c42d9465ce

Token

Not a token

This contract does not expose ERC-20 metadata.

Read contract (20)

FEE_TIMELOCK()uint256
MAX_CREATION_FEE()uint256
PLATFORM()string
PLATFORM_URL()string
TOTAL_SUPPLY()uint256
allTokens(uint256)address, address, address, string, uint256
allTokensLength()uint256
creationFee()uint256
defaultMigrationThreshold()uint256
defaultVirtualEthReserve()uint256
defaultVirtualTokenReserve()uint256
feeRecipient()address
feeRecipientEta()uint256
isCurve(address)bool
locker()address
npm()address
owner()address
pendingFeeRecipient()address
pendingOwner()address
predictAddresses(address, string, string, bytes32)address, address

Events (7)

CreationFeeUpdatedFeeRecipientCancelledFeeRecipientProposedFeeRecipientUpdatedOwnershipTransferStartedOwnershipTransferredTokenCreated

ABI

[
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "feeRecipient_",
        "type": "address"
      },
      {
        "internalType": "contract INonfungiblePositionManager",
        "name": "npm_",
        "type": "address"
      },
      {
        "internalType": "uint256",
        "name": "virtualEthReserve_",
        "type": "uint256"
      },
      {
        "internalType": "uint256",
        "name": "virtualTokenReserve_",
        "type": "uint256"
      },
      {
        "internalType": "uint256",
        "name": "migrationThreshold_",
        "type": "uint256"
      },
      {
        "internalType": "uint256",
        "name": "creationFee_",
        "type": "uint256"
      }
    ],
    "stateMutability": "nonpayable",
    "type": "constructor"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "owner",
        "type": "address"
      }
    ],
    "name": "OwnableInvalidOwner",
    "type": "error"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "account",
        "type": "address"
      }
    ],
    "name": "OwnableUnauthorizedAccount",
    "type": "error"
  },
  {
    "anonymous": false,
    "inputs": [
      {
        "indexed": false,
        "internalType": "uint256",
        "name": "newFee",
        "type": "uint256"
      }
    ],
    "name": "CreationFeeUpdated",
    "type": "event"
  },
  {
    "anonymous": false,
    "inputs": [
      {
        "indexed": true,
        "internalType": "address",
        "name": "cancelled",
        "type": "address"
      }
    ],
    "name": "FeeRecipientCancelled",
    "type": "event"
  },
  {
    "anonymous": false,
    "inputs": [
      {
        "indexed": true,
        "internalType": "address",
        "name": "next",
        "type": "address"
      },
      {
        "indexed": false,
        "internalType": "uint256",
        "name": "eta",
        "type": "uint256"
      }
    ],
    "name": "FeeRecipientProposed",
    "type": "event"
  },
  {
    "anonymous": false,
    "inputs": [
      {
        "indexed": true,
        "internalType": "address",
        "name": "prev",
        "type": "address"
      },
      {
        "indexed": true,
        "internalType": "address",
        "name": "next",
        "type": "address"
      }
    ],
    "name": "FeeRecipientUpdated",
    "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": true,
        "internalType": "address",
        "name": "token",
        "type": "address"
      },
      {
        "indexed": true,
        "internalType": "address",
        "name": "curve",
        "type": "address"
      },
      {
        "indexed": true,
        "internalType": "address",
        "name": "creator",
        "type": "address"
      },
      {
        "indexed": false,
        "internalType": "string",
        "name": "name",
        "type": "string"
      },
      {
        "indexed": false,
        "internalType": "string",
        "name": "symbol",
        "type": "string"
      },
      {
        "indexed": false,
        "internalType": "string",
        "name": "metadataURI",
        "type": "string"
      }
    ],
    "name": "TokenCreated",
    "type": "event"
  },
  {
    "inputs": [],
    "name": "FEE_TIMELOCK",
    "outputs": [
      {
        "internalType": "uint256",
        "name": "",
        "type": "uint256"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "MAX_CREATION_FEE",
    "outputs": [
      {
        "internalType": "uint256",
        "name": "",
        "type": "uint256"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "PLATFORM",
    "outputs": [
      {
        "internalType": "string",
        "name": "",
        "type": "string"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "PLATFORM_URL",
    "outputs": [
      {
        "internalType": "string",
        "name": "",
        "type": "string"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "TOTAL_SUPPLY",
    "outputs": [
      {
        "internalType": "uint256",
        "name": "",
        "type": "uint256"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "acceptOwnership",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "uint256",
        "name": "",
        "type": "uint256"
      }
    ],
    "name": "allTokens",
    "outputs": [
      {
        "internalType": "address",
        "name": "token",
        "type": "address"
      },
      {
        "internalType": "address",
        "name": "curve",
        "type": "address"
      },
      {
        "internalType": "address",
        "name": "creator",
        "type": "address"
      },
      {
        "internalType": "string",
        "name": "metadataURI",
        "type": "string"
      },
      {
        "internalType": "uint256",
        "name": "createdAt",
        "type": "uint256"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "allTokensLength",
    "outputs": [
      {
        "internalType": "uint256",
        "name": "",
        "type": "uint256"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "applyFeeRecipient",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "cancelFeeRecipient",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "string",
        "name": "name",
        "type": "string"
      },
      {
        "internalType": "string",
        "name": "symbol",
        "type": "string"
      },
      {
        "internalType": "string",
        "name": "metadataURI",
        "type": "string"
      },
      {
        "internalType": "bytes32",
        "name": "salt",
        "type": "bytes32"
      },
      {
        "internalType": "uint256",
        "name": "minTokensOut",
        "type": "uint256"
      }
    ],
    "name": "createToken",
    "outputs": [
      {
        "internalType": "address",
        "name": "tokenAddr",
        "type": "address"
      },
      {
        "internalType": "address",
        "name": "curveAddr",
        "type": "address"
      }
    ],
    "stateMutability": "payable",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "creationFee",
    "outputs": [
      {
        "internalType": "uint256",
        "name": "",
        "type": "uint256"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "defaultMigrationThreshold",
    "outputs": [
      {
        "internalType": "uint256",
        "name": "",
        "type": "uint256"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "defaultVirtualEthReserve",
    "outputs": [
      {
        "internalType": "uint256",
        "name": "",
        "type": "uint256"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "defaultVirtualTokenReserve",
    "outputs": [
      {
        "internalType": "uint256",
        "name": "",
        "type": "uint256"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "feeRecipient",
    "outputs": [
      {
        "internalType": "address",
        "name": "",
        "type": "address"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "feeRecipientEta",
    "outputs": [
      {
        "internalType": "uint256",
        "name": "",
        "type": "uint256"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "",
        "type": "address"
      }
    ],
    "name": "isCurve",
    "outputs": [
      {
        "internalType": "bool",
        "name": "",
        "type": "bool"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "locker",
    "outputs": [
      {
        "internalType": "address",
        "name": "",
        "type": "address"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "npm",
    "outputs": [
      {
        "internalType": "contract INonfungiblePositionManager",
        "name": "",
        "type": "address"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "owner",
    "outputs": [
      {
        "internalType": "address",
        "name": "",
        "type": "address"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "pendingFeeRecipient",
    "outputs": [
      {
        "internalType": "address",
        "name": "",
        "type": "address"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "pendingOwner",
    "outputs": [
      {
        "internalType": "address",
        "name": "",
        "type": "address"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "creator",
        "type": "address"
      },
      {
        "internalType": "string",
        "name": "name",
        "type": "string"
      },
      {
        "internalType": "string",
        "name": "symbol",
        "type": "string"
      },
      {
        "internalType": "bytes32",
        "name": "salt",
        "type": "bytes32"
      }
    ],
    "name": "predictAddresses",
    "outputs": [
      {
        "internalType": "address",
        "name": "tokenAddr",
        "type": "address"
      },
      {
        "internalType": "address",
        "name": "curveAddr",
        "type": "address"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "r",
        "type": "address"
      }
    ],
    "name": "proposeFeeRecipient",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "renounceOwnership",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "uint256",
        "name": "newFee",
        "type": "uint256"
      }
    ],
    "name": "setCreationFee",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "newOwner",
        "type": "address"
      }
    ],
    "name": "transferOwnership",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  }
]

Source code

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;

import "@openzeppelin/contracts/access/Ownable2Step.sol";
import "./LaunchToken.sol";
import "./BondingCurveV3.sol";
import "./RawbinLocker.sol";
import "./interfaces/IUniswapV3.sol";

/// @title RawbinFunV3
/// @author Rawbin — the #1 launchpad on Robinhood Chain
/// @custom:website https://rawbin.fun
/// @custom:mission We're building a transparent launchpad. Verified contracts, no
///  hidden owner over tokens, no token tax, liquidity you can never rug. Read the
///  code and verify everything yourself. Every launch is a drop. — rawbin
/// @notice One call deploys a LaunchToken + BondingCurveV3. On migration the curve
///         seeds a Uniswap V3 pool and locks the position in the Locker forever, so
///         trading fees keep flowing (ETH -> platform, token -> creator). The
///         platform fee recipient is owner-controlled behind a 24h timelock.
contract RawbinFunV3 is Ownable2Step {
    string public constant PLATFORM = "rawbin.fun";
    string public constant PLATFORM_URL = "https://rawbin.fun";

    uint256 public constant TOTAL_SUPPLY = 1_000_000_000 ether;

    /// @notice The Rawbin mark, enforced on-chain: every token address must end in
    ///         0x…999 (last 3 hex nibbles). A token that doesn't end in 999 was not
    ///         created here — the factory reverts otherwise, so fakes are impossible.
    uint160 internal constant VANITY_MASK = 0xFFF;
    uint160 internal constant VANITY_SUFFIX = 0x999;

    /// @notice Uniswap V3 position manager (canonical deploy on Robinhood Chain).
    INonfungiblePositionManager public immutable npm;
    /// @notice The Locker that custodies every migrated position NFT. Deployed by
    ///         this factory in its own constructor and immutable thereafter — the
    ///         factory↔locker wiring can never be changed (no admin power over it).
    address public immutable locker;

    // Curve economics — fixed at deploy, no setters.
    uint256 public immutable defaultVirtualEthReserve;
    uint256 public immutable defaultVirtualTokenReserve;
    uint256 public immutable defaultMigrationThreshold;

    /// @notice Where platform fees go (creation fee + curve 1% + the V3 ETH-side
    ///         fees). Mutable but behind a 24h timelock (propose -> apply).
    address public feeRecipient;
    address public pendingFeeRecipient;
    uint256 public feeRecipientEta; // 0 = nothing pending
    uint256 public constant FEE_TIMELOCK = 24 hours;

    uint256 public creationFee;
    uint256 public constant MAX_CREATION_FEE = 0.005 ether;

    /// @notice True for every BondingCurveV3 this factory deployed — the Locker uses
    ///         it to authorize lockPosition().
    mapping(address => bool) public isCurve;

    struct TokenInfo {
        address token;
        address curve;
        address creator;
        string metadataURI;
        uint256 createdAt;
    }
    TokenInfo[] public allTokens;

    event TokenCreated(
        address indexed token,
        address indexed curve,
        address indexed creator,
        string name,
        string symbol,
        string metadataURI
    );
    event CreationFeeUpdated(uint256 newFee);
    event FeeRecipientProposed(address indexed next, uint256 eta);
    event FeeRecipientUpdated(address indexed prev, address indexed next);
    event FeeRecipientCancelled(address indexed cancelled);

    constructor(
        address feeRecipient_,
        INonfungiblePositionManager npm_,
        uint256 virtualEthReserve_,
        uint256 virtualTokenReserve_,
        uint256 migrationThreshold_,
        uint256 creationFee_
    ) Ownable(feeRecipient_) {
        require(feeRecipient_ != address(0), "feeRecipient=0");
        require(virtualEthReserve_ > 0 && virtualTokenReserve_ > 0 && migrationThreshold_ > 0, "curve params=0");
        require(creationFee_ <= MAX_CREATION_FEE, "fee > cap");
        feeRecipient = feeRecipient_;
        npm = npm_;
        defaultVirtualEthReserve = virtualEthReserve_;
        defaultVirtualTokenReserve = virtualTokenReserve_;
        defaultMigrationThreshold = migrationThreshold_;
        creationFee = creationFee_;
        // Deploy the Locker now and wire it immutably — no setLocker, no window,
        // no admin power to point at a different locker later.
        locker = address(new RawbinLocker(npm_, IRawbinFactoryView(address(this))));
    }

    // ----------------------------------------------------------------- admin

    function setCreationFee(uint256 newFee) external onlyOwner {
        require(newFee <= MAX_CREATION_FEE, "fee > cap");
        creationFee = newFee;
        emit CreationFeeUpdated(newFee);
    }

    // --- feeRecipient change: 24h timelock (propose -> wait -> apply) ---

    function proposeFeeRecipient(address r) external onlyOwner {
        require(r != address(0), "feeRecipient=0");
        pendingFeeRecipient = r;
        feeRecipientEta = block.timestamp + FEE_TIMELOCK;
        emit FeeRecipientProposed(r, feeRecipientEta);
    }

    function applyFeeRecipient() external onlyOwner {
        // A 24h delay can't be meaningfully gamed by a validator's few-second
        // timestamp drift — block.timestamp is the correct clock here.
        // forge-lint: disable-next-line(block-timestamp)
        require(feeRecipientEta != 0 && block.timestamp >= feeRecipientEta, "timelock");
        emit FeeRecipientUpdated(feeRecipient, pendingFeeRecipient);
        feeRecipient = pendingFeeRecipient;
        pendingFeeRecipient = address(0);
        feeRecipientEta = 0;
    }

    function cancelFeeRecipient() external onlyOwner {
        emit FeeRecipientCancelled(pendingFeeRecipient);
        pendingFeeRecipient = address(0);
        feeRecipientEta = 0;
    }

    // -------------------------------------------------------------- create

    /// @param salt Vanity salt ground off-chain so the token address ends in 0x…99.
    ///        Namespaced by msg.sender so creators can never collide or grief.
    /// @param minTokensOut Slippage bound for the optional dev buy (value above the
    ///        creation fee buys tokens for the creator in the same tx).
    function createToken(
        string calldata name,
        string calldata symbol,
        string calldata metadataURI,
        bytes32 salt,
        uint256 minTokensOut
    ) external payable returns (address tokenAddr, address curveAddr) {
        require(msg.value >= creationFee, "creation fee not paid");

        {
            bytes32 nsSalt = keccak256(abi.encodePacked(msg.sender, salt));

            BondingCurveV3 curve = new BondingCurveV3{salt: nsSalt}(
                defaultVirtualEthReserve,
                defaultVirtualTokenReserve,
                defaultMigrationThreshold
            );
            isCurve[address(curve)] = true; // set before any external call
            LaunchToken token = new LaunchToken{salt: nsSalt}(name, symbol, TOTAL_SUPPLY, address(curve));

            // Enforce the Rawbin mark on-chain: the token address must end in 0x…999.
            // The off-chain grinder finds a salt that satisfies this; a bad salt reverts.
            require(uint160(address(token)) & VANITY_MASK == VANITY_SUFFIX, "not a 0x...999 address");

            curve.initialize(token, msg.sender);
            token.initializeMetadata(metadataURI);

            uint256 fee = creationFee;
            if (fee > 0) {
                (bool feeSent, ) = feeRecipient.call{value: fee}("");
                require(feeSent, "fee transfer failed");
            }
            if (msg.value > fee) {
                curve.buyFor{value: msg.value - fee}(msg.sender, minTokensOut);
            }

            tokenAddr = address(token);
            curveAddr = address(curve);
        }

        allTokens.push(TokenInfo({
            token: tokenAddr,
            curve: curveAddr,
            creator: msg.sender,
            metadataURI: metadataURI,
            createdAt: block.timestamp
        }));
        emit TokenCreated(tokenAddr, curveAddr, msg.sender, name, symbol, metadataURI);
    }

    function allTokensLength() external view returns (uint256) {
        return allTokens.length;
    }

    /// @notice Predicts the CREATE2 addresses for a given creator + salt (source of
    ///         truth for the off-chain vanity grinder).
    function predictAddresses(
        address creator,
        string calldata name,
        string calldata symbol,
        bytes32 salt
    ) external view returns (address tokenAddr, address curveAddr) {
        bytes32 nsSalt = keccak256(abi.encodePacked(creator, salt));

        bytes32 curveInitHash = keccak256(
            abi.encodePacked(
                type(BondingCurveV3).creationCode,
                abi.encode(defaultVirtualEthReserve, defaultVirtualTokenReserve, defaultMigrationThreshold)
            )
        );
        curveAddr = _create2Address(nsSalt, curveInitHash);

        bytes32 tokenInitHash = keccak256(
            abi.encodePacked(
                type(LaunchToken).creationCode,
                abi.encode(name, symbol, TOTAL_SUPPLY, curveAddr)
            )
        );
        tokenAddr = _create2Address(nsSalt, tokenInitHash);
    }

    function _create2Address(bytes32 salt, bytes32 initCodeHash) internal view returns (address) {
        return address(uint160(uint256(
            keccak256(abi.encodePacked(bytes1(0xff), address(this), salt, initCodeHash))
        )));
    }
}
Chain explorer5140msChain node82ms