Open the app

Integrating as a launcher

Read what changes when the creator is a contract instead of a wallet, how discount tiers apply, and the reverts an interface must surface.

Introduction

A launcher is a contract that opens Hookr markets for its own users. This guide covers what changes when the creator is a contract rather than an EOA, and the two facts that most often surprise integrators.

Your Contract Is the Creator

openNewTokenMarket requires args.expectedCreator == msg.sender, and openExistingTokenMarket records msg.sender as the creator. When your contract calls either one, your contract is the creator of every market it opens.

Three things follow.

Your tier applies to every market you open. protocolShareBps(creator) resolves on the calling address. A tier granted to your launcher applies to every market it ever opens, for every one of your users, until it is cleared. That is the intended integrator mechanism: one tier, granted once, applied to all your users.

lpFeeRecipient is where the founding fees actually go. The creator field records your contract; the fee recipient is a separate parameter. Set it to your user's address, or to a splitter you deploy per launch. It is frozen at creation and cannot be changed afterwards.

intentId is scoped to the creator. launchedByIntent[creator][intentId] guards against replay for one creator address. With a contract launcher that is a single namespace shared by all your users, so derive intentId from something user-specific.

Getting a Tier

Tiers are owner-set on the coordinator and unconditional. There is no application flow in the contracts.

uint24 mine = coordinator.protocolShareBps(address(myLauncher));

The default is 2,000 bps (20%) and the ceiling is 5,000 bps (50%). A tier can be zero. Read your own rate before quoting a fee to a user rather than assuming the default, and read it again in the same transaction you launch in, because the config you submit must match it exactly or admission reverts ProtocolShareTierMismatch.

Predicting the Token Address

Your users will want to know the token address before they sign. Ask the coordinator:

address subject = coordinator.previewNewTokenAddress(args, intentId);

It is a view function of the launch arguments and the intentId, so you can show it in a preview and assert it at launch time by passing the same address as expectedToken. A mismatch reverts UnexpectedToken(expected, actual).

Sizing a Creator Buy

The cap is on the subject the creator receives, not on the quote they spend:

uint256 cap = coordinator.maxInitialBuySubject();  // 5% of supply

To land on it, compute the quote amount from your opening price and the founding band, quote it through the Hookr quoter, and adjust. Then check two other bounds before submitting:

  • The buy must fit inside maxBuyQuoteAmount for its block, if the guard is on. It shares that budget with nobody else in the launch transaction, but the value is your own choice, so set it high enough.
  • The buy must consume its whole input. A price limit that stops it short reverts.

A creator buy that clears the 5% cap but breaks either of these reverts the entire launch, token deployment included.

Two Things That Surprise People

Exact-output sells pay the protocol nothing. The unspecified currency on that swap is the subject token and the protocol never holds subject tokens, so the whole surcharge stays with LPs. If you are modelling revenue, a market traded mostly through exact-output sells earns nothing.

A base-fee-only pool earns nothing either. The protocol's share is carved out of the opt-in add-ons. A pool with no surge, no guard, no burn, no LP reward and no pot produces zero protocol revenue, permanently. Tell a partner who expects a cut of every pool before they build on that expectation.

Fail-Closed Behaviour to Handle

Your interface should surface these rather than retrying blindly.

RevertMeaning
ProtocolShareTierMismatch(expected, actual)Your tier changed between reading it and launching. Re-read and rebuild the config
MarketOpeningPaused(caller)Opening is owner-restricted right now
IntentAlreadyUsed(creator, intentId, subject)Your intent namespace collided. Derive it per user
TokenSaltAlreadyUsed(create2Salt, subject)Same launch arguments were already used. Vary deploymentSalt
InvalidMarketArgsUsually the opening price is not exactly on a usable tick
InitialBuyAboveCap(subjectOut, cap)The creator buy would deliver more than 5% of supply
MaxBuyExceeded(attempted, max)The creator buy breaks the guard's per-block cap
NativeMechanicsModuleRequiredYour selections contain no native mechanics module
TaxedTransferA token moved by a different amount than the coordinator asked for: the new token's supply check after deployment, or a subject transfer. A fee-on-transfer ERC-20 quote fails in the router instead (InputDebitMismatch, SettlementMismatch) or at claim time (ClaimTransferFailed)

Checklist Before Mainnet

  • Read your own protocolShareBps in the same transaction you launch in.
  • Set limits.baseLpFeePips equal to the config's baseFeePips.
  • Set limits.trustedRouter and limits.trustedQuoter to the registered Hookr router and quoter, or the registry rejects the stack (IntegrationOutsideRootProfile).
  • Put the opening price exactly on a usable tick for your tick spacing.
  • Approve the router for an ERC-20 quote if you are doing a creator buy. The coordinator never pulls the quote.
  • Set potMinBuyWei to at least 10 ** (decimals - 3) for an ERC-20 quote with a pot.
  • Test one buy against a burn-enabled pool on a fork before launching, if the subject is not a token you deployed.

Example: A Launcher's Revenue Split

This example puts the pieces above together: a launcher contract opens the market with itself as the creator, splits the founding position's fees between the creator and the integrator, and takes its royalty on the cuts.

// Your launcher contract calls openNewTokenMarket, so msg.sender is the creator.
HookrNativeMechanicsBlockV2.Config memory cfg = HookrNativeMechanicsBlockV2.Config({
    // ... poolId, kernel, subject and the rest, exactly as in "Open a new-token market" ...
    royaltyBps:        500,                                     // 5% of the cuts, to royaltyTo
    royaltyTo:          integratorRoyaltyRecipient,
    protocolRecipient:  treasuryForwarder,
    protocolShareBps:   coordinator.protocolShareBps(address(this))   // your launcher's own tier
});

MarketParams memory market = MarketParams({
    // ...
    lpFeeRecipient:  address(feeSplitter),   // pays the creator's and the integrator's share
    tickSpacing:     60,
    sqrtPriceX96:    OPENING_SQRT_PRICE_X96  // tick 198060: the fixed 2.5 ETH opening valuation, below
    // ...
});

lpFeeRecipient is feeSplitter, a contract you deploy that forwards the founding position's fees between the creator and your launcher on whatever split the two of you have agreed. The coordinator only ever pays the one address named there; the split itself is a fact of your splitter, not of Hookr's.

Every new-token market opens at the same fixed valuation, 2.5 ETH, so sqrtPriceX96 is not a free choice. Against native ETH that valuation needs no conversion: for an 18-decimal subject at a tick spacing of 60, the usable tick nearest it is 198,060. Against USDG or HOOKR the coordinator still only takes a sqrtPriceX96, and it has no ETH price of its own to convert from, so the launcher (the app's wizard, or your own integration) converts 2.5 ETH into that quote's raw units from a reference pool's live spot price when it builds the launch, then lands on the nearest usable tick: the WETH/USDG 0.01% Uniswap v3 pool for a USDG-quoted market, the hookless HOOKR/ETH Uniswap v4 pool for a HOOKR-quoted one. Any other quote has no reference pool this repo has verified, and the app's own wizard refuses to size a launch for one rather than guessing.

Your launcher requests a protocol-share tier by sending its own address: the Hookr owner sets that tier before your launcher's first pool, and it freezes into every pool your launcher opens afterwards, because protocolShareBps(creator) resolves on the calling address and admission reverts ProtocolShareTierMismatch for anything else. At the deployed default of 20%, the base LP fee stays 100% to LPs and each opted-in add-on splits 80/20 between LPs and the protocol. A launcher tier of 10% changes that add-on split to 90/10 for every pool that launcher opens; the base LP fee is still 100% to LPs either way, because the protocol never takes a slice of it.

Next Steps

Read Claiming pot and royalty payouts for how your users claim what the native block owes them: the same ledger pays out regardless of which launcher opened the market. Read Config schema and limits for every bound the validator enforces.