StandardTokenLaunchRequirements
0xb66009788ea741b366ba852762a2821b1f3f7418
Verification
Verified
v0.8.24+commit.e11b9ed9
Type
Contract
6,240 bytes
ABI entries
45
10 read · 9 write
License
none
Contract information
- Address
- 0xb66009788ea741b366ba852762a2821b1f3f7418
- Chain
- Robinhood Chain (4663)
- Compiler
- v0.8.24+commit.e11b9ed9
- Optimization
- Enabled
- Creator
- 0xd03543d97B…F446Cd1418
- Creation tx
- 0x9b0c6422f8…5fdb288e31
Token
Not a token
This contract does not expose ERC-20 metadata.
Read contract (10)
bondLockDuration() → uint64
creatorBondAmount() → uint256
factory() → address
getReservation(uint256) → tuple
lifecycleManager() → address
owner() → address
pendingOwner() → address
reservationKey(uint256) → bytes32
staking() → address
templateRegistry() → address
Events (9)
CreatorBondConfiguredCreatorBondLockedCreatorBondReturnedEquiStakeReleasedEquiStakeReservedFactoryConfiguredLifecycleManagerConfiguredOwnershipTransferStartedOwnershipTransferred
ABI
[
{
"inputs": [
{
"internalType": "address",
"name": "initialOwner",
"type": "address"
},
{
"internalType": "contract EquifoldStaking",
"name": "staking_",
"type": "address"
},
{
"internalType": "contract LaunchTemplateRegistry",
"name": "templateRegistry_",
"type": "address"
}
],
"stateMutability": "nonpayable",
"type": "constructor"
},
{
"inputs": [],
"name": "AlreadyConfigured",
"type": "error"
},
{
"inputs": [],
"name": "AlreadyReserved",
"type": "error"
},
{
"inputs": [],
"name": "BondAlreadyReturned",
"type": "error"
},
{
"inputs": [],
"name": "BondStillLocked",
"type": "error"
},
{
"inputs": [],
"name": "BondTransferMismatch",
"type": "error"
},
{
"inputs": [],
"name": "InvalidAddress",
"type": "error"
},
{
"inputs": [],
"name": "InvalidBondConfig",
"type": "error"
},
{
"inputs": [],
"name": "InvalidProject",
"type": "error"
},
{
"inputs": [],
"name": "NoBondEscrowed",
"type": "error"
},
{
"inputs": [],
"name": "NotCreator",
"type": "error"
},
{
"inputs": [],
"name": "NotFactory",
"type": "error"
},
{
"inputs": [],
"name": "NotLifecycleManager",
"type": "error"
},
{
"inputs": [],
"name": "NotReserved",
"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": [
{
"internalType": "address",
"name": "token",
"type": "address"
}
],
"name": "SafeERC20FailedOperation",
"type": "error"
},
{
"anonymous": false,
"inputs": [
{
"indexed": false,
"internalType": "uint256",
"name": "amount",
"type": "uint256"
},
{
"indexed": false,
"internalType": "uint64",
"name": "lockDuration",
"type": "uint64"
}
],
"name": "CreatorBondConfigured",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "uint256",
"name": "projectNumber",
"type": "uint256"
},
{
"indexed": true,
"internalType": "address",
"name": "creator",
"type": "address"
},
{
"indexed": false,
"internalType": "uint256",
"name": "amount",
"type": "uint256"
},
{
"indexed": false,
"internalType": "uint64",
"name": "unlockAt",
"type": "uint64"
}
],
"name": "CreatorBondLocked",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "uint256",
"name": "projectNumber",
"type": "uint256"
},
{
"indexed": true,
"internalType": "address",
"name": "creator",
"type": "address"
},
{
"indexed": false,
"internalType": "uint256",
"name": "amount",
"type": "uint256"
}
],
"name": "CreatorBondReturned",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "uint256",
"name": "projectNumber",
"type": "uint256"
},
{
"indexed": true,
"internalType": "address",
"name": "creator",
"type": "address"
},
{
"indexed": true,
"internalType": "bytes32",
"name": "reservationKey",
"type": "bytes32"
},
{
"indexed": false,
"internalType": "uint256",
"name": "amount",
"type": "uint256"
}
],
"name": "EquiStakeReleased",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "uint256",
"name": "projectNumber",
"type": "uint256"
},
{
"indexed": true,
"internalType": "address",
"name": "creator",
"type": "address"
},
{
"indexed": true,
"internalType": "bytes32",
"name": "reservationKey",
"type": "bytes32"
},
{
"indexed": false,
"internalType": "uint256",
"name": "amount",
"type": "uint256"
}
],
"name": "EquiStakeReserved",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "address",
"name": "factory",
"type": "address"
}
],
"name": "FactoryConfigured",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "address",
"name": "lifecycleManager",
"type": "address"
}
],
"name": "LifecycleManagerConfigured",
"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"
},
{
"inputs": [],
"name": "acceptOwnership",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [],
"name": "bondLockDuration",
"outputs": [
{
"internalType": "uint64",
"name": "",
"type": "uint64"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "uint256",
"name": "projectNumber",
"type": "uint256"
}
],
"name": "claimExpiredBond",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [],
"name": "creatorBondAmount",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "factory",
"outputs": [
{
"internalType": "address",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "uint256",
"name": "projectNumber",
"type": "uint256"
}
],
"name": "getReservation",
"outputs": [
{
"components": [
{
"internalType": "address",
"name": "creator",
"type": "address"
},
{
"internalType": "uint256",
"name": "amount",
"type": "uint256"
},
{
"internalType": "bytes32",
"name": "reservationKey",
"type": "bytes32"
},
{
"internalType": "bool",
"name": "released",
"type": "bool"
},
{
"internalType": "uint256",
"name": "bondAmount",
"type": "uint256"
},
{
"internalType": "uint64",
"name": "bondUnlockAt",
"type": "uint64"
},
{
"internalType": "bool",
"name": "bondReturned",
"type": "bool"
}
],
"internalType": "struct StandardTokenLaunchRequirements.LaunchReservation",
"name": "",
"type": "tuple"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "lifecycleManager",
"outputs": [
{
"internalType": "address",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "owner",
"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": "uint256",
"name": "projectNumber",
"type": "uint256"
}
],
"name": "releaseLaunch",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [],
"name": "renounceOwnership",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "uint256",
"name": "projectNumber",
"type": "uint256"
}
],
"name": "reservationKey",
"outputs": [
{
"internalType": "bytes32",
"name": "",
"type": "bytes32"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "creator",
"type": "address"
},
{
"internalType": "uint256",
"name": "projectNumber",
"type": "uint256"
},
{
"internalType": "bytes32",
"name": "templateKey",
"type": "bytes32"
},
{
"internalType": "uint32",
"name": "templateVersion",
"type": "uint32"
}
],
"name": "reserveLaunch",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "uint256",
"name": "amount",
"type": "uint256"
},
{
"internalType": "uint64",
"name": "lockDuration",
"type": "uint64"
}
],
"name": "setCreatorBond",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "factory_",
"type": "address"
}
],
"name": "setFactory",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "lifecycleManager_",
"type": "address"
}
],
"name": "setLifecycleManager",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [],
"name": "staking",
"outputs": [
{
"internalType": "contract EquifoldStaking",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "templateRegistry",
"outputs": [
{
"internalType": "contract LaunchTemplateRegistry",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"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 { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import { Ownable2Step, Ownable } from "@openzeppelin/contracts/access/Ownable2Step.sol";
import { ILaunchRequirements } from "../interfaces/ILaunchRequirements.sol";
import { EquifoldStaking } from "./EquifoldStaking.sol";
import { LaunchTemplateRegistry } from "./LaunchTemplateRegistry.sol";
/// @title Standard-token EQUI launch requirements
/// @notice Reserves the template-defined external EQUI stake through a product-neutral key, and
/// escrows a refundable EQUI creator bond (skin-in-the-game) that is returned at
/// graduation or reclaimable by the creator once the lock expires.
contract StandardTokenLaunchRequirements is ILaunchRequirements, Ownable2Step {
using SafeERC20 for IERC20;
error AlreadyConfigured();
error AlreadyReserved();
error BondAlreadyReturned();
error BondStillLocked();
error BondTransferMismatch();
error InvalidAddress();
error InvalidBondConfig();
error InvalidProject();
error NoBondEscrowed();
error NotCreator();
error NotFactory();
error NotLifecycleManager();
error NotReserved();
uint64 internal constant MAX_BOND_LOCK_DURATION = 365 days;
struct LaunchReservation {
address creator;
uint256 amount;
bytes32 reservationKey;
bool released;
uint256 bondAmount;
uint64 bondUnlockAt;
bool bondReturned;
}
EquifoldStaking public immutable staking;
LaunchTemplateRegistry public immutable templateRegistry;
address public factory;
address public lifecycleManager;
/// @notice EQUI bond pulled from the creator at launch. Zero disables the bond entirely.
uint256 public creatorBondAmount;
/// @notice How long the bond stays locked before the creator can self-reclaim a never-graduated launch.
uint64 public bondLockDuration;
mapping(uint256 projectNumber => LaunchReservation reservation) private _reservations;
event FactoryConfigured(address indexed factory);
event LifecycleManagerConfigured(address indexed lifecycleManager);
event CreatorBondConfigured(uint256 amount, uint64 lockDuration);
event CreatorBondLocked(
uint256 indexed projectNumber, address indexed creator, uint256 amount, uint64 unlockAt
);
event CreatorBondReturned(
uint256 indexed projectNumber, address indexed creator, uint256 amount
);
event EquiStakeReserved(
uint256 indexed projectNumber,
address indexed creator,
bytes32 indexed reservationKey,
uint256 amount
);
event EquiStakeReleased(
uint256 indexed projectNumber,
address indexed creator,
bytes32 indexed reservationKey,
uint256 amount
);
constructor(
address initialOwner,
EquifoldStaking staking_,
LaunchTemplateRegistry templateRegistry_
) Ownable(initialOwner) {
if (
initialOwner == address(0) || address(staking_) == address(0)
|| address(templateRegistry_) == address(0) || address(staking_).code.length == 0
|| address(templateRegistry_).code.length == 0
) revert InvalidAddress();
staking = staking_;
templateRegistry = templateRegistry_;
}
function setFactory(
address factory_
) external onlyOwner {
if (factory != address(0)) revert AlreadyConfigured();
_validateContract(factory_);
factory = factory_;
emit FactoryConfigured(factory_);
}
function setLifecycleManager(
address lifecycleManager_
) external onlyOwner {
if (lifecycleManager != address(0)) revert AlreadyConfigured();
_validateContract(lifecycleManager_);
lifecycleManager = lifecycleManager_;
emit LifecycleManagerConfigured(lifecycleManager_);
}
/// @notice Sets the refundable EQUI creator bond. `amount` of 0 disables the bond. When enabled,
/// `lockDuration` must be in (0, 365 days]; it bounds how long a never-graduated launch's
/// bond stays locked before the creator can self-reclaim it.
function setCreatorBond(
uint256 amount,
uint64 lockDuration
) external onlyOwner {
if (amount != 0 && (lockDuration == 0 || lockDuration > MAX_BOND_LOCK_DURATION)) {
revert InvalidBondConfig();
}
creatorBondAmount = amount;
bondLockDuration = amount == 0 ? 0 : lockDuration;
emit CreatorBondConfigured(amount, bondLockDuration);
}
function reserveLaunch(
address creator,
uint256 projectNumber,
bytes32 templateKey,
uint32 templateVersion
) external {
if (msg.sender != factory) revert NotFactory();
if (creator == address(0) || projectNumber == 0) revert InvalidProject();
if (_reservations[projectNumber].creator != address(0)) revert AlreadyReserved();
uint256 amount = templateRegistry.getTemplate(templateKey, templateVersion).stakeRequirement;
bytes32 key = reservationKey(projectNumber);
if (amount != 0) staking.reserveStake(creator, key, amount);
uint256 bond = creatorBondAmount;
uint64 unlockAt;
if (bond != 0) {
unlockAt = uint64(block.timestamp) + bondLockDuration;
IERC20 equi = staking.stakeToken();
uint256 balanceBefore = equi.balanceOf(address(this));
equi.safeTransferFrom(creator, address(this), bond);
if (equi.balanceOf(address(this)) - balanceBefore != bond) {
revert BondTransferMismatch();
}
emit CreatorBondLocked(projectNumber, creator, bond, unlockAt);
}
_reservations[projectNumber] = LaunchReservation({
creator: creator,
amount: amount,
reservationKey: key,
released: false,
bondAmount: bond,
bondUnlockAt: unlockAt,
bondReturned: false
});
emit EquiStakeReserved(projectNumber, creator, key, amount);
}
function releaseLaunch(
uint256 projectNumber
) external {
if (msg.sender != lifecycleManager) revert NotLifecycleManager();
LaunchReservation storage reservation = _reservations[projectNumber];
if (reservation.creator == address(0) || reservation.released) revert NotReserved();
reservation.released = true;
if (reservation.amount != 0) staking.releaseStake(reservation.reservationKey);
emit EquiStakeReleased(
projectNumber, reservation.creator, reservation.reservationKey, reservation.amount
);
if (reservation.bondAmount != 0 && !reservation.bondReturned) {
_returnBond(projectNumber, reservation);
}
}
/// @notice Lets the creator reclaim their bond once the lock expires if the launch never graduated.
function claimExpiredBond(
uint256 projectNumber
) external {
LaunchReservation storage reservation = _reservations[projectNumber];
if (reservation.creator != msg.sender) revert NotCreator();
if (reservation.bondAmount == 0) revert NoBondEscrowed();
if (reservation.bondReturned) revert BondAlreadyReturned();
if (block.timestamp < reservation.bondUnlockAt) revert BondStillLocked();
_returnBond(projectNumber, reservation);
}
/// @dev Returns the escrowed bond to its creator. Effects precede the transfer (CEI); the
/// `bondReturned` flag makes double-return impossible from either release path.
function _returnBond(
uint256 projectNumber,
LaunchReservation storage reservation
) private {
reservation.bondReturned = true;
uint256 bond = reservation.bondAmount;
staking.stakeToken().safeTransfer(reservation.creator, bond);
emit CreatorBondReturned(projectNumber, reservation.creator, bond);
}
function reservationKey(
uint256 projectNumber
) public view returns (bytes32) {
return keccak256(
abi.encode("EQUIFOLD_STANDARD_TOKEN", block.chainid, address(this), projectNumber)
);
}
function getReservation(
uint256 projectNumber
) external view returns (LaunchReservation memory) {
return _reservations[projectNumber];
}
function _validateContract(
address target
) private view {
if (target == address(0) || target.code.length == 0) revert InvalidAddress();
}
}
Chain explorer5586msChain node84ms