Appearance
Launching
Launching is one call to the factory.
solidity
struct LaunchParams {
string name; // 1-32 bytes
string symbol; // 1-12 bytes
string metadataURI; // <= 512 bytes, e.g. data:application/json;base64,...
address creator; // fee recipient; address(0) = msg.sender
uint16 creatorTaxBps; // within [minCreatorTaxBps, maxCreatorTaxBps]
bool buybackEnabled;
uint40 tradingStartsAt; // 0 = now; at most 7 days ahead
uint256 devBuyQuote; // ETH for the atomic first buy, 0 to skip
uint256 devBuyMinTokens;
address[] snipeExempt; // up to 32 extra wallets
bytes32 salt; // namespaced per launcher
}
function launch(LaunchParams calldata p, uint256 configId, bytes32 expectedTerms)
external payable returns (address token, address curve);msg.value must equal launchFee() + devBuyQuote.
Pinning terms
Read previewTerms(configId) immediately before launching and pass the result as expectedTerms. If the owner changes any launch economics in between, the call reverts with TermsMismatch instead of settling on terms you did not read.
ts
import { parseAbi, toHex } from "viem";
const factoryAbi = parseAbi([
"struct LaunchParams { string name; string symbol; string metadataURI; address creator; uint16 creatorTaxBps; bool buybackEnabled; uint40 tradingStartsAt; uint256 devBuyQuote; uint256 devBuyMinTokens; address[] snipeExempt; bytes32 salt }",
"function previewTerms(uint256 configId) view returns (bytes32)",
"function launchFee() view returns (uint256)",
"function launch(LaunchParams p, uint256 configId, bytes32 expectedTerms) payable returns (address token, address curve)",
]);
const configId = 0n;
const [terms, fee] = await Promise.all([
client.readContract({ address: factory, abi: factoryAbi, functionName: "previewTerms", args: [configId] }),
client.readContract({ address: factory, abi: factoryAbi, functionName: "launchFee" }),
]);
const devBuy = parseEther("0.0005");
await wallet.writeContract({
address: factory,
abi: factoryAbi,
functionName: "launch",
args: [
{
name: "Roof Tile",
symbol: "TILE",
metadataURI: "data:application/json;base64,...",
creator: account.address,
creatorTaxBps: 300,
buybackEnabled: false,
tradingStartsAt: 0,
devBuyQuote: devBuy,
devBuyMinTokens: 0n,
snipeExempt: [],
salt: toHex(crypto.getRandomValues(new Uint8Array(32))),
},
configId,
terms,
],
value: fee + devBuy,
});Metadata format
metadataURI is stored on-chain, so the app packs everything into a compact data URI:
json
{ "image": "ipfs://<cid>", "description": "...", "website": "...", "twitter": "...", "telegram": "..." }base64-encoded as data:application/json;base64,.... Images are uploaded to IPFS separately; only the CID goes on-chain. Any ipfs://, https:// or data:image/ value works for image.
Deterministic addresses
Tokens and curves are deployed with CREATE2. The salt is namespaced by the launching wallet, so no one can occupy your predicted addresses by submitting your salt first, and you can mine a vanity address offline:
solidity
function predictLaunch(address launcher, LaunchParams calldata p, uint256 configId)
external view returns (address token, address curve);WARNING
The curve's address depends on tradingStartsAt, which is normalised to block.timestamp when passed as 0. To predict accurately, pass an explicit future start time.
Access
canLaunch(address) returns whether a wallet may launch right now (public launching open, or the wallet is whitelisted). A disabled config reverts with ConfigDisabled.