Launching
The inoculate transaction step by step, what it pins, the first buy and its 2.5% fee, the Pons launch gate and attribution.
§One transaction
A virus is launched with a single call to VirusLaunchCoordinator.inoculate. In one transaction the coordinator checks the host, computes the fee terms from live Pons values, deploys the virus's feeder, launches the token on Pons with that feeder as its creator fee recipient, verifies what Pons recorded, binds the feeder, optionally performs the first buy, and registers the virus. If any step fails, the whole transaction reverts and nothing is launched.
function inoculate(InoculateParams calldata p)
external
payable
returns (address virus, address curve, address feeder);
struct InoculateParams {
address host;
uint256 launchConfigId;
bytes32 expectedPonsEconomics; // from ponsFactory.previewLaunchEconomics(launchConfigId, host); required
uint16 expectedCreatorTaxBps; // the creator tax you were shown; must equal the live computation
bytes32 salt;
uint256 firstBuy; // host tokens to spend on the first buy (0 = none, and no inoculation fee)
uint256 minVirusOut; // minimum virus tokens from the first buy; non-zero when firstBuy is
uint256 deadline;
Metadata meta;
}
struct Metadata {
string name;
string symbol;
string logo;
string description;
IPonsV2LaunchFactory.Socials socials; // twitter, telegram, discord, website, farcaster
}
msg.value must equal the Pons launch fee exactly. The call is nonReentrant.
§Before you sign
You need:
- A host with status
VIABLE(see Hosts). - Pons launch access for your own address.
factory.canLaunch(you)must be true. - ETH equal to
factory.launchFee(), plus gas. - For a first buy:
firstBuy + inoculationFeeof the host in your wallet, approved to the coordinator.
§Step by step
The order below follows the contract.
§1. Preflight
| Check | Reverts with |
|---|---|
block.timestamp ≤ deadline |
Expired() |
registry.launchesPaused() == false |
LaunchesPaused() |
ponsFactory.canLaunch(msg.sender) |
CallerCannotLaunch() |
ponsFactory.canLaunch(coordinator) |
CoordinatorCannotLaunch() |
expectedPonsEconomics != 0 |
EconomicsNotPinned() |
firstBuy == 0 or minVirusOut > 0 |
ZeroSlippageBound() |
msg.value == ponsFactory.launchFee() |
WrongLaunchFee(sent, required) |
| Metadata within Pons caps: name 1–64 bytes, symbol 1–16 bytes, logo ≤ 512, description ≤ 2048, each social ≤ 256 | InvalidMetadata() |
The metadata caps mirror PonsV2LaunchDeployer, so an oversized field fails here with a clear error rather than inside Pons.
§2. Host checks
hostCheck(host) must return viable == true, or the call reverts with HostNotViable(host). This covers Pons launch-token provenance, Pons pair approval, usable pair economics with 18 decimals, the VIRUS disabled flag, and depth ≤ 3.
§3. Economics
previewEconomics(launchConfigId) reads the live launch config and the live Pons fee policy (memeHook.currentFeePolicy()), and computes the creator tax:
creatorTaxBps = requiredCreatorTax(curveFeeBps, protocolFeeShareBps)
| Check | Reverts with |
|---|---|
The launch config is enabled |
ConfigDisabled(launchConfigId) |
Tax ≤ maxCreatorTaxBps, tax fits in uint16, curveFeeBps + tax ≤ 2000, hookFeeBps + tax ≤ 2000 |
CreatorTaxUnreachable(required, cap) |
Tax equals expectedCreatorTaxBps |
CreatorTaxMismatch(expected, actual) |
The formula is explained in Fees and burns.
§4. Deploy the feeder
The coordinator reads the current treasury from the registry and deploys a new VirusFeeder clone:
feederSalt = keccak256(abi.encode(msg.sender, p.salt))
config = { host, developer = msg.sender, treasury = registry.treasury(), registry,
ponsFactory, feeEscrow = ponsFactory.feeEscrow(),
memeHook = ponsFactory.memeHook(), feederFactory }
The feeder's address depends on your address, your salt and this config, and can be predicted beforehand with coordinator.predictFeeder(developer, userSalt, host). Every field of config is immutable from this moment.
The feeder has to exist before the launch because Pons needs its address as the creator fee recipient. It cannot be bound to the virus yet: the virus address is only known once Pons has deployed it, and VIRUS does not assume Pons launch addresses are predictable (the published Pons source is inconsistent on this point, UNVERIFIED X2). Binding happens in step 7, in the same transaction.
§5. Launch on Pons
(virus, curve) = ponsFactory.launchToken{value: msg.value}(
TokenParams({
name, symbol, logo, description, socials,
creatorFeeRecipient: feeder,
creatorTaxBps: creatorTaxBps,
buybackEnabled: false,
expectedEconomics: p.expectedPonsEconomics,
salt: feederSalt
}),
p.launchConfigId,
p.host // pairToken
);
Pons performs its own checks here, including its launch gate on the coordinator, pair approval of the host, and the economics digest: if any of the ten pinned terms moved since you previewed them, Pons reverts. The launch fee is forwarded unchanged; Pons sends it to memeHook.protocolFeeRecipient().
§6. Verify what Pons recorded
The coordinator re-reads ponsFactory.getLaunchedToken(virus) and the new curve, and reverts with LaunchVerificationFailed() unless:
- the record exists,
token == virus,curvematches; pairToken == host;creatorFeeRecipient == feeder;buybackEnabled == false;creatorTaxBpsequals the computed tax;curve.pairToken() == host,curve.feeBps() == curveFeeBps,curve.creatorTaxBps() == tax,curve.token() == virus.
§7. Bind the feeder
feederFactory.bind(feeder, virus, curve) records the pair once in the factory and calls feeder.bind(virus, curve), which checks the same Pons record again (BindMismatch on any difference) and stores the V4 pool id the virus will graduate into. A feeder can never be rebound and a virus can never get a second feeder.
§8. Optional first buy
If firstBuy > 0, the coordinator performs the first infection (next section).
§9. Register
registry.registerVirus(...) writes the virus record, assigns the next strain number, registers the host if this is its first virus, and emits VirusInoculated and InoculationRecorded (plus HostRegistered for a new host). Registration happens after the first buy so that the record stores the host actually spent and the fee actually charged. Finally the coordinator emits Inoculated(virus, host, developer, feeder, curve, creatorTaxBps, ponsEconomics).
§What gets pinned
A launch signed through VIRUS cannot land on terms you were not shown. Two values are pinned, and both are required:
| Pin | What it covers | Checked by |
|---|---|---|
expectedPonsEconomics |
The Pons digest from previewLaunchEconomics(launchConfigId, host), a hash over ten terms: phantomQuote, graduationThreshold, supply, curveFeeBps, poolFee, tickSpacing, protocolFeeShareBps, buybackBurnBps, hookFeeBps, maxInternalPriceImpactBps |
Pons, inside launchToken. VIRUS refuses a zero digest. |
expectedCreatorTaxBps |
The creator tax VIRUS computed for you | The coordinator (CreatorTaxMismatch) |
The tax is pinned separately because the Pons digest does not include maxCreatorTaxBps, and because the tax is derived from digest terms by VIRUS, not by Pons (VIRUS_ADAPTATION A5).
§The first buy and the inoculation fee
The first buy is optional. It lets the developer buy the virus in the same transaction that creates it, before anyone else can trade.
The inoculation fee is 2.5% of the host actually spent on the first buy, rounded up, paid in host to the VIRUS treasury:
inoculationFee(x) = 0 if x == 0
= ceil(x × 250 / 10,000) otherwise
No first buy means no inoculation fee. The Pons ETH launch fee is separate and is always due.
The mechanics, from _firstInfection:
maxFee = inoculationFee(firstBuy). Pull exactlyfirstBuy + maxFeehost from you. If the coordinator's balance did not rise by exactly that amount, revertInexactHostTransfer(expected, received).- Approve the curve for
firstBuy, callcurve.buy(firstBuy, minVirusOut, coordinator), then reset the approval to zero. - Measure the virus tokens received and any host the curve refunded.
spent = firstBuy − unspent, andfee = inoculationFee(spent).- Send
feeto the treasury recorded for this launch. Refundunspent + (maxFee − fee)host to you. Send all virus tokens to you. - Emit
FirstInfection(virus, developer, spent, fee, virusOut, refund).
§Partial fill
If the first buy is large enough to sell out the curve, Pons fills it only up to the sellable allocation and refunds the rest of the host (VERIFIED_PONS C6). VIRUS then charges the 2.5% only on the host the curve actually consumed, and refunds both the unspent host and the unearned part of the fee (security invariant 30).
Example with illustrative round numbers: you set firstBuy = 10,000 host. The coordinator pulls 10,250. The curve sells out after consuming 8,000 and refunds 2,000. The fee is ceil(8,000 × 0.025) = 200. You receive 2,000 + (250 − 200) = 2,050 host back, plus the virus tokens.
The coordinator holds no host, virus or ETH after the transaction (security invariant 25).
§The Pons launch fee
Pons charges a flat ETH fee per launch, factory.launchFee(), and requires msg.value to equal it exactly (VERIFIED_PONS F7). The coordinator forwards it unchanged; VIRUS takes no part of it. The app always shows it as its own line, in ETH, separate from the inoculation fee, which is in host. Its live value is read at run time; these docs do not state it as fact.
§The confirmation screen
Before you sign, the app shows everything previewInoculation(caller, host, launchConfigId, firstBuy) returns, plus the transaction summary:
| Field | Source |
|---|---|
| Host, its status and each check A–H, host depth and virus depth | hostCheck |
| Launch config, supply, curve base fee, pool hook fee, protocol share | previewEconomics |
| Creator tax, the Pons cap, and whether the tax fits | previewEconomics |
Curve-phase and pool-phase fee rows, parity (EXACT or PONS-NATIVE) and delta |
previewEconomics |
| Pons economics digest that will be pinned | previewLaunchEconomics |
| First buy, inoculation fee, total host required | previewInoculation |
| Estimated virus out, slippage, MINIMUM FIRST INFECTION | previewInoculation, slippage setting |
| PONS LAUNCH FEE in ETH | launchFee() |
| VIRUS INOCULATION FEE in host (only with a first buy) | inoculationFee(firstBuy) |
| Whether you and the coordinator pass the Pons gate, whether VIRUS launches are paused, and the overall verdict | callerCanLaunch, coordinatorCanLaunch, launchesPaused, canInoculate |
Method inoculate(InoculateParams) on VirusLaunchCoordinator |
Transaction plan |
The estimate prices the first buy against a fresh curve: base fee and creator tax floored off the input, the net priced against the host's phantom reserve, clamped to the sellable allocation. The transaction plan refuses to build if the digest is zero or if a first buy has a zero minimum.
§The Pons launch gate
Pons decides who may launch: canLaunch(a) = launchEnabled || whitelistedLaunchers[a] (VERIFIED_PONS F6). Pons applies the check to msg.sender of launchToken, which for a virus is the coordinator.
If VIRUS relied on that alone, a Pons whitelisting of the coordinator would open launching to everyone who calls the coordinator. VIRUS prevents that: inoculate requires canLaunch(msg.sender) and canLaunch(coordinator) (VIRUS_ADAPTATION A8, security invariant 26). VIRUS never bypasses the Pons whitelist. When the gate is closed, the app runs in LAB MODE with INOCULATE disabled.
VIRUS also cannot use the official Pons launch-and-buy router. Pons restricts launchTokenFor to a single launchForwarder, and that router is not published (BLOCKED_BY_PONS X3/B8). The coordinator performs the launch and the first buy atomically itself, without forwarder privileges.
§The coordinator's snipe-tax exemption
Pons applies a snipe tax during a short window after each launch, and automatically exempts the launch's originalDeployer and creatorFeeRecipient (VERIFIED_PONS F15, factory side). Because the coordinator calls launchToken, it is the originalDeployer of every virus and therefore exempt on every VIRUS curve.
That exemption is used for one thing only: the atomic first buy of the virus the coordinator just launched. The coordinator has no trade function, no receive function and no admin, so no one can route other trades through it (security invariant 24). The first buy is made with recipient = coordinator and then transferred to you, so it is exempt whether the deployed curve keys the exemption by msg.sender or by recipient. Which one it uses is UNVERIFIED (see Trading).
The coordinator calls the launchToken variant without an extra exemption list. After the launch transaction, your own trades on the virus are treated like anyone else's.
§Attribution
Pons stores deployer metadata on every launch token. For a virus, token.deployer() reads as the coordinator's address, not yours, because the coordinator is the contract that called Pons (VERIFIED_PONS T2). Any tool that reads deployer() from the token will see the coordinator.
The real developer is recorded by VIRUS:
registry.getVirus(virus).developerfeeder.developer(), an immutable clone argument- the indexed
developertopic ofInoculated,VirusInoculatedandFirstInfection
The developer's 30% share can only ever be paid to that address.
§After launch
| Where | What you can read |
|---|---|
| Pons | getLaunchedToken(virus): pair token = host, creator fee recipient = feeder, buyback off, creator tax |
| VirusRegistry | getVirus(virus): host, feeder, developer, treasury, depth, strain, launch block, creator tax, launch config, graduation threshold, first buy, inoculation fee, and running totals |
| VirusFeeder | state(): phase, recipient status, escrowed, undistributed, developer balances, totals |
To confirm each of these yourself, see Verify on chain.