Protocol
Contracts
Every contract in the V3 stack and what it owns.
The stack#
Seven contracts, deployed together and wired to each other at deploy time. Nothing is upgradeable and nothing is a proxy, so a new version means a new stack. Addresses are on network.
JoseonLaunchFactoryV3
├── deploys JoseonTokenV2 + BondingCurvePool per launch
└── registers with LaunchRegistry
LaunchRegistry ──── launch state + fee recipient of record
MigrationManagerV2
├── reads LaunchRegistry
├── calls JoseonSwapFactoryV3.createLaunchPair (via the launch factory)
└── locks the initial LP in PermanentLiquidityLocker
JoseonSwapFactoryV3 ──▶ JoseonSwapPairV2 (per pair)
JoseonSwapRouter ──▶ paths, deadlines, ETH ⇄ WETH
WETH9 ──▶ per-stack wrapped ETHLaunchpad#
JoseonLaunchFactoryV3#
Creates a launch in one transaction: deploys the token, deploys its curve, funds the curve with the full supply, registers it, and optionally executes the creator's first buy.
createToken(string name, string symbol, uint256 initialBuyMinOut, uint256 deadline)
returns (address token, address curve)
curveFor(address token) returns (address)
allTokens(uint256 index) returns (address)
allTokensLength() returns (uint256)Constants, identical for every launch:
| Constant | Value |
|---|---|
TOTAL_SUPPLY | 1,000,000,000e18 |
CURVE_SUPPLY | 800,000,000e18 |
DEX_SUPPLY | 200,000,000e18 |
INITIAL_VIRTUAL_ETH_RESERVE | 1.2e18 |
INITIAL_VIRTUAL_TOKEN_RESERVE | 1,066,666,666.666666666666666666e18 |
MINIMUM_CURVE_RESERVE | 0 |
JoseonTokenV2#
Fixed-supply ERC-20, 18 decimals, no mint and no burn. It adds an ERC-1046 tokenURI() derived from the token's own
address against an immutable base, so wallets and explorers can find the logo and description on-chain.
There is no setter and no owner for that URI. Nobody, including the deployer, can repoint a live token's metadata.
supportsInterface reports both ERC-165 and ERC-1046.
BondingCurvePool#
One per launch. Prices trades on virtual reserves, holds real ETH and tokens, accrues fees, and hands assets to the migration manager exactly once. Full maths in bonding curve.
quoteBuy(uint256 grossEthIn) returns (
uint256 ethUsed, uint256 ethRefunded, uint256 fee, uint256 netEthIn,
uint256 tokenOut, uint256 nextVirtualEthReserve,
uint256 nextVirtualTokenReserve, bool willGraduate)
quoteSell(uint256 tokenIn) returns (...)
buy(uint256 minTokensOut, uint256 deadline) payable
sell(uint256 tokenIn, uint256 minEthOut, uint256 deadline)
getCurrentPrice() returns (uint256) // WAD, eth per token
getProgress() returns (uint256) // WAD, 1e18 == 100%
claimableCreatorFees(address recipient) returns (uint256)
claimCreatorFees(address recipient) returns (uint256)releaseMigrationAssets is callable only by the migration manager, only in GRADUATING, and only once —
migrationReleased makes a second call impossible.
LaunchRegistry#
The record of what exists and who gets paid. Holds a LaunchInfo per token — creator, curve, pair, timestamps,
supplies, state — and the feeRecipientOf mapping that both the curve and launch pairs consult.
State transitions are driven by the launch factory and migration manager; the registry itself enforces that they happen in order.
MigrationManagerV2#
Owns graduation. Full sequence in graduation.
getMigrationQuote(address token)
returns (uint256 wethAmount, uint256 tokenAmount, uint256 initialDexPrice)
graduate(address token)MAX_PRICE_GAP_BPS = 50 — a 0.5% tolerance for rounding between the curve's final price and the pool's opening price.
PermanentLiquidityLocker#
One function, lock, callable only by the migration manager. It verifies the pair is the factory's canonical
token/WETH pair, that the factory considers it a launch pair, and that this token has not already been locked.
No withdraw function exists. Locked liquidity is locked.
DEX#
JoseonSwapFactoryV3#
createPair(address tokenA, address tokenB) returns (address pair)
createLaunchPair(address token, address weth) returns (address pair)
getPair(address tokenA, address tokenB) returns (address)
allPairs(uint256 index) returns (address)
allPairsLength() returns (uint256)
isLaunchPair(address pair) returns (bool)
isLaunchPairCreator(address creator) returns (bool)
feeTo() returns (address)isLaunchPairCreator is a set, not a single slot. Authorising a new launch factory does not revoke an older one,
so in-flight curves on a previous factory can still graduate. That design came out of a real finding — the earlier
single-slot version would have stranded a live launch on upgrade.
JoseonSwapPairV2#
Constant-product pair with V2 semantics plus booked fees and the UNSEEDED → OPEN launch-pair gate.
mint(address to) returns (uint256 liquidity)
burn(address to) returns (uint256 amount0, uint256 amount1)
swap(uint256 amount0Out, uint256 amount1Out, address to, bytes data)
getReserves() returns (uint112 reserve0, uint112 reserve1, uint32 blockTimestampLast)
sync()
skim(address to)
claimCreatorFees() returns (uint256 amount0, uint256 amount1)
claimProtocolFees() returns (uint256 amount0, uint256 amount1)
feeShares() returns (uint256 creatorBps, uint256 protocolBps, uint256 lpBps)| Constant | Value |
|---|---|
TOTAL_FEE_BPS | 30 (0.30%) |
LAUNCH_CREATOR_SHARE_BPS | 5,000 |
LAUNCH_PROTOCOL_SHARE_BPS | 3,000 |
PLAIN_PROTOCOL_SHARE_BPS | 3,000 |
MINIMUM_LIQUIDITY | 1,000 |
JoseonSwapRouter#
Standard V2-style router: quoting helpers, swapExactTokensForTokens and friends, the ETH variants, and liquidity
add/remove. Paths are capped at two hops.
WETH9#
Canonical WETH, redeployed per stack. Byte-identical to previous versions but a distinct address — which is exactly why mixing stack addresses breaks routing.
Reading the source#
contracts/src/launch/ JoseonLaunchFactoryV3, JoseonTokenV2, BondingCurvePool,
LaunchRegistry, MigrationManagerV2, PermanentLiquidityLocker
contracts/src/core/ JoseonSwapFactoryV3, JoseonSwapPairV2, JoseonSwapERC20
contracts/src/periphery/JoseonSwapRouterEvery deployed contract is source-verified on Blockscout, so you can read the exact bytecode-matched source from the explorer rather than trusting this page.