# Build a module

Write, test and preview a module on your own machine, against the real contracts. If this page and the Solidity disagree, the Solidity wins.

## Two kinds

A **launch module** gets a token’s market started. A **token module** acts on a live token’s transfers and trades. How many a token carries is under [Modules](https://poo.meme/docs/start/overview#modules). A contract is never both.

**Token modules are open to anyone, and this page is about writing one.** POO.MEME publishes the launch modules: today the registry takes `publishLaunch` from its owner alone. That is a switch the owner holds open or closed, not something the contract fixes — ask the registry’s `launchPublishingOpen()` for where it stands on the chain you are on.

So your module is a token module:

- **Factory**: `IPooTokenModuleFactory`
- **Instance**: `IPooTokenModule`
- **Hooks**: Gate, Track, Receive, Operate on sell, Operate on buy
- **Installer**: `IPooTokenModuleInstaller`
- **Publish**: `publishTokenModule`

## Set up

You need Node 22+ and [Foundry](https://getfoundry.sh). Install the `poo` CLI from npm, then start a module:

```text
npm install -g @poomeme/sdk
poo init my-module
```

`poo init` names the contract and its handle after the folder, then builds and tests the project.

| Command | What it does |
| --- | --- |
| `forge test` | Publishes your module to a real registry on a throwaway chain |
| `poo preview` | Draws the page your manifest asks for, and says if the registry accepts it |
| `poo dev` | Starts a local chain with POO.MEME and a token that installs your module |
| `poo check` | Refuses imports outside the published surface |
| `poo publish` | Checks a deployed factory; with `--broadcast`, publishes it |

`my-module/`

```text
src/MyModule.sol          the contract a token installs
src/MyModuleFactory.sol   its manifest, its probe, how instances are made
test/MyModule.t.sol       POO.MEME in memory, publishing your module
script/Poo.s.sol          the same as a script: poo dev broadcasts it, poo preview runs it in memory
lib/poo-sdk/              the published surface, test kit and dependencies
```

### Toolchain

`poo init` writes the compiler settings POO.MEME itself builds with:

`foundry.toml`

```toml
[profile.default]
src = "src"
out = "out"
libs = ["lib"]
test = "test"
solc_version = "0.8.36"
auto_detect_solc = false
evm_version = "cancun"
optimizer = true
optimizer_runs = 200
via_ir = true
```

### Imports

`remappings.txt`

```text
@openzeppelin/=lib/poo-sdk/lib/openzeppelin-contracts/
@uniswap/v2-core/=lib/poo-sdk/lib/v2-core/contracts/
@uniswap/v2-periphery/=lib/poo-sdk/lib/v2-periphery/contracts/
forge-std/=lib/poo-sdk/lib/forge-std/src/
@standard/=lib/poo-sdk/src/standard/
@launch-module/=lib/poo-sdk/src/launch-module/
@token-module/=lib/poo-sdk/src/token-module/
@modules/=src/

@poo/devkit/=lib/poo-sdk/devkit/
@registry/=lib/poo-sdk/src/registry/
@token/=lib/poo-sdk/src/token/
```

- Sources may import the first block: POO.MEME’s interfaces and types, OpenZeppelin and the Uniswap V2 interfaces.
- Tests and scripts may also import the second block, to stand POO.MEME up.
- `poo check` refuses a source that imports a file outside the published surface, however the path is spelled. Build against interfaces, never against POO.MEME’s own contracts.

## Token modules

Your ERC-165 answers are your hooks. The registry reads them from your probe and pins them; the token calls only those. A *leg* below is one transfer: a buy, a sell or a wallet transfer. Signatures and bit values are in the [Reference](https://poo.meme/docs/developers/reference#hooks).

| Hook | Call | When | If It Fails |
| --- | --- | --- | --- |
| Gate | `gate` | Each buy by a non-exempt address, before tax (static call); never a liquidity removal or a skim | The buy reverts |
| Track | `track` | Each leg outside an Operate run | Skipped |
| Receive | `onReceive` | After your tax row is paid, naming the asset it was paid in | Skipped; funds already arrived |
| Operate on sell | `operateOnSell` | In a run, just before a taxed sell or inside `process()`; may move the token | Skipped |
| Operate on buy | `operateOnBuy` | In a run, inside a taxed buy, after the gate has let it through; may move the token | Skipped |

Declare either side of Operate, or both: they are two bits over one run, and one `operateGas` serves whichever you declare. Declaring a side without that gas is refused, and declaring the gas without a side is refused too. Every other call gets exactly the gas your manifest declares for it.

### Rules for every hook

- Accept calls from the token only. The probe’s token is the registry’s stand-in, which makes the publishing calls, so publishing still passes.
- Return nothing.
- In Gate, Track and Receive the token is locked: moving it reverts `Locked()`. A run is the only hook that may move it.
- A skipped call is never retried. Work from balances, not from counted callbacks: if you pay holders, pay on the balance still held.
- Publishing calls each hook once; Gate must allow `gate(publisher, 1)`. A hook declared with zero gas is refused.

### Exemption

Your instance is exempt with its token: legs with it skip Gate and the token’s tax. That is the only exemption there is, it is decided when the token is created, and **you cannot give it to anything else** — there is no hook and no call for it. Say in your description whether your instance opens a [route around the tax](https://poo.meme/docs/protocol/contracts#route).

### Operate runs

A run is one mechanism with two sides, armed on one trade and delivered on the next of its kind.

| Side | Armed by | Delivered |
| --- | --- | --- |
| Operate on sell | Every taxed sell, the token’s own tax sale, and your `requestRun()` from outside a transfer — you must declare this side to request one | At the head of the next taxed sell, or inside `process()` |
| Operate on buy | Every taxed buy. A liquidity removal or a `skim` is not a buy and arms nothing | Inside the next taxed buy, after its gate has let it through |

An exempt trade, a wallet transfer and a leg the token is already running arm nothing on either side.

**The two sides are not symmetrical, and an author will assume they are.** `process()` drains the sell side alone — a buy run has no buyer to hand it outside a buy, so there is no way to reach the buy side except by buying. `requestRun()` therefore arms the sell side only, and a module that declares **just** the buy side is refused with `BuyRunNotRequestable()` rather than quietly given nothing: every taxed buy already arms you, so a request would add nothing there. If your module must act without a trade, the sell side is the only one that can.

One transaction can reach you twice on either side: a trade delivers what is armed and then arms you again, so a second trade of that kind in the same transaction runs you a second time, at a price that has barely moved. Cap what one transaction can take out, not only what one run can.

A leg the token is itself running has your module on one side of it, which makes it an [exempt leg](https://poo.meme/docs/developers/build#exempt): it skips every gate and the token’s own tax, and [POO.MEME’s share](https://poo.meme/docs/protocol/fees#share) still accrues on it at whatever rate the token took.

A leg inside your run that your module is on **neither** side of is not exempt at all, and an author will expect it to be. It is classified like any other leg: a buy or a sell there pays the token’s whole table and a buy meets every Gate, exactly as it would outside a run. It also has a gas floor of its own, checked at that leg as usual — so a run that moves someone else’s tokens has to leave the gas behind for it, and a run that starves there is undone and skipped like any other.

Each run is told where it runs and the trader and amount that triggered it: `context` sets `RUN_ARMED_BY_ROUND`, `RUN_ARMED_BY_REQUEST` or both on the sell side, `RUN_IN_PROCESS` inside `process()`, where the trader and amount are zero, and `RUN_ON_BUY` — alone — on the buy side.

The trade that delivers a run — the sell on one side, the buy on the other — completes after you, at the price you left. So:

- Cap how much one run moves, as a share of live reserves.
- Trade round trips inside one run, so the price ends where it started.
- Set a floor on every swap from the reserves it faces.
- Size a supply release from a price you recorded over several blocks — the lowest of the last few closes, not the pool’s spot price, which the trade in front of you set.
- Cap a release per transaction as well as per run, so no one trade can take out more than a fixed slice of what you hold.
- Describe what a run does and how far it can move the price.

The token enforces the outcome, ahead of a sell and inside `process()`: an Operate run that leaves less quote in the pool, a lower price with more quote, or unsynced tokens is undone, and the sell goes on. An undone run is not armed again for that round. Adding tokens and syncing passes, and so does a round trip that sells back what it bought. The token gives the guard 20,000 gas beyond your declared cap, and charges it to the sell floor.

## The launch module beside you

Your token module shares its token with exactly one launch module, and that shapes what you can rely on:

- The token mints the launch’s whole share of the supply straight to it. It holds any money the launch raises, pays the refunds, creates and seeds the token’s pool, and schedules the opening. POO.MEME does none of it.
- `scheduleOpen(openAt)` is the launch module’s alone, once, up to 72 hours ahead and only once the pool holds liquidity; `open()` is anyone’s from the scheduled time. Launched is the token’s own `openedAt` and no module reports it.
- No buy or sell reaches you before then, because a trade on an unopened token is refused; wallet transfers happen from creation onward and reach Track. A run requested while the token was still closed is delivered inside `open()`.
- It is untrusted like everything else, and [what publishing cannot check](https://poo.meme/docs/protocol/contracts#modules) nobody checks. Whether it locks the liquidity it adds, and for how long, is in its own description.

## The manifest

`manifest()` is your module’s whole interface: the token page draws exactly what it declares. The registry pins its bytes, so build it `pure` or from immutables.

| Member | Rule |
| --- | --- |
| `name` | Not empty |
| `summary` | One line, 1–120 bytes |
| `description` | Up to 1,000 bytes: your disclosure |
| `handle` | Your module family’s [handle](https://poo.meme/docs/start/overview#handles) |
| `requires` | What your module accepts and needs (below) |
| `seeds` | What a creator pays your instance at creation |
| `receiveGas … operateGas` | Each hook’s exact gas; zero without it, and one `operateGas` for both sides of a run |
| `lifecycle, reads` | Launch modules only: statuses, and the figures POO.MEME shows for the launch |
| `probeConfig, config` | The probe’s config, and the fields a creator fills in |
| `sections, events, errors` | Your panel, your lists, and a sentence per error |

### Requirements

| Member | Meaning | Refused As |
| --- | --- | --- |
| `quote` | `Any`, or one quote kind | `QuoteKindMismatch` |
| `unique` | One seat per token for your handle | `DuplicateTokenModule` |
| `minSupplyBps` | Least share of supply | `SupplyShareBelowMinimum` |
| `minTotalBps` | Least buy + sell tax for your row | `MinTotalBpsNotMet` |
| `taxAsset` | The asset your tax row is paid in: `None` (no row), `Quote` or `Token` | `TaxShareNotAccepted` |
| `acceptsSupply` | Takes a supply share | `SupplyShareNotAccepted` |

A minimum needs its share: publishing refuses `minTotalBps` with a `taxAsset` of `None`, and `minSupplyBps` without `acceptsSupply`.

### Config and fields

`config` is your fields as one ABI tuple, in order: `abi.encode(MyConfig({...}))`, read back with `abi.decode(config, (MyConfig))`. `probeConfig` is encoded the same way and must fit the declaration (`ProbeConfigInvalid`): empty for an empty `config`, opening with the word `0x20` when any field is dynamic, otherwise exactly 32 bytes per field.

| Type | In `config` | In An Action Input |
| --- | --- | --- |
| `address`, `bool`, `string`, `bytes`, `bytes32`, `uint8` to `uint256` in steps of 8 | Yes | Yes |
| A list of any of those but `bytes`, such as `address[]` | No | Yes |
| `bytes[]`, bare `uint`, signed integers, tuples, fixed or nested arrays | No | No |

Any other type is `UnsupportedFieldType`; a name used twice in one list is `FieldNameRepeated`. A structure `create` needs travels as one `bytes` field your code decodes.

- `min`, `max`, `defaultValue` and `dependsValue` are `abi.encode` of one value of the field’s type. The registry does not read them and the page enforces them, so repeat every bound and cross-field rule on chain.
- `dependsOn` indexes another field: this one is drawn only while that field equals `dependsValue`. An empty `dependsValue` draws it always.
- A blank `optional` field and a hidden field encode zero. The create page puts required fields first and folds optional ones into one group; the encoding keeps your order.

| The Field Declares | Drawn As | Encodes |
| --- | --- | --- |
| `uintN[]` with `options` | A checkbox group | The indexes checked |
| Any other list | One item per line; consecutive list inputs of one action share one editor of parallel columns | The array |
| `uintN` with `options` | A one-of choice | The option’s index |
| `bool` | A checkbox when `optional` or given a `defaultValue`, otherwise a choice left open until picked; two `options` name false and true | `false` or `true` |
| `address` | An address box; on the token page, with quick fills for the wallet, the token, its pair and its modules | The address |
| `bytes32`, `bytes` | A hex box | The bytes |
| `string` | A line, or a box when `multiline` | The text |
| `uintN` in `Timestamp` | A date and time, in UTC | Unix seconds |
| `uintN` in `Duration` | Days, hours, minutes and seconds | Seconds |
| `uintN` in `Bps` | A percentage in steps of 0.01; a slider when `min` and `max` are set | Bps |
| `uintN` in `TokenAmount` or `QuoteAmount` | An amount in that asset, accepting `10k` and `2.5m` | Base units |
| `uintN` in `Price` | The whole tokens one whole quote token buys | Token base units per quote base unit × 10^18 |
| `uintN` in `Raw` with `assetOf` | An amount in that ERC-20 | Base units |
| `uintN` in `Raw` named `multipleWad` | A multiple, entered plainly and suffixed `×` | The multiple × 10^18 |
| Any other `uintN` | A whole number | The number |

- `Price` is the ratio `tokensOut = quoteIn * price / 1e18`. It carries 18 + token decimals − quote decimals places, and a finer figure is refused, never rounded.
- `maxOf` and `assetOf` belong only on `Raw`, `TokenAmount` or `QuoteAmount`, and `presetBps` also on `Bps` (`FieldAmountRuleOnNonAmount`).
- `maxOf` is `selector(address viewer) -> uint256`, the most this wallet may enter now. An action’s amount is capped at the smaller of it and what the wallet holds.
- `assetOf` is `selector() -> address`, the ERC-20 the amount is in, and must answer nonzero (`FieldAssetUnanswered`). Zero means the unit’s own asset.
- `presetBps` are quick fills as bps of that cap, strictly increasing within 1–10,000 (`FieldPresetsInvalid`). An action amount without them gets 25 %, 50 %, 75 % and Max.
- `valueOf` makes an action input your module’s own answer, `selector() ->` the input’s type, read live and drawn as a fact. Action inputs only (`FieldValueOfOnConfig`), static types only (`FieldValueOfDynamic`).
- `multiline` is for `string` only (`FieldMultilineOnNonText`).
- A token module whose config has exactly one `address` field named `owner`, in `Unit.Address`, is drawn as an owner choice: Creator, Another address or None. Treat zero as no owner, with every owner-only setting final.
- A `Unit.Raw` number named `multipleWad`, or whose name ends in `MultipleWad`, is a multiple of something you already name — "how many times the opening". The creator writes `20` and your module reads `20e18`, so declare `min` and `max` at that same WAD scale; the create page draws them back as `5×` and `1,000×` and refuses anything outside. On the create page it also names the market cap a target lands on beside the box — `$50,000 (20×)` — from the opening pool the chosen launch module answers with. Any other `Raw` number keeps the plain whole-number box.

### Sections and views

A `Section` is a titled list of items. Each `Item` names one of the section’s own views, actions or lists.

- `items` is the drawing order and names every element exactly once (`ItemIndexOutOfRange`, `ItemRepeated`, `ItemUnreferenced`). A `Gap` item holds an empty cell, with index 0.
- `columns` is 0–4, where 0 takes the page’s own grid (`ColumnsOutOfRange`). An item’s `span` is at most the columns, or 4 without them; 0 takes the kind’s default (`SpanOutOfRange`).
- `role` says where you offer the section: `Body` in your panel, in your order; `Progress`, `Terms` or `Trade` to those parts of the page. A `Trade` section docks beside the market.
- `visibleWhen`, on a section, view or action, is a `Condition`: a read answering a word, where nonzero shows and zero hides, asked as `selector()` or, with `withViewer`, `selector(address viewer)`. Its `label` says when the item shows, 1–80 bytes, and is empty exactly when the selector is zero (`ConditionLabelInvalid`). A viewer condition waits for a connected wallet. A condition is a hint; your code stays the authority.
- A view that answers nothing is not drawn: a zero, an empty string, the zero address or an empty list. A `State` is the exception, since its zero is a word. A section with nothing left to draw disappears.

A `View` is one typed read. Its widget sets how many selectors it takes (`ViewShapeInvalid`) and what the registry asks on your probe:

| Widget | `selectors` | Drawn As |
| --- | --- | --- |
| `Figure` | `[value]`, or `[value, multiple]` | A labelled figure in `unit`, with `multiple` drawn beside it in parentheses |
| `Progress` | `[current, marker, max]`; `marker` and `max` may be zero | A bar; zero `max` is uncapped, and `labels[0]` names the marker |
| `Countdown` | `[timestamp]` | The moment, and how far off while ahead |
| `State` | `[index]` | `labels[index]` in `tones[index]`; needs labels (`StateWithoutLabels`), never in `Text` or `Address` (`StateUnitInvalid`) |
| `Address` | `[address]` | A short address with copy and explorer links |
| `Text` | `[string]` | Plain text, never markup or links |
| `Rows` | `[count, at]`, with `at(uint256 index)` | A paged list; `at` answers every index, zero past the end |
| `Series` | `[sample, start, end]`, with `sample(uint256 x)` | A curve over your own domain; `sample` answers everywhere |

- Every read the registry asks must return at least one 32-byte word, or it is `SelectorNotAnswered`. `Rows` and `Series` are asked at index 0 and at `type(uint256).max`.
- A view in `Unit.AssetAmount` adds one last selector answering the ERC-20 it counts in: nonzero (`FieldAssetUnanswered`) and answering `decimals()` (`AssetNotAnErc20`).
- A `Figure` may declare one optional second selector, probed exactly as its first and refused when it is zero or unanswered. It draws in parentheses after the figure as a plain multiple — `$12.5k (25×)` — never in the view's own unit. The `Unit.AssetAmount` pointer stays last, so a figure that wants both declares `[value, multiple, asset]`, while `[value, asset]` is a figure with no multiple.
- `tones` run beside `labels`: `Neutral`, `Positive`, `Negative` or `Warning`; empty is Neutral throughout.

`poo init` writes a section like this one:

`src/MyModuleFactory.sol`

```solidity
function _streakSection() private pure returns (Section memory s) {
    s.title = "Streak";
    s.views = new View[](2);
    s.views[0] = _figure("Minimum balance", MyModule.threshold.selector, false, Unit.TokenAmount);
    s.views[1] = _figure("Your streak", MyModule.streakOf.selector, true, Unit.Duration);
    s.lists = new ListDecl[](0);
    s.actions = new Action[](1);
    s.actions[0] = _forgetAction();
    s.columns = 2;
    s.items = new Item[](3);
    s.items[0] = Item({kind: ItemKind.View, span: 0, index: 0});
    s.items[1] = Item({kind: ItemKind.View, span: 0, index: 1});
    s.items[2] = Item({kind: ItemKind.Action, span: 0, index: 0});
}
```

### Actions

An `Action` is a transaction the page offers: a call when `inputs` is empty, a form otherwise. The page shows `name` in sentence case, with `description` beneath.

- `name` is the called function’s exact name and `inputs` its exact parameters, in order. The registry rebuilds the selector from them (`ActionSelectorMismatch`) and refuses an empty name (`ActionNameEmpty`).
- `preview` is zero, or a view over exactly the action’s inputs returning `uint256` in `previewUnit`; without one, `previewUnit` is `Raw` (`PreviewUnitWithoutPreview`). When every input is static the registry asks it with every input zero, so answer zero rather than revert.
- `minOutInput` and `deadlineInput` name an input as its index + 1; 0 names none. The minimum is a required `uint256` in `TokenAmount` or `QuoteAmount`, on an action whose preview answers in that same unit. The deadline is a required `uint256` or `uint64` in `Timestamp`. Neither is `valueOf`, and they are never the same input (the `MinOut…` and `Deadline…` errors).
- The page fills both: the minimum from your preview and the reader’s slippage, the deadline from the chain’s clock. Your code enforces them: revert below the minimum and past the deadline. The registry only proves they are declared usably.
- `flow` says which way value moves for the presser: `WalletPays`, `WalletReceives` or `None`. The profile page gathers `WalletReceives` actions under **Available now**. `isExit` marks how a wallet leaves a launch. Neither authorizes anything.
- `isPayable` sends a `QuoteAmount` input as the call’s value on a native market. On an ERC-20 market the page asks for an approval of that amount to your module first, as it does for any `WalletPays` action with a `QuoteAmount` input. A payable action is `WalletPays` (`PayableActionNotWalletPays`) and has no target (`PayableActionWithTarget`).
- `amount` is zero or `selector(address viewer) -> uint256`, a figure drawn beside the button in `amountUnit`.
- `formValues` is zero or `selector(address viewer) -> bytes[]`, one ABI-encoded value per input (`FormValuesLengthMismatch`), so an edit form opens on what is live.
- `target` is zero or `selector() -> address`: the transaction goes to that contract, and `name` and `inputs` are its signature. Every other read stays on your module.
- Do not declare `transferOwnership` or `renounceOwnership`: the page draws them for the owner of an instance answering `owner()`.

### Seeds

A seed is quote the token factory collects from the creator and hands your instance at creation.

- Declare each in `seeds` as a `Quote` seed; a `Token` seed is refused (`TokenSeedUnsupported`). Name its amount exactly once (`SeedAmountAmbiguous`): `amountField` indexes a `uint256` `config` field (`SeedFieldOutOfRange`, `SeedFieldNotAnAmount`), or is `NO_SEED_FIELD` with a fixed `amount`. Only the first 32 `config` fields can name a seed amount — the registry tracks which are spoken for in a 32-bit mask, so an index of 32 or more is `SeedFieldOutOfRange` however long your config is.
- The registry pins the fixed amounts and the seed fields. The factory pays their sum from your pinned manifest and the creator’s config, so your instance never asks for its seed and never gets more than the form showed.
- On a native market the seed arrives as `msg.value`; the value above the creation fee must match exactly (`SeedMismatch`). On an ERC-20 market the factory moves it from the creator before `onInstalled`.
- `onInstalled` runs on every instance with an installer interface, seeded or not: token modules in table order, the launch module last, with no gas cap. A revert undoes the creation. Accept it only from `IPooToken(token).factory()`, once.

### Events and lists

There is no required event. POO.MEME’s indexer listens at each instance for exactly the events your manifest declares, and a list authorizes nothing.

An `EventDecl` gives `signature` in readable form, parameter names and `indexed` included, and `topic0`, its selector; they must match (`EventSignatureMismatch`).

- `walletArg`, `amountArg`, `keyArg`, `flagArg` and `textArg` are parameter indexes or `NO_EVENT_ARG` (`EventArgOutOfRange`). `textArg` is a non-indexed `string` (`EventTextArgInvalid`).
- `amountUnit` is `Raw` when there is no amount (`EventShapeMismatch`). The same `topic0` under the same `role` twice is `EventRepeated`.

| `role` | Used For | Must Carry (`EventShapeMismatch`) |
| --- | --- | --- |
| `WalletInflow` | A launch’s contributions on the profile page | A wallet and an amount in `QuoteAmount` |
| `ListAdd`, `ListRemove` | A `Membership` list | A wallet |
| `Custom` | Any list you build on it | What the list needs |

A `ListDecl` is a list the indexer builds from a declared event, paged and searchable by wallet (`ListEventOutOfRange`, `ListRoleMismatch`):

| `shape` | Built From | Drawn As |
| --- | --- | --- |
| `WalletTotals` | One event with a wallet and an amount | A total per wallet, largest first, with the sum |
| `Membership` | A `ListAdd` event, and optionally a `ListRemove` one | The wallets added and not since removed, newest first |
| `Log` | One event with a wallet or a key | Every entry, newest first |
| `Snapshot` | One event with a wallet, re-emitted whole on every change | The rows of your latest write |

`role: ShareTable` marks payees each taking a share of a whole; the token page draws it under Allocations, as parts of that whole.

### Errors

`errors` pairs each of your custom error selectors with a sentence of 1–200 bytes (`ErrorSelectorInvalid`, `ErrorRepeated`, `ErrorTextInvalid`). When a call to your instance reverts with one that no POO.MEME contract declares, the page shows your sentence. Explain every error a reader can meet.

## Gas caps

- A cap is the exact gas that hook gets, on every call, for the token’s life.
- Each hook has its own ceiling, and publishing refuses a cap above it: Gate 30,000, Track 550,000, Receive 265,000, Operate 1,000,000 — one ceiling over both sides of a run. `gasCeiling(hook)` on the registry answers each one.
- A tax payee contract’s own `receiveGas()` is held to the Receive ceiling: at most 265,000.
- Declaring Gate costs the token a flat 15,000 gas beyond your cap — once, however many gates a token carries — on its buy, nested and exempt-pair floors. That is what it reserves to tell a real buy from a liquidity removal or a skim, which never reach a gate, and it is charged whether or not yours is called.
- Publishing only proves the hook fits on the probe. Measure your worst case on a real token.
- Your caps become part of the token’s gas floors, and a floor is headroom rather than a bill: the token compares the floor for that leg against the gas the transaction arrived with, refuses the leg if it is short, and never spends the floor on the check. A trader pays for what your hooks actually use. But every trade that can reach you has to carry your whole cap, so a cap you never spend still raises the limit every one of those transactions must be sent with.
- The floors exist because a failed call is skipped. Without them, a leg entered on thin gas would quietly drop your hook and succeed anyway; the floor turns that into a refusal the trader can see, and it is why a wallet cannot size one of these transactions from usage.
- Too large: tokens with your module can fail to be created. Too small a `gateGas`: every buy of those tokens reverts.

## How you get paid

- **A tax row**: Declare a `taxAsset` of `Quote` or `Token`. The creator sets your share, for the token’s life. A `Quote` row arrives after each tax sale; a `Token` row arrives inside each taxed trade. `onReceive(asset, buyAmount, sellAmount)` tells you. A quote payment that fails is kept and added to your next one, or your module can call `claimTax()` on the token to collect it sooner.
- **A creation seed**: Declare it in `seeds` and receive it in `onInstalled(creator)`.

There is no cut of a launch’s raise. Whatever a launch module pays out of its raise is its own code, stated in its description.

## Your security obligations

Nothing checks these for you. Answer them before you publish — your description and your code are the whole account a reader gets.

- Does `initialize` check every setting on chain?
- Can an instance ever run other code than the probe’s?
- Does the description name every owner power and how it ends?
- Can tokens sent to your instance be forwarded?
- Is value safe when a Track, Receive or Operate call is skipped?

Any owner power to withdraw, seize, pause, censor, rescue, expire claims or refunds, or make arbitrary calls must be in your description, in plain words.
