Foundry scripts for deploying HIP-3 perp markets on Hyperliquid via the Kinetiq Launch protocol.
For a reference of the protocol these scripts interact with, see SPECIFICATION.md.
For deployer onboarding — configuration field reference, operator lifecycle calls, and Kinetiq-managed enclave + sub deployer scope — see WALKTHROUGH.md.
-
Copy
TEMPLATE.jsonto your own per-market file (e.g.my-market.json) — that's what$MARKET_CONFIG_JSONwill point at. Seescript/config/example-{mainnet,mainnet-dryrun,testnet}.jsonfor filled-in examples. -
Set
network.nameto one of:mainnet,testnet,mainnet-dryrun. The deploy script auto-resolves the matchingscript/config/globals/<network>.jsonand asserts your declaredchainIdlines up with the RPC. -
Fill in
evm.marketParams:Field What to fill Side adminmultisig that controls role transfers via the factory EVM operatorreceives OPERATOR_ROLE; drives the lifecycle (fund/launch/setUnwindPhase/unwind)EVM opBondwei, 1e10-aligned; locked for the life of the market, returned to deployer on unwind()finalizeEVM validatorL1 validator (must be in Kinetiq's approved set) EVM ↔ HC gateIEXGateaddress, or0x0for no gate (typical initial setup)EVM lstName/lstSymbolERC20 metadata for your per-market EXLSTsharesEVM hyperCoreDeployerHC spot address that deploys (or has deployed) the HC asset paired with your EXLSTHC deployerTreasuryHC spot treasury for your fee share (must already be activated on HC) HC buybackBpsbasis points of post-protocol-fee remainder routed to the LST buyback loop (typical 1000= 10%)EVM ↔ HC -
Fill in
core.registerAsset(HC perp dex registration — coin, decimals, oracle, collateral token). The deploy scripts readcollateralTokenas the activation token. SeeWALKTHROUGH.mdfor the HC-side schema.
enclaver + marketTier are pinned by Kinetiq in script/config/globals/<network>.json — no action needed. Optionally set evm.cancelRecipient if you want a pre-bond cancelMarket refund to land somewhere other than the deploy EOA. Full per-field detail in WALKTHROUGH.md.
Before risking capital on a real deploy, fork-test the lifecycle against the live protocol singletons. The rehearsal creates a mainnet fork inside the script and runs either the full success path (e2e) or the pre-bond cancel (cancel) — no --broadcast, no on-chain effect.
Set SENDER to your production deployer address (the one that will eventually call deployMarket for real). The rehearsal reads its live balance + state from the fork; if your address already holds enough HYPE + activation token, the run is fully realistic. If not, the script logs the gap and tops up via cheatcodes. PRIVATE_KEY can be a throwaway dev key — the rehearsal never broadcasts. Set at least one of SENDER or PRIVATE_KEY (if only PRIVATE_KEY is set, msg.sender derives from it).
export RPC_URL=https://rpc.hyperliquid.xyz/evm
export MARKET_CONFIG_JSON=script/config/example-mainnet.json
export SENDER=0xYourProductionDeployer # your real deploy address
./script/deployment/lifecycle-rehearsal.sh e2e
./script/deployment/lifecycle-rehearsal.sh cancelThe rehearsal snapshots marketConfig.deployed.* at entry and restores it at exit, so the file is unchanged after a successful run. If the rehearsal reverts mid-flight, run git checkout $MARKET_CONFIG_JSON to recover.
Per-market lifecycle is three on-chain phases. HyperCore must confirm the activation token bridge between phases, so each phase is its own command. Set the env vars once per shell session, then run the phases in order.
Capital required on the deployer EOA:
| Phase | Capital | Notes |
|---|---|---|
deployMarket |
opBond HYPE on HyperEVM [EVM] |
Must satisfy globalConfig.minOperatorBond() — see Network parameters. Sent as msg.value, escrowed by the factory. Bridge HYPE from HyperCore spot first if your balance lives there. |
activateMarket |
6 × core.registerAsset.schema.collateralToken on HyperEVM (e.g. 6 USDC at 6 decimals) |
Script pulls from the deployer's HyperEVM balance and bridges per-contract shares to HyperCore so the per-market contracts have HC accounts [Core after bridge]. Bridge USDC from HyperCore spot first if needed. |
bondMarket |
none | Factory forwards escrowed opBond to be staked in the per-market reserve (earns validator delegation rewards alongside depositor stake). The 1:1 EXLST shares minted against the bonded stake are escrowed in EXManager as the deployer's skin-in-the-game — locked for the life of the market and swept back to the deployer only at unwind() finalize. Transitions to FUNDING [EVM]. |
Before broadcasting:
- Enable HyperEVM big blocks on your deployer address before
deployMarket. The factory deploys a full per-market contract suite (~13 contracts) in one tx; HyperEVM's small-block gas limit is too low to fit it. Toggle big blocks via the Hyperliquid UI/API for the deployer address before runningdeployMarket. - Set
BROADCAST=1to actually send the transactions. The default (BROADCAST=0) runs as fork simulation only — no on-chain effect.vm.writeJsonstill mutates$MARKET_CONFIG_JSONeither way, so point at a throwaway copy if you want the original clean.
export RPC_URL=https://...
export PRIVATE_KEY=0x...
export MARKET_CONFIG_JSON=path/to/market.json # your filled-in copy of TEMPLATE.json
export BROADCAST=1 # REQUIRED to actually send tx on-chain
./script/deployment/deploy-first-market.sh deployMarket
./script/deployment/deploy-first-market.sh activateMarket
./script/deployment/deploy-first-market.sh bondMarketA pre-bond cancelMarket exit is also available (time-locked):
./script/deployment/deploy-first-market.sh cancelMarketRead-only verifiers — one per phase, run after the corresponding deploy step (no PRIVATE_KEY
needed):
./script/deployment/verify-first-market.sh verifyDeployMarket
./script/deployment/verify-first-market.sh verifyActivateMarket
./script/deployment/verify-first-market.sh verifyBondMarketThe script writes deployed marketId / exManager / bonded / cancelled flags back to $MARKET_CONFIG_JSON between phases — run them in order, no need to manually thread state.
After bondMarket the market is in FUNDING. The operator (the address set as operator in MarketParams at deploy time, or whoever the admin most recently rotated to via EXFactory.transferOperator) drives the rest of the lifecycle on EXManager via ./script/deployment/operator-flow.sh. Same env-var preamble as the deploy driver.
export RPC_URL=https://...
export PRIVATE_KEY=0x... # operator EOA (must hold OPERATOR_ROLE)
export MARKET_CONFIG_JSON=path/to/market.json
export BROADCAST=1 # REQUIRED to send tx on-chain
# FUNDING -> LAUNCHING (once reserves >= tier minHypeStake)
./script/deployment/operator-flow.sh fund
# LAUNCHING -> LIVE (walletDataHex + walletSignatureHex come from Kinetiq's enclave)
./script/deployment/operator-flow.sh launch <walletDataHex> <walletSignatureHex>
# Queue voluntary unwind (true) or cancel a queued one (false)
./script/deployment/operator-flow.sh setUnwindPhase true
./script/deployment/operator-flow.sh setUnwindPhase false
# Finalize wind-down after unwindDelay (LIVE-phase additionally needs Kinetiq HC attestation + minLinkAgeForUnwind cliff)
./script/deployment/operator-flow.sh unwindEach phase pre-flights its on-chain prerequisites (phase guard, unwind state, ghost-LST reserve floor for fund, payload-length sanity for launch) and inline-verifies post-broadcast deltas with operator-readable revert messages. Default is fork-only simulation; set BROADCAST=1 to actually send.
The depositor surface (./script/deployment/user-flow.sh) is most useful as a testing tool — primarily for bringing reserves up to the tier floor on testnet or mainnet-dryrun so the operator can call fund(). Same env-var preamble as above.
export RPC_URL=https://...
export PRIVATE_KEY=0x... # depositor EOA (must hold the HYPE)
export MARKET_CONFIG_JSON=path/to/market.json
export BROADCAST=1
# Stake HYPE -> mint EXLST shares (amount must be >= globalConfig.minStakeAmount and 1e10-aligned)
./script/deployment/user-flow.sh deposit <amountWei> [<recipient>] [<dataHex>]
# Burn EXLST -> queue HYPE withdrawal (paid out via confirm after the protocol's withdrawalDelay)
./script/deployment/user-flow.sh withdraw <sharesWei> [<recipient>] [<maxBlockedShares>] [<dataHex>]
# Confirm a queued withdrawal -> HYPE settles to recipient
./script/deployment/user-flow.sh confirmWithdraw <withdrawalId> [<recipient>]Defaults: recipient = 0x0…0 (resolves to msg.sender), dataHex = 0x (NoOp gate — correct for example markets), maxBlockedShares = type(uint256).max. The per-network globalConfig.minStakeAmount is 0.1 HYPE (1e17 wei) on every network, and every payable entrypoint enforces amount % 1e10 == 0 (HC 8-decimal bridge alignment).
globalConfig floors and delays the deploy + post-bond flows depend on, per network:
| Param | mainnet | dryrun / testnet | Binds |
|---|---|---|---|
minOperatorBond |
1000 HYPE | 1 HYPE | deployMarket{msg.value} floor |
unwindDelay |
7 days | 1 day | cancelMarket pre-bond + setUnwindPhase → unwind finalize |
minLinkAgeForUnwind |
183 days | 1 day | LIVE-phase unwind() cliff (clock starts when Kinetiq attests the HC perp deploy is live) |
Live values: cast call <GlobalConfig> <param>()(uint256) against the address in script/config/globals/<network>.json.
The per-market suite is in FUNDING. Three actors take over — the deployer should understand each surface even if the roles are delegated to different addresses.
| Actor | Held by | Surface |
|---|---|---|
| Admin | deployer multisig | EXFactory.transferOperator / transferAdmin / transferEnclaver — meta-authority, rotates roles only (no lifecycle power) |
| Operator | deployer EOA or multisig | EXManager.fund → launch → setUnwindPhase → unwind |
| Enclaver | Kinetiq-pinned in globals | passive identifier; off-chain enclave reads WALLET_ROLE to authenticate HC API-wallet requests for this market |
The operator drives the per-market lifecycle on EXManager:
- Phase transitions:
fund()— FUNDING → LAUNCHING when reserves ≥ tierminHypeStake[EVM]launch(walletSignedData)— LAUNCHING → LIVE; registers the HC API wallet via CoreWriter [EVM + Core trigger]setUnwindPhase(true)→unwind()— voluntary wind-down; LIVE finalize additionally requires Kinetiq's HC attestation + theminLinkAgeForUnwindcliff
- API wallet rotations.
updateWallet(walletSignedData)in LIVE / WOUND_DOWN, same Kinetiq-signed-payload path aslaunch. - Coordination with Kinetiq. Request the
WalletDatapayload from Kinetiq's enclave (off-chain, signed withglobalConfig.exWalletAdmin) forlaunchand any futureupdateWalletrotation. On-chain submission is the operator's.
Full per-call inventory + Wallet Admin Flow detail in WALKTHROUGH.md.
Per-network singleton addresses for the Launch protocol's deployer-facing contracts. Implementation, facet, and beacon impl addresses are intentionally omitted — these are the contracts your scripts and contract calls interact with directly. The deploy + verify scripts pick these up automatically from script/config/globals/<network>.json based on the network.name in your market config.
| Contract | Address |
|---|---|
GlobalConfig |
0x23CcD0f1926E4f97d9292683B45fAdBDb066AE50 |
PauserRegistry |
0xC2b4350D952550ce4d2A4023a7935931fD2Bd6dA |
FacetRegistry |
0xB6509D615553DCba03D1208BC4F103109537294C |
UpgradeableBeaconRegistry |
0x804A50082A394C65672Cb6A8Bd31EBe2CEb22bA1 |
EXDeployer |
0x5f06F2B0A5ABDa058A2771107b15b374E82361ea |
EXFactory |
0x188a8CFa039C049c6B076C52164E5d51ee207a98 |
ProtocolRolesController |
0x9f4Ea6E461759E5D920B5e294f10870ef0949A4E |
EXRouter |
0x06629c64AAF1639A7CDE24DFe0D1E1eD3E56BF3d |
Separate deployment on the same HyperEVM mainnet chain — for testing the deploy flow against real mainnet state without touching production singletons.
| Contract | Address |
|---|---|
GlobalConfig |
0x0CD8bd124CB0B610CEC8EA35311ac2FEE6167269 |
PauserRegistry |
0x5609700347B35F39Ee82E23d77f71F6Bb9758168 |
FacetRegistry |
0x6420589a7C7d85c0D887eE61ea0a8ad42861224A |
UpgradeableBeaconRegistry |
0x85e91F54DE98F2B7c4A59A3EC5933Ffa93C3e1bf |
EXDeployer |
0xf9176658db4259aFF43699D7d6A5FAfb300959BB |
EXFactory |
0xde9913e6A081F25b36e7ADd224D13d20FD3940B2 |
ProtocolRolesController |
0xae0b43ccb16423fE16B7A9D7392Fa6a907DD28D4 |
EXRouter |
0x7a5d01e345f28A6F2fb974808CA93F50a473C426 |
| Contract | Address |
|---|---|
GlobalConfig |
0xeCE7dfc7825d33B8C33BAB5CaB6B0178Aa0371a6 |
PauserRegistry |
0x343B88f27bb9553000C215aE9F146ee6421B2aA0 |
FacetRegistry |
0xAb9b5eaCD8bcBA443414958cDAA9d621Aa66ad64 |
UpgradeableBeaconRegistry |
0x6D446056920855afCB560f42916B0f0f996fC356 |
EXDeployer |
0x3B6192AF370D29c3F7baC7A12334a9BF26CA49cB |
EXFactory |
0xba9DBf3C09F50F301531Eb60440c14151632F9d5 |
ProtocolRolesController |
0x9a43406488dc72b3c6b76B5462A466F8AAdb7d24 |
EXRouter |
0xbE1882e3De9250e875d6ecbBf30D924f4e38036E |