contracts/script/Deploy.s.sol deploys the whole protocol in one forge script run on any chain in its _defaults table (Arc testnet and mainnet, Robinhood testnet and Robinhood Chain); other chains pass every external address in the environment. The wrapper scripts under scripts/ take the RPC, the gas flags and the deployment file name from the chain registry through scripts/chain-env.sh.
What gets deployed
In order, from one deployer account:- A mintable test dollar (
MockERC20, 6 decimals, one million units to the deployer), only withDEPLOY_TEST_DOLLAR=trueon a testnet without a real dollar. KeeperFxOraclewith a 2 hour fresh window, a 5 day stale limit andmaxMoveBps1500; theKEEPERaddress is allow-listed and EUR plus the 60 codes ofCurrencies.solare enabled.PythFxOracle(60 s fresh, 5 days stale, confidence 100) only where the chain table orPYTHnames a Pyth contract; its feeds come fromPythFeeds.sol.CurrencyRegistrypointed at the dollar and the keeper oracle. EUR is registered as a real currency when EURC is known. Then oneSynthTokenplusFxVaultper synthetic currency (up toMAX_CURRENCIES, default 60) throughregistry.createSynthetic, every vault with 0.3% mint and redeem fees, 1% stale fee, 20% protocol share,minCRBps10,000 (fully backed by the dollars paid in) and a 1,000,000 dollar liability cap.- Uniswap v4: the official
PoolManager,PositionManager,StateViewandQuoterwhen the chain table or the environment names them, otherwise the script deploys its own set (plus aWETHfor the position manager). - The launchpad:
LiquidityLocker, theBondingCurveimplementation,LaunchFactoryandRouter. The factory’s launch config is 1e27 total supply, 750e24 on the curve, 250e24 for the pool, 375e24 virtual tokens, 6,000 USD of virtual quote, 1% trade fee with 30% to the creator, 2% graduation fee.
Inputs
The signer is either an encrypted keystore (forge script --account <name>; PRIVATE_KEY is then ignored) or PRIVATE_KEY from the environment or contracts/.env (testnets only).
The per-chain table (
_defaults) mirrors dollar, eurc, permit2, pyth and uniswapV4 from packages/abis/chains.json; keep both in sync:
Testnet
1
Fund a deployer
Put a funded testnet key in
contracts/.env as PRIVATE_KEY. Arc testnet: https://faucet.circle.com (budget about 8 USDC of gas for all 60 currencies). Robinhood testnet: https://faucet.quicknode.com/robinhood/testnet.2
Deploy
CHAIN_ID, uses the first alternate RPC of the registry (mirrors handle the broadcast burst better than Arc’s primary), pins --with-gas-price and --priority-gas-price only on chains with a base-fee floor (Arc’s 20 gwei; ETH-gas chains let forge estimate), runs the script with --broadcast --slow, checks that the factory has code (a deployer with pending transactions produces a nonce mismatch and an empty address) and copies contracts/deployments/<id>.json to packages/abis/deployments/<id>.json. On a testnet whose registry dollar has no address yet, it sets DEPLOY_TEST_DOLLAR=true and prints the mock’s address: put it under dollar.address in chains.json afterwards.3
Post rates
pnpm --filter @launchpad/keeper start running (Keeper).4
Seed (optional)
UW dollars each (default 2 units) and creates four sample launches with BUY dollar initial buys (default 1 unit), all with cast send. On Arc, forge script cannot simulate the native USDC contract (it calls a precompile), so anything that moves USDC has to be sent with cast or a wallet; contracts/script/Seed.s.sol (SEED_RATES, SEED_UNDERWRITE_USDC, SEED_UNDERWRITE_COUNT, SEED_LAUNCHES, SEED_INITIAL_BUY_USDC) is for the local anvil demo.5
Point the services at it
Indexer with
CHAIN_ID, PORT, PONDER_RPC_URL_<id> and START_BLOCK_<id> (the deploy block; the testnet script does not record it), keeper with CHAIN_ID, web with NEXT_PUBLIC_INDEXER_URL_<id> and a rebuild. See Self-hosting.Mainnet
Mainnet deployments are signed by an encrypted Foundry keystore, never by aPRIVATE_KEY in a file. scripts/deploy-mainnet.sh refuses testnets, unsets PRIVATE_KEY, and requires KEEPER.
1
Create the keystore once
DEPLOYER_ACCOUNT and DEPLOYER_PASSWORD_FILE override the names.2
Fund the deployer
ETH on Robinhood Chain; the whole deployment simulates at about 0.003 ETH, and the script requires at least 0.01 ETH before broadcasting.
3
Simulate, then deploy
CHAIN_ID defaults to 4663. FEE_COLLECTOR defaults to the deployer, MAX_CURRENCIES to 60. The script prints the chain, deployer, balance, keeper, fee collector and coin naming before running forge script --account, then records the deploy block from the broadcast receipts as startBlock in the deployment file, copies it to packages/abis/deployments/4663.json and runs scripts/verify.sh.4
Hand over
Commit the deployment file and push it to the box; then, on the box, pull, install, set the web app’s default chain and indexer URLs, restart the keeper and indexer under their mainnet names and rebuild the web app (see Self-hosting).
Verification
scripts/verify.sh verifies every contract of a deployment on the chain’s Blockscout (explorerApi in the registry), skipping the ones already verified:
VERIFIER_URLoverrides the API,RPCthe endpoint,DELAYthe pause between submissions (4 s).- The self-deployed Uniswap v4 contracts (testnets) need their own compiler profiles:
PoolManagerwith--compilation-profile v4coreandPositionManagerwithposm(both via-IR, seecontracts/foundry.toml). Official deployments are already verified. - The coins and vaults are created by
CurrencyRegistry.createSynthetic, so forge cannot guess their constructor arguments from a creation transaction; the script rebuilds them from the contracts’ own getters. Blockscout marks bytecode-identical contracts as verified once one of them is, so the 60 coins usually need oneSynthTokenand oneFxVaultsubmission. - Blockscout’s public verifier answers “Too many requests” under load; get an API key at https://dev.blockscout.com and pass
--etherscan-api-key. Where a Blockscout API sits behind Cloudflare (Robinhood Chain’s does),VERIFIER=sourcifyis the way in: Blockscout imports Sourcify matches.
The deployment file
Deploy.s.sol writes contracts/deployments/<chainId>.json; the wrappers copy it to packages/abis/deployments/<chainId>.json, which every off-chain process reads (its TypeScript shape is Deployment in @launchpad/abis). Commit it: the web app’s chain switcher, the keeper and the indexer all key off its presence.
The live addresses per chain are listed under Addresses.