Skip to content
SECTION 04 / 15

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 + inoculationFee of 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, curve matches;
  • pairToken == host;
  • creatorFeeRecipient == feeder;
  • buybackEnabled == false;
  • creatorTaxBps equals 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:

  1. maxFee = inoculationFee(firstBuy). Pull exactly firstBuy + maxFee host from you. If the coordinator's balance did not rise by exactly that amount, revert InexactHostTransfer(expected, received).
  2. Approve the curve for firstBuy, call curve.buy(firstBuy, minVirusOut, coordinator), then reset the approval to zero.
  3. Measure the virus tokens received and any host the curve refunded.
  4. spent = firstBuy − unspent, and fee = inoculationFee(spent).
  5. Send fee to the treasury recorded for this launch. Refund unspent + (maxFee − fee) host to you. Send all virus tokens to you.
  6. 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).developer
  • feeder.developer(), an immutable clone argument
  • the indexed developer topic of Inoculated, VirusInoculated and FirstInfection

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.