LaunchDeployer
0xa2445bd356556e8214f1cdbcebfd0c7f032a3449
Verification
Verified
v0.8.26+commit.8a97fa7a
Type
Contract
524 bytes
ABI entries
16
8 read · 0 write
License
none
Contract information
- Address
- 0xa2445bd356556e8214f1cdbcebfd0c7f032a3449
- Chain
- Robinhood Chain (4663)
- Compiler
- v0.8.26+commit.8a97fa7a
- Optimization
- Enabled
- Creator
- 0x962b8b17f3…9bE9eab78f
- Creation tx
- 0x6bba72749b…93633d805e
Token
Not a token
This contract does not expose ERC-20 metadata.
Read contract (8)
initTick() → int24
mirror() → address
pool() → address
positionTokenId() → uint256
renderer() → address
router() → address
token() → address
tokenSalt() → bytes32
Events (1)
LaunchCompleted
ABI
[
{
"inputs": [
{
"components": [
{
"internalType": "address",
"name": "v3Factory",
"type": "address"
},
{
"internalType": "bytes32",
"name": "v3FactoryCodehash",
"type": "bytes32"
},
{
"internalType": "address",
"name": "npm",
"type": "address"
},
{
"internalType": "bytes32",
"name": "npmCodehash",
"type": "bytes32"
},
{
"internalType": "address",
"name": "weth",
"type": "address"
},
{
"internalType": "bytes32",
"name": "wethCodehash",
"type": "bytes32"
},
{
"internalType": "address",
"name": "universalRouter",
"type": "address"
},
{
"internalType": "bytes32",
"name": "universalRouterCodehash",
"type": "bytes32"
}
],
"internalType": "struct LaunchDependencies",
"name": "dependencies",
"type": "tuple"
},
{
"components": [
{
"internalType": "address",
"name": "owner",
"type": "address"
},
{
"internalType": "address",
"name": "dev",
"type": "address"
},
{
"internalType": "address",
"name": "guardian",
"type": "address"
}
],
"internalType": "struct LaunchRoles",
"name": "roles",
"type": "tuple"
},
{
"internalType": "contract KaleidoRendererV2",
"name": "rendererV2",
"type": "address"
},
{
"internalType": "bytes32",
"name": "rendererCodehash",
"type": "bytes32"
},
{
"internalType": "bytes32",
"name": "launchSalt",
"type": "bytes32"
},
{
"internalType": "int24",
"name": "initialTick",
"type": "int24"
}
],
"stateMutability": "nonpayable",
"type": "constructor"
},
{
"inputs": [
{
"internalType": "address",
"name": "dependency",
"type": "address"
},
{
"internalType": "bytes32",
"name": "expected",
"type": "bytes32"
},
{
"internalType": "bytes32",
"name": "actual",
"type": "bytes32"
}
],
"name": "DependencyCodehashMismatch",
"type": "error"
},
{
"inputs": [
{
"internalType": "address",
"name": "dependency",
"type": "address"
}
],
"name": "DependencyHasNoCode",
"type": "error"
},
{
"inputs": [],
"name": "InvalidInitTick",
"type": "error"
},
{
"inputs": [],
"name": "LauncherCannotRemainOwner",
"type": "error"
},
{
"inputs": [],
"name": "ZeroAddress",
"type": "error"
},
{
"inputs": [],
"name": "ZeroTokenSalt",
"type": "error"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "address",
"name": "token",
"type": "address"
},
{
"indexed": true,
"internalType": "address",
"name": "mirror",
"type": "address"
},
{
"indexed": true,
"internalType": "address",
"name": "router",
"type": "address"
},
{
"indexed": false,
"internalType": "address",
"name": "renderer",
"type": "address"
},
{
"indexed": false,
"internalType": "address",
"name": "pool",
"type": "address"
},
{
"indexed": false,
"internalType": "uint256",
"name": "positionTokenId",
"type": "uint256"
},
{
"indexed": false,
"internalType": "int24",
"name": "initTick",
"type": "int24"
}
],
"name": "LaunchCompleted",
"type": "event"
},
{
"inputs": [],
"name": "initTick",
"outputs": [
{
"internalType": "int24",
"name": "",
"type": "int24"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "mirror",
"outputs": [
{
"internalType": "address",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "pool",
"outputs": [
{
"internalType": "address",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "positionTokenId",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "renderer",
"outputs": [
{
"internalType": "contract KaleidoRendererV2",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "router",
"outputs": [
{
"internalType": "contract ClaimRouter",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "token",
"outputs": [
{
"internalType": "contract KaleidoToken",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "tokenSalt",
"outputs": [
{
"internalType": "bytes32",
"name": "",
"type": "bytes32"
}
],
"stateMutability": "view",
"type": "function"
}
]Source code
// SPDX-License-Identifier: UNLICENSED
pragma solidity 0.8.26;
import { Script, console2 } from "forge-std/Script.sol";
import { TickMath } from "@uniswap/v4-core/src/libraries/TickMath.sol";
import { KaleidoToken } from "../src/KaleidoToken.sol";
import { ClaimRouter } from "../src/ClaimRouter.sol";
import { KaleidoRendererV2 } from "../src/KaleidoRendererV2.sol";
struct LaunchDependencies {
address v3Factory;
bytes32 v3FactoryCodehash;
address npm;
bytes32 npmCodehash;
address weth;
bytes32 wethCodehash;
address universalRouter;
bytes32 universalRouterCodehash;
}
struct LaunchRoles {
address owner;
address dev;
address guardian;
}
interface ILaunchV3Factory {
function getPool(address tokenA, address tokenB, uint24 fee) external view returns (address);
}
interface ILaunchV3Pool {
function slot0() external view returns (uint160 sqrtPriceX96, int24 tick, uint16, uint16, uint16, uint8, bool);
}
interface ILaunchNpm {
function ownerOf(uint256 tokenId) external view returns (address);
}
/// @notice One-transaction production assembly for KALEIDO. The constructor creates the core, seeds the v3 reserve,
/// deploys the exact ClaimRouter implementation, wires it one-shot, installs the pre-verified Prisme V2
/// renderer, and transfers the core's only remaining ownership to the final owner.
/// @dev The renderer is deliberately deployed and verified BEFORE this transaction. Embedding its 8.9 kB creation
/// code here would push the launcher's EIP-3860 initcode too close to the 49,152-byte ceiling; renderer setup is
/// cosmetic and does not create the H-01 pool-init window. Every money-bearing/core step is atomic.
///
/// IMPORTANT: the token uses CREATE2 with an operator-supplied high-entropy salt. Keep that salt secret until
/// direct sequencer submission; revealing it makes the child address predictable again. Atomic assembly then
/// removes the post-deployment window. The seed checks remain the final on-chain fail-closed defense.
contract LaunchDeployer {
error ZeroAddress();
error DependencyHasNoCode(address dependency);
error DependencyCodehashMismatch(address dependency, bytes32 expected, bytes32 actual);
error InvalidInitTick();
error ZeroTokenSalt();
error LauncherCannotRemainOwner();
event LaunchCompleted(
address indexed token,
address indexed mirror,
address indexed router,
address renderer,
address pool,
uint256 positionTokenId,
int24 initTick
);
int24 internal constant TICK_SPACING = 200;
KaleidoToken public immutable token;
address public immutable mirror;
ClaimRouter public immutable router;
KaleidoRendererV2 public immutable renderer;
address public immutable pool;
uint256 public immutable positionTokenId;
int24 public immutable initTick;
bytes32 public immutable tokenSalt;
constructor(
LaunchDependencies memory dependencies,
LaunchRoles memory roles,
KaleidoRendererV2 rendererV2,
bytes32 rendererCodehash,
bytes32 launchSalt,
int24 initialTick
) {
if (
roles.owner == address(0) || roles.dev == address(0) || roles.guardian == address(0)
|| address(rendererV2) == address(0)
) {
revert ZeroAddress();
}
if (launchSalt == bytes32(0)) revert ZeroTokenSalt();
if (roles.owner == address(this)) revert LauncherCannotRemainOwner();
_checkDependency(dependencies.v3Factory, dependencies.v3FactoryCodehash);
_checkDependency(dependencies.npm, dependencies.npmCodehash);
_checkDependency(dependencies.weth, dependencies.wethCodehash);
_checkDependency(dependencies.universalRouter, dependencies.universalRouterCodehash);
_checkDependency(address(rendererV2), rendererCodehash);
int24 minTick = TickMath.minUsableTick(TICK_SPACING);
int24 maxTick = TickMath.maxUsableTick(TICK_SPACING);
if (initialTick % TICK_SPACING != 0 || initialTick <= minTick || initialTick >= maxTick) {
revert InvalidInitTick();
}
// The launcher is the temporary owner because only it can execute all setup calls inside this transaction.
KaleidoToken t = new KaleidoToken{ salt: launchSalt }(
dependencies.v3Factory, dependencies.npm, dependencies.weth, address(this), roles.dev
);
(int24 tickLower, int24 tickUpper) = _singleSidedTicks(t.kaleidoIsToken0(), initialTick);
uint256 posId = t.seed(TickMath.getSqrtPriceAtTick(initialTick), tickLower, tickUpper);
// Constructing the exact implementation here closes M-03R for the launch path: no operator-supplied router
// can self-attest with a fake core() getter. ClaimRouter also binds WETH back to the core in its constructor.
ClaimRouter cr = new ClaimRouter(address(t), dependencies.universalRouter, roles.guardian, dependencies.weth);
t.setClaimRouter(address(cr));
t.setRenderer(address(rendererV2));
t.transferOwnership(roles.owner);
token = t;
mirror = address(t.mirror());
router = cr;
renderer = rendererV2;
pool = t.pool();
positionTokenId = posId;
initTick = initialTick;
tokenSalt = launchSalt;
_emitLaunchCompleted();
}
function _emitLaunchCompleted() private {
emit LaunchCompleted(
address(token), mirror, address(router), address(renderer), pool, positionTokenId, initTick
);
}
function _checkDependency(address dependency, bytes32 expectedCodehash) private view {
if (dependency == address(0)) revert ZeroAddress();
if (dependency.code.length == 0) revert DependencyHasNoCode(dependency);
bytes32 actual = dependency.codehash;
if (expectedCodehash == bytes32(0) || actual != expectedCodehash) {
revert DependencyCodehashMismatch(dependency, expectedCodehash, actual);
}
}
function _singleSidedTicks(bool kaleidoIsToken0, int24 initialTick)
private
pure
returns (int24 tickLower, int24 tickUpper)
{
if (kaleidoIsToken0) {
tickLower = initialTick;
tickUpper = TickMath.maxUsableTick(TICK_SPACING);
} else {
tickLower = TickMath.minUsableTick(TICK_SPACING);
tickUpper = initialTick;
}
}
}
/// @dev Shared, pinned Robinhood Mainnet 4663 configuration and postflight assertions for the production scripts.
abstract contract RobinhoodLaunchBase is Script {
uint256 internal constant ROBINHOOD_MAINNET_CHAIN_ID = 4663;
uint256 internal constant SEED_MAX_DUST = 1e9;
address internal constant V3_FACTORY = 0x1f7d7550B1b028f7571E69A784071F0205FD2EfA;
address internal constant V3_NPM = 0x73991a25C818Bf1f1128dEAaB1492D45638DE0D3;
address internal constant WETH = 0x0Bd7D308f8E1639FAb988df18A8011f41EAcAD73;
address internal constant UNIVERSAL_ROUTER = 0x8876789976dEcBfCbBbe364623C63652db8C0904;
// Live runtime hashes measured on Robinhood Mainnet at P0 implementation time. Any drift aborts the launch and
// requires a dependency re-verification; silently accepting changed infrastructure is not launch-safe.
bytes32 internal constant V3_FACTORY_CODEHASH = 0xec72b1abd1f2faee020cfea9c646bd8994f9fb389054f6e574f103a895091739;
bytes32 internal constant V3_NPM_CODEHASH = 0x0a493d1af3d0f25fed8efa205244ebee14114267a08647fc38c515c7cd6ead4f;
bytes32 internal constant WETH_CODEHASH = 0x5706be52f64875fee65a2cec0d80e47a23d8793cbe85d214b48445e2d05f5353;
bytes32 internal constant UNIVERSAL_ROUTER_CODEHASH =
0x2ce6aaaf9f4151f5e1cbf774668772f17f532ae11b15e9284fd0a072a8b0fbde;
bytes32 internal constant RENDERER_V2_CODEHASH = 0xf755d81a77116eb5bf58038534eb8ba82e79a32487d0b64c25c3023724245dbe;
function _dependencies() internal pure returns (LaunchDependencies memory) {
return LaunchDependencies({
v3Factory: V3_FACTORY,
v3FactoryCodehash: V3_FACTORY_CODEHASH,
npm: V3_NPM,
npmCodehash: V3_NPM_CODEHASH,
weth: WETH,
wethCodehash: WETH_CODEHASH,
universalRouter: UNIVERSAL_ROUTER,
universalRouterCodehash: UNIVERSAL_ROUTER_CODEHASH
});
}
function _requireMainnetConfirmation() internal view {
require(block.chainid == ROBINHOOD_MAINNET_CHAIN_ID, "Robinhood Mainnet/fork only");
require(
keccak256(bytes(vm.envString("MAINNET_LAUNCH_CONFIRM"))) == keccak256("KALEIDO_MAINNET_4663"),
"set MAINNET_LAUNCH_CONFIRM=KALEIDO_MAINNET_4663"
);
}
function _readInitTick() internal view returns (int24 initialTick) {
int256 raw = vm.envInt("INIT_TICK");
require(raw >= type(int24).min && raw <= type(int24).max, "INIT_TICK outside int24");
// forge-lint: disable-next-line(unsafe-typecast) -- range checked immediately above.
initialTick = int24(raw);
}
function _verify(
LaunchDeployer launcher,
LaunchRoles memory roles,
KaleidoRendererV2 expectedRenderer,
bytes32 expectedTokenSalt,
int24 expectedInitTick
) internal view {
KaleidoToken t = launcher.token();
ClaimRouter cr = launcher.router();
uint256 posId = launcher.positionTokenId();
require(address(t) != address(0) && address(cr) != address(0), "zero launch component");
require(t.seeded(), "core not seeded");
require(t.owner() == roles.owner, "wrong final owner");
require(t.dev() == roles.dev, "wrong dev");
require(t.claimRouter() == address(cr), "router not wired");
require(t.renderer() == address(expectedRenderer), "renderer not wired");
require(address(expectedRenderer).codehash == RENDERER_V2_CODEHASH, "wrong renderer bytecode");
require(address(t.mirror()) == launcher.mirror(), "mirror mismatch");
require(t.pool() == launcher.pool() && t.pool().code.length > 0, "pool mismatch");
require(t.positionTokenId() == posId && ILaunchNpm(V3_NPM).ownerOf(posId) == address(t), "position unlocked");
require(t.balanceOf(address(t)) <= SEED_MAX_DUST, "reserve dust too high");
require(t.allowance(address(t), V3_NPM) == 0, "NPM allowance not revoked");
require(address(cr.core()) == address(t), "router core mismatch");
require(cr.weth() == WETH, "router WETH mismatch");
require(cr.universalRouter() == UNIVERSAL_ROUTER, "wrong UniversalRouter");
require(cr.guardian() == roles.guardian, "wrong guardian");
require(launcher.initTick() == expectedInitTick, "launcher tick mismatch");
require(launcher.tokenSalt() == expectedTokenSalt && expectedTokenSalt != bytes32(0), "token salt mismatch");
(address token0, address token1) = t.kaleidoIsToken0() ? (address(t), WETH) : (WETH, address(t));
require(ILaunchV3Factory(V3_FACTORY).getPool(token0, token1, t.POOL_FEE()) == t.pool(), "non-canonical pool");
(uint160 poolPrice,,,,,,) = ILaunchV3Pool(t.pool()).slot0();
require(poolPrice == TickMath.getSqrtPriceAtTick(expectedInitTick), "wrong pool price");
}
function _log(LaunchDeployer launcher) internal view {
console2.log("LaunchDeployer", address(launcher));
console2.log("KaleidoToken ", address(launcher.token()));
console2.log("KaleidoMirror", launcher.mirror());
console2.log("ClaimRouter ", address(launcher.router()));
console2.log("Renderer V2 ", address(launcher.renderer()));
console2.log("v3 pool ", launcher.pool());
console2.log("position id ", launcher.positionTokenId());
}
}
/// @notice Deploy the renderer ahead of the core launch. This transaction carries no protocol funds and creates no
/// predictable KALEIDO address. Verify it on Blockscout and pass it as RENDERER_ADDRESS to `Launch`.
contract DeployRenderer is RobinhoodLaunchBase {
function run() external returns (KaleidoRendererV2 rendererV2) {
_requireMainnetConfirmation();
uint256 privateKey = vm.envUint("PRIVATE_KEY");
vm.startBroadcast(privateKey);
rendererV2 = new KaleidoRendererV2();
vm.stopBroadcast();
require(address(rendererV2).codehash == RENDERER_V2_CODEHASH, "renderer bytecode mismatch");
console2.log("KaleidoRendererV2", address(rendererV2));
console2.logBytes32(address(rendererV2).codehash);
}
}
/// @notice Production launch script. Simulate it against a fresh mainnet fork first; broadcast the exact transaction
/// directly to Robinhood's sequencer with `--skip-simulation` only after the runbook gates are signed off.
/// A remote pre-broadcast simulation would expose TOKEN_SALT before transaction ordering is secured.
contract Launch is RobinhoodLaunchBase {
function run() external returns (LaunchDeployer launcher) {
_requireMainnetConfirmation();
uint256 privateKey = vm.envUint("PRIVATE_KEY");
LaunchRoles memory roles = LaunchRoles({
owner: vm.envAddress("OWNER_ADDRESS"),
dev: vm.envAddress("DEV_ADDRESS"),
guardian: vm.envAddress("GUARDIAN_ADDRESS")
});
KaleidoRendererV2 rendererV2 = KaleidoRendererV2(vm.envAddress("RENDERER_ADDRESS"));
bytes32 launchSalt = vm.envBytes32("TOKEN_SALT");
int24 initialTick = _readInitTick();
vm.startBroadcast(privateKey);
launcher = new LaunchDeployer(_dependencies(), roles, rendererV2, RENDERER_V2_CODEHASH, launchSalt, initialTick);
vm.stopBroadcast();
_verify(launcher, roles, rendererV2, launchSalt, initialTick);
_log(launcher);
}
}
/// @notice Independent, read-only postflight. Run from a separate machine/RPC after broadcast and before announcing
/// or funding any ancillary service.
contract VerifyLaunch is RobinhoodLaunchBase {
function run() external view {
_requireMainnetConfirmation();
LaunchDeployer launcher = LaunchDeployer(vm.envAddress("LAUNCHER_ADDRESS"));
LaunchRoles memory roles = LaunchRoles({
owner: vm.envAddress("OWNER_ADDRESS"),
dev: vm.envAddress("DEV_ADDRESS"),
guardian: vm.envAddress("GUARDIAN_ADDRESS")
});
KaleidoRendererV2 rendererV2 = KaleidoRendererV2(vm.envAddress("RENDERER_ADDRESS"));
_verify(launcher, roles, rendererV2, vm.envBytes32("TOKEN_SALT"), _readInitTick());
_log(launcher);
console2.log("POSTFLIGHT OK");
}
}
Chain explorer5678msChain node79ms