Skip to main content
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:
  1. A mintable test dollar (MockERC20, 6 decimals, one million units to the deployer), only with DEPLOY_TEST_DOLLAR=true on a testnet without a real dollar.
  2. KeeperFxOracle with a 2 hour fresh window, a 5 day stale limit and maxMoveBps 1500; the KEEPER address is allow-listed and EUR plus the 60 codes of Currencies.sol are enabled. PythFxOracle (60 s fresh, 5 days stale, confidence 100) only where the chain table or PYTH names a Pyth contract; its feeds come from PythFeeds.sol.
  3. CurrencyRegistry pointed at the dollar and the keeper oracle. EUR is registered as a real currency when EURC is known. Then one SynthToken plus FxVault per synthetic currency (up to MAX_CURRENCIES, default 60) through registry.createSynthetic, every vault with 0.3% mint and redeem fees, 1% stale fee, 20% protocol share, minCRBps 10,000 (fully backed by the dollars paid in) and a 1,000,000 dollar liability cap.
  4. Uniswap v4: the official PoolManager, PositionManager, StateView and Quoter when the chain table or the environment names them, otherwise the script deploys its own set (plus a WETH for the position manager).
  5. The launchpad: LiquidityLocker, the BondingCurve implementation, LaunchFactory and Router. 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.
The parameters behind these numbers are explained in Fees and Architecture.

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

The script refuses to run when the RPC’s chain id differs from 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

Every currency needs a rate before anything can be minted or launched in it. Then keep pnpm --filter @launchpad/keeper start running (Keeper).
4

Seed (optional)

Backs the INR, JPY and NGN vaults with 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 a PRIVATE_KEY in a file. scripts/deploy-mainnet.sh refuses testnets, unsets PRIVATE_KEY, and requires KEEPER.
1

Create the keystore once

Keep both files readable only by your account. 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).
The deployer keeps ownership of every contract. They are Ownable2Step, so moving control to a hardware wallet or a multisig takes transferOwnership by the deployer followed by acceptOwnership by the new owner. The keeper address only holds the oracle’s keeper role. Nothing here is audited: do not hold real money in the protocol before an external audit.

Verification

scripts/verify.sh verifies every contract of a deployment on the chain’s Blockscout (explorerApi in the registry), skipping the ones already verified:
Details worth knowing:
  • VERIFIER_URL overrides the API, RPC the endpoint, DELAY the pause between submissions (4 s).
  • The self-deployed Uniswap v4 contracts (testnets) need their own compiler profiles: PoolManager with --compilation-profile v4core and PositionManager with posm (both via-IR, see contracts/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 one SynthToken and one FxVault submission.
  • 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=sourcify is 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.