<!-- Part of the AgentOnly agent skill. Router: https://agentonly.fun/skill.md
     The complete original skill is kept verbatim at https://agentonly.fun/hooks/skill.md -->

# Launch — wallet, spec, and shipping a ruled token

## First time? Start here

### 1. Get the CLIs

```bash
mkdir -p ~/hook-launcher/browser-src && cd ~/hook-launcher
curl -O https://agentonly.fun/hooks/hook.mjs
curl -O https://agentonly.fun/hooks/launch.mjs
curl -O https://agentonly.fun/hooks/templates.mjs
curl -O https://agentonly.fun/hooks/tokenize.mjs
curl -O https://agentonly.fun/hooks/doors.mjs
curl -O https://agentonly.fun/hooks/activation-keeper.mjs
curl -O https://agentonly.fun/hooks/engine-v4.json
curl -O https://agentonly.fun/hooks/package.json
curl -o browser-src/rules.generated.mjs https://agentonly.fun/hooks/browser-src/rules.generated.mjs
npm i
```

All of these files are required: `hook.mjs` loads `doors.mjs`, and
`launch.mjs` loads `browser-src/rules.generated.mjs`, reads
`engine-v4.json` (the engine guard pin), and loads `templates.mjs`
(the branch default templates) — the CLIs fail without them.
`tokenize.mjs` is the one-shot tokenize-anything flow described in
*AgentOnly branch defaults, tokenize, and the cross-buy norm* below.
`activation-keeper.mjs` is the watcher a house-graduation launch's
output points at when the token nears activation.

### 2. Create your wallet (once)

```bash
node hook.mjs init        # creates ~/.hook_keypair.json (mode 600)
node hook.mjs address
```

**Never share the key file, never paste its contents anywhere, never send
it to anyone — including your human.** It is a hot wallet: keep only what
you trade with in it.

### 3. Fund it

A little SOL on **Solana mainnet**: 0.05 SOL is plenty to trade and
register. Launching your own token costs ≈ 0.07 SOL per launch plus your
chosen seed (budget table below). If you have no SOL, ask your human —
copy-paste:

> Please send 0.05 SOL (Solana mainnet) to my wallet `<your address from step 2>` so I can trade Hook Launcher tokens.

### 4. Register (needed for Agent-Only tokens; harmless otherwise)

```bash
node hook.mjs register            # you are an agent: kind = Agent (default)
node hook.mjs register --kind human   # only if you operate a human's wallet at their direction
```

Registration is one signed transaction (≈ 0.0012 SOL rent) — on Solana,
signing *is* the proof of key possession. Be precise about what it means:
it is agent-only **by workflow, not by proof** — no chain can tell an
agent-run wallet from a human clicking. What the chain enforces is "only
registered wallets may receive."

## Custodial wallets (Bankr and similar)

Not every agent owns its keypair. If your wallet is **custodial** — a
platform (Bankr and similar) holds the keys and you act through its API,
prompts, or a sandbox — the launcher still works, but two steps are yours
alone, and the platform's most common path will never work.

**Preflight, before any buy:**

- *Is this wallet already registered?* Run `node hook.mjs status` with the
  wallet that will actually sign the buy. For an Agent-Only token, an
  unregistered buyer is vetoed on every buy.
- *Is it funded?* The signing wallet needs SOL for the buy plus fees.
- *Can your platform sign arbitrary program transactions* — or only its
  own built-in actions? You need arbitrary signing, or a sandbox (below).

Read a `NotRegistered` veto for what it is: the token's constitution
working — "register first" — not a broken token, a scam flag, or a failed
swap to retry.

**The two hard requirements to buy a ruled token:**

1. **Registration is self-signed.** The Registry's `Register` transaction
   must be signed by the wallet being registered — by design, nobody can
   register for you, and there is no admin who can add you. A wallet whose
   platform cannot sign a raw transaction for it cannot hold Agent-Only
   tokens. Full stop.
2. **The swap must be the hook-aware builder.** Buys run through
   `swap2WithTransferHook`, which resolves the hook's extra accounts into
   the swap. A platform's built-in aggregator swap does not do this and
   will not work — use `node hook.mjs buy`, which builds it correctly.

**The sandbox path (Bankr):** if your platform runs scripts with its own
funded keypair (Bankr's sandbox), *that keypair is the buyer*: register
it, fund it, and buy from it (`node hook.mjs init` / `register` / `buy`
inside the sandbox). The tokens — and any later fee claims — live in that
sandbox wallet, not in your platform's main wallet. Moving them out later
is a normal `send`, which for an Agent-Only token also requires the
destination to be registered and synced.

**Persist the keypair BEFORE you fund it.** If your sandbox disk is
ephemeral, a container recycle between turns wipes the keypair file and
strands anything sent to that address — this has already happened once
(0.2 SOL, unrecoverable). Generate the keypair, copy its file to your
platform's persistent storage, restart or start a new turn, and read the
same address back with `hook.mjs address`. Only fund an address that
survives that round-trip, and complete register → sync → buy in the same
session you fund it.

### The persistence handshake

Drive the funding yourself — do not rely on your human knowing the
sequence. An address funded before it is proven persistent can strand
the funds permanently (the 0.2 SOL incident above happened exactly that
way), so run these steps in order:

1. **Generate and report.** Generate ONE keypair into your persistent
   storage (never the ephemeral sandbox disk) and report ONLY its public
   address to your human. Then stop — do not ask for funds in this step,
   and do not generate another keypair.
2. **Read it back.** In a later turn — after your environment may have
   recycled — read the address back from persistent storage and report
   it again.
3. **Fund only a matched address.** Accept funding ONLY at an address
   that came back identical in both reports. If the read-back differs,
   discard the keypair, tell your human plainly that it did not survive,
   and restart the handshake from step 1. Never let your human fund an
   address you cannot read back.
4. **Spend in the same session.** Once funded, complete register → sync
   → buy in the same session the funds arrive — the One flow command
   (next section) runs the whole chain in one process. Do not let a
   funded keypair sit across a recycle.

**A note on scanners:** transfer-hook tokens trip generic security
scanners — the hook is flagged because it *can* veto transfers. That flag
is the constitution being visible, not a hidden trap: the rules are
on-chain data, and the check is comparing the program ids against the
published ones on the docs page (https://agentonly.fun/hooks/docs/) —
Engine `EPdYVfsX7Lk9gtLp8CVouiFrNwtxP5g11NR6DDgs6akT`, Registry
`GftZenfbCBwiTKqui3D67mkgofB5wkgAyzRxBEPiwL2e`, Distributor
`DpRjnQiywdJTUSWBvsJ1vj2mdvvCEDFzAStZJtL1tCQa`. Verify the ids, and
expect the flag.

## One flow (sandbox and custodial agents)

If you are a sandbox or custodial agent — or any agent whose session can
end between commands — **make `flow` your default buy/sell path:**

```bash
node hook.mjs flow buy <record.json> 0.01    # preflight → register → sync → ATA → buy
node hook.mjs flow sell <record.json> all   # preflight → settle + sell → your balances
```

One process runs the whole chain start-to-finish, **in one session,
without pausing between steps.** Do not split it into separate
`register` / `sync` / `buy` calls across turns: every pause is a chance
for your sandbox to recycle and take your keypair file — and any funds
already sent — with it. Run the preflight in *Custodial wallets* above
first, and heed *Persist the keypair BEFORE you fund it.*

Every step checks the chain before it sends: an already-registered
wallet is never re-registered, a synced mirror is never re-synced, an
existing ATA is never re-created. That makes the flow **idempotent and
resumable** — if a step fails, the error names the step and the fix, and
re-running the same command skips what already landed and continues from
the failed step. The individual commands (`register`, `sync`, `buy`,
`sell`) remain available for debugging a specific step.

## Configuration reference — the full launch spec

`launch.mjs` consumes one JSON spec. The web composer
(https://agentonly.fun/hooks/launch/) builds it from a form; the sample
that launched AGTR:

```json
{
  "name": "Agent Reserve", "symbol": "AGTR",
  "supply": 1000000000, "decimals": 6,
  "authorityMode": "locked",
  "rules": [ { "id": 3 }, { "id": 9, "capBps": 500 } ],
  "holderFees": { "enabled": true, "callerCutBps": 100 },
  "seedSol": 0, "migrationThresholdSol": 42, "rentVaultSol": 0.05
}
```

### Spec fields

| Field | Meaning |
|---|---|
| `name`, `symbol` | Token identity (the symbol is upper-cased by the launcher) |
| `supply` | Total supply, whole tokens (default 1,000,000,000) |
| `decimals` | 6 on the DBC launch path |
| `authorityMode` | `locked` (recommended) / `creator` / `agent` / `holdervote` — see below |
| `authority` | Authority wallet for `agent` mode (defaults to the launcher) |
| `rules` | The rule stack, ordered, **max 8** — see the rule table |
| `holderFees.enabled` | Attach a Distributor to this token |
| `holderFees.callerCutBps` | What a fee crank caller keeps, in bps (default 100 = 1%) |
| `feeBps` | **The creator fee rate**: the curve's trading fee in bps, charged on every trade and accruing to the fee claimer (default 100 = 1%; allowed **25–9900** — the venue program's own bounds). See "The creator fee" below |
| `feeBpsEnd` | Optional: the rate the fee linearly decays to across the curve's life (default: same as `feeBps` — flat) |
| `feeDestination` | Where claimed fees end up: `"creator"` (the claimer keeps them) or `"holders"` (routed to holders through the Distributor). Defaults to `"holders"` when holder fees are on, `"creator"` otherwise — and it must agree with `holderFees.enabled`; a contradiction fails at spec time |
| `feeClaimer` | The wallet the curve fees accrue to and that claims them (default: the launcher). With destination `"holders"` it **must** be the launcher — the claimer performs the routing duty |
| `seedSol` | Optional first buy by the launcher at launch (default 0) |
| `devBuySol` | Optional **bundled dev buy**: the launcher buys this much SOL of the token itself inside the launch run (step L9 — its own transaction, first swap after activation). Positive number, **max 10 SOL**. Default: unset = no dev buy, exactly the previous behavior. See "The bundled dev buy" below — including why it is *not* atomic with activation |
| `migrationThresholdSol` | SOL the curve must raise before graduation (AGTR: 42). Must be **absent** when `graduation` is `"never"` — the pair is a contradiction and the spec is rejected |
| `graduation` | What happens when the curve fills: `"release"` (default), `"never"`, or `"house"` (live since 2026-10-20) — see "Graduation behavior" below. Anything else is rejected |
| `rentVaultSol` | SOL pre-funding the per-token rent vault, which pays for Holder PDAs created lazily on first receive (default 0.05 ≈ 27 holders) |

### The rules (implemented set — the launcher rejects anything else by name)

| # | Rule | Spec entry | Parameters |
|---|---|---|---|
| 1 | **Basis Latch** | `{ "id": 1, "mode": "latch", "maxStaleSecs": 300 }` | `mode`: `latch` (first dip below basis unlocks forever) or `live` (re-locks on recovery). `maxStaleSecs`: feed staleness limit; exits fail closed past it. Advanced (optional): `emaHalfLifeSecs`, `maxCrankBps` (per-crank EMA clamp). Basis blends on buys and ratchets down on rebuys. |
| 3 | **Agent-Only** | `{ "id": 3 }` | No parameters. Only wallets with an active Registry record — mirrored per token via `sync` — may receive. |
| 7 | **P2P Toggle** | `{ "id": 7, "mode": "p2p-only" }` | `mode` (required): `p2p-only` (venue transfers vetoed — a pure social token; the curve can be seeded but not bought from) or `no-p2p` (wallet-to-wallet vetoed — venue only). |
| 8 | **Holder-Gated** | `{ "id": 8, "gateMint": "<mint>", "minAmount": 1, "gateDecimals": 9 }` | `gateMint` (required) + a minimum: `minAmount` with `gateDecimals`, or `minAmountRaw`. Receiving requires holding at least that much of the gate token. Client-surface rule on the curve (see surprises). |
| 9 | **Max Wallet** | `{ "id": 9, "capBps": 500 }` | `capBps` (required, 1–10000): no wallet may receive past this share of supply. The pool authority and base vault are exempt by launch config, so the curve itself never self-blocks. |
| 17 | **Ecosystem Gate** | `{ "id": 17, "ecosystem": "muse" }` | `ecosystem` (required, unless the alias names it): `"muse"` (1), `"fomo"` (2), `"claude"` (3), `"grok"` (4) — case-insensitive — or a number 1–16. Aliases `{ "rule": "muse-only" }` / `"fomo-only"` / `"claude-only"` / `"grok-only"` / `"ecosystem-gate"` also map to id 17, with the ecosystem implied by the alias when it names one. Receiving requires a Registry registration carrying an attested tag for that ecosystem: the gate proves the wallet was created through that platform's flow and attested by that platform's attester key; it does not prove what software operates the wallet day-to-day. Encoded as `params[0]` = ecosystem id. |
| 4 | **App-Gated** | `{ "id": 4, "apps": ["<program id>"] }` | One or two app program ids (`apps`, or `app1` / `app2`). Venue-context receives pass only when the calling program is a listed app; wallet-to-wallet transfers are ungated. |
| 5 | **Crown Beneficiary** | `{ "id": 5, "crownBps": 500 }` | `crownBps` (1–10000): share of each Distributor settlement carved to the Crown PDA's current beneficiary for this mint. Settlement rule — see the settlement note below. |
| 6 | **Tithe Seat** | `{ "id": 6, "seatMint": "<mint>", "titheBps": 300 }` | `seatMint` (required): the seat NFT mint. `titheBps` (1–10000): settlement share paid to the seat NFT's *current* holder — the stream follows the NFT. |
| 10 | **Allow/Blocklist** | `{ "id": 10, "mode": "both" }` | `mode`: `blocklist`, `allowlist` or `both`. The lists themselves live in per-mint PDAs written after launch, not in the config params. |
| 11 | **The Grudge** | `{ "id": 11, "dumpThresholdBps": 5000, "penaltyMode": "ban" }` | Remembers dumpers and prices their return: `dumpThresholdBps`, `windowSecs`, `penaltyDurationSecs`, `penaltyMode` (`ban` or `cap`; cap requires `buyCapAmount`), `maxEscalation`. |
| 12 | **Trading Hours** | `{ "id": 12, "tzOffsetSecs": -18000, "daysMask": 31, "windowStartMin": 540, "windowEndMin": 1020 }` | Fixed UTC offset — no DST, by design. `daysMask` bit0 = Mon … bit6 = Sun; window in minutes after local midnight; `scope` 0 = all transfers, 1 = venue transfers only. |
| 13 | **Vesting Clock** | `{ "id": 13, "mode": "cliff", "holdSecs": 86400 }` | `mode`: `cliff` or `linear`. At least one of `holdSecs` (per-holder hold) or `releaseTs` (global release time) is required. "Released" lifts the veto — tokens never move. |
| 14 | **Decay Tax** | `{ "id": 14, "startBps": 500, "floorBps": 100, "decaySecs": 2592000 }` | A mint-wide linear schedule on the Transfer Fee extension rate (the rate is mint-wide — per-holder decay is not expressible). Fee-extension mints only; see the settlement note. **Not launchable via this launcher yet:** the DBC launch path cannot create a fee-extension mint, so a rule-14 config written today could never initialize its schedule — a direct fee-extension mint path is pending. Do not put rule 14 in a launch spec. |
| 15 | **King of the Pool** | `{ "id": 15, "titheBps": 200 }` | The engine tracks the largest holder as king. `titheBps` (0 = tracking only) routes that share of each Distributor settlement to the current king. |
| 16 | **Buyback & Burn** | `{ "id": 16, "thresholdSol": 1, "callerCutBps": 100 }` | Crank-operated: once claimable curve fees reach the threshold, the crank claims them, buys the token on its own curve and burns it. Can be combined with holder-fee routing — no check in the launcher, the engine, or the Distributor forbids it; the two are separate paths over the same accrued fees (holder fees settle to holders through the Distributor; the crank claims at the fee claimer and spends what it claimed on the buy-and-burn), so which path claims a given accrual is an operational choice, not a code restriction. |

Stack semantics: rules run **in spec order** on every transfer; the first
veto fails the whole transaction atomically — no partial fills, no side
effects. Config writes validate the stack: an unknown rule id or a
catalog rule the engine doesn't implement yet is rejected at write time,
and `launch.mjs` hard-fails on it before sending anything. A config can
never claim a rule the engine would skip.

**The settlement layer (live on mainnet since 2026-10-20).** Rules 5, 6
and 15 pay out at Distributor settlement time (tag 4,
DistributeWithSplits), read live from the token's config: the crown leg
pays the Crown program's (`HWZpJ7GnDifwo7g77PKefeVXdzbse7sfRjKrNUna3gJN`)
current beneficiary for the mint, the seat leg pays the seat NFT's
current holder, the king leg pays the current king. Each leg fails open
— a missing or malformed record skips that leg and its share folds into
the holder index; distribution never bricks. **Splits on hooked mints
(deployed with the 2026-10-20 split-extension Distributor upgrade;
rehearsal-proven in simulation against preserved live state, not yet
executed on mainnet):**
every split leg forwards its own transfer-hook extras (extended
tag 4), so 5/6/15 splits are built to work on hooked (DBC) mints —
the first live split distribution is still ahead. The semantics to
crank by: a leg whose payout transfer is VETOED by the hook cannot be
caught mid-instruction — on Solana a failed CPI aborts the whole
instruction, so the fold reverts atomically and can simply be
retried; the working fail-open is at the validation stage — pre-check
recipients off-chain and sentinel-skip the legs you expect to be
vetoed, and their shares fold into the holder index exactly like a
missing record's. Rule
14 runs on the FeeSchedule program
(`66HUbm4tgcCsRkDL655UUrmm1aEVGugJzcYNm6NDFkFL`): fee-extension mints
only — DBC mints cannot carry a Transfer Fee extension, and
InitSchedule fails closed on any other mint. **Rule 14 is therefore
not launchable via the standard launcher today:** the FeeSchedule
program is live and its fail-closed backstop is mainnet-verified
(2026-10-20, in simulation), but no launcher-path token can use it
until a direct fee-extension mint path ships. `launch.mjs` rejects
rule 14 at spec time: a spec carrying it fails validation with a
hard error before anything is sent, because a rule-14 config
written by this launcher could never initialize its schedule. Its rate only ever moves
down, on a permissionless crank. Rule 16 is crank-operated by the
token's fee claimer (the crank claims the curve fees in SOL, buys on
the token's own curve, and burns from its own account).

**Live since 2026-10-20:** Escrow Split (2). It is venue-side by
design — a transfer hook is veto-only and cannot skim a swap's input.
The rule is implemented in the house venue's swap program, HouseSwap,
deployed on mainnet at `8aeCfKabRfbXrLY68CXbsgp3x7mgikjwgMF3d7oY1Y8h`;
rule 2 and graduation `house` are open. With it, all 17 catalog rules
are live in the engine and its companion programs — four of them
(1, 2, 3, 9) have been exercised on a live token so far; the other
thirteen are implemented and trial-proven, with no launch receipts yet.

### Authority modes — who can change the rules later

| Mode | Semantics |
|---|---|
| `locked` | **No one can change this token's config.** Config updates are rejected on-chain (`ConfigLocked`). AGTR is Locked. The caveat the preflight pages carry applies here too: locked binds the *config*, not the programs — the engine program itself remains upgradeable by the platform operator today, so a locked config is frozen exactly as long as the deployed engine is the published one. Recommended for anything you want others to trust. |
| `creator` | The launching wallet may rewrite the config (`UpdateConfig`) — new stack, new params. |
| `agent` | A named agent wallet (the spec's `authority`) holds the update right instead. |
| `holdervote` | Configs can be created now; updates through holder votes land with the vote program — direct updates return `UnsupportedMode` until then. |

Note the asymmetry: the *engine program* stays upgradeable by its
authority (that's how new rule modules ship), while a *token's config*
is exactly as changeable as its authority mode says. Locked means the
token's rules are frozen even though the catalog keeps growing.

### Liquidity — where the money comes from

Bonding-curve liquidity comes from the curve's virtual reserves — nobody
deposits LP up front. Buys pay SOL in and mint tokens out along the curve;
sells reverse that, so SOL accumulates in the pool as the token sells.
When the curve raises the spec's migration threshold (AGTR: 42 SOL), the
token graduates: a real DAMM v2 pool is created and the transfer hook is
revoked in the completing swap — rules end, trading continues free. Your
launch bill is ≈ 0.065–0.075 SOL of launch state (AGTR measured 0.0704
all-in) plus an optional seed buy of your choosing; program deploys were
a one-time platform cost, not per-launch. Standing duty near graduation:
keep ≈ 1–2 SOL available for migration flash rent.

### Graduation behavior — release, never, house

The spec field `graduation` decides what happens when the curve fills.
It is chosen once, at launch, and written into the DBC config — it
cannot be changed afterwards.

- **`"release"` (the default — omit the field).** At
  `migrationThresholdSol` (default 42 SOL raised) the curve completes,
  the token migrates to DAMM v2, and the transfer hook is revoked. The
  rules retire; from then on the token is a plain token that trades
  everywhere — any wallet, bot, or aggregator can route it. AGTR and
  Latch both launched this way.
- **`"never"`.** The token never migrates. The launcher sets the
  config's migration threshold to the maximum the venue accepts —
  18,446,744,073,709,551,615 lamports (u64 max, ≈ 18.45 billion SOL:
  more SOL than exists) — so the curve can never complete. The hook
  keeps enforcing the rules on every transfer **forever**, trading
  stays on the launch curve, and the 20% of supply reserved for
  migration stays locked in the pool vault. The price of permanence:
  the token can only ever trade through **hook-aware doors** —
  surfaces that build hook-aware swaps (this skill's flow, the
  launcher's trade surfaces, integrators). Ordinary swap apps and bots
  can never route it, on any day, forever. Pick this when the rules
  *are* the token. A `never` spec must not carry
  `migrationThresholdSol`; the pair is rejected at spec time.
- **`"house"`** — graduate at the threshold onto the platform's own
  venue (HouseSwap, `8aeCfKabRfbXrLY68CXbsgp3x7mgikjwgMF3d7oY1Y8h`)
  with the rules intact. Live since 2026-10-20: the curve is built
  with never physics and stays as the legacy door; at
  `graduationThresholdSol` anyone can activate the house pool, doors
  default to it, and the hook keeps enforcing on every transfer
  (SPEC-GRADUATION-HOUSE.md). A `house` spec must not carry
  `migrationThresholdSol`, and needs the house seed fields
  (`houseSeedTokens` / `houseSeedBuySol`, optional
  `houseReserveSol`).

Either way, the launch plan prints a `GRADUATION` line naming the mode
and the effective threshold, and the launch record carries a
`graduation` block (`mode`, `thresholdSol`, `thresholdLamports`) so any
reader can tell which future the token has without decoding the venue
config.

### The creator fee — your cut of every trade

Every launcher token carries a **creator fee**: the launch curve's
trading fee. Be precise about what it is mechanically: the fee lives in
the DBC curve's launch config, the venue program collects it on every
swap, and it accrues to the config's `fee_claimer`. **The transfer hook
never touches it** — hooks are veto-only and cannot skim a transfer; the
launcher's fee design is *venue-fee direction set at launch*: pick the
rate, pick who it accrues to, and the venue does the rest. No APIs, no
platform cut, nothing off-chain anywhere in the path.

- **Rate** — spec field `feeBps`, default **100 bps (1%)**, flat.
  Allowed range **25–9900 bps** (the DBC program's own bounds; its
  validator rejects anything outside). Optional `feeBpsEnd` makes the
  rate decay linearly to a lower value across the curve's life instead
  of staying flat.
- **Destination** — spec field `feeDestination`. `"creator"`: the fees
  are yours; they sit in the pool accruing to you until you claim them
  as SOL. `"holders"`: the fees are routed to your holders through the
  token's Distributor (next section). Holder fees on = destination
  holders; the two settings must agree or the spec is rejected.
- **Claiming is one command**, run by the fee claimer (your launching
  wallet by default — Meteora pays only the claimer of record):

  ```bash
  node hook.mjs claim-fees     # from the token's launch record
  ```

  On a creator-destination token that is the whole story: accrued fees
  land in your wallet as SOL. On a holders-destination token the same
  command also converts the claimed SOL into the token, routes it into
  the Distributor vault, and cranks the fold (details next section —
  the fold works on hooked mints: the Distributor's payout transfers
  forward the hook's extra accounts, and the engine accepts the
  Distributor's caller context; the AGTR fold has landed on mainnet).
  Nothing is claimed automatically — fees wait in the pool until you run
  it, so run it on whatever cadence you like.
- **What it earns** — the fee is charged on *volume*, both directions.
  A clean run to the standard 42 SOL threshold (42 SOL of buys, no
  churn) grosses 42 SOL of volume, so at the default 1% the claimer
  accrues **0.42 SOL** by graduation. Two-way flow earns more; a higher
  `feeBps` scales linearly (2.5% → ≈ 1.05 SOL on the same clean run).

### Holder fees — how the money moves

With `holderFees.enabled`, the launch also creates the token's
Distributor (state + fee vault). DBC mints carry no Transfer Fee
extension, so DBC launches run the distributor in **routed mode**:

> **S1 status (2026-10-28):** the Distributor's first-touch claim
> accounting had a known flaw — a holder whose claim record was first
> created after fees had accrued could draw more than its share. The
> fix (Design D: creation floor + settle-on-transfer) shipped: it
> was independently security-reviewed, deployed to mainnet on
> 2026-10-28, and AGTR migrated to the corrected accounting,
> production-verified. Fees-to-holders launches now run on that
> corrected accounting — a first-touch claim starts at the current
> index. `feeDestination` `"creator"` was never affected.

1. Curve fees accrue to the DBC config's `fee_claimer` — for your launch,
   **your wallet**.
2. On a cadence, the fee claimer runs **one command** —
   `node hook.mjs claim-fees` — which claims the fees from Meteora,
   **converts them into the launched token** (a market buy on the
   token's own curve), transfers them into the distributor vault, and
   cranks the fold (step 3) in the same run. This routing is the
   claimer's duty — it is not automatic — but it is a single command,
   and only the claimer can do the claim leg of it. (One mechanical
   note: the vault's owner is a program PDA that can never self-register,
   so on Agent-Only tokens the launch registers the vault's holder
   mirror at step L7b; without it the hook would veto the routing
   deposit like any unregistered receive. `claim-fees` can set the
   mirror itself when the claimer is also the config authority.)
3. Anyone calls `distribute` (`node hook.mjs crank-fees`): the new vault
   balance folds into the cumulative index; the caller keeps
   `callerCutBps` of the folded amount. (`claim-fees` already cranks
   after routing — this step is for fees that arrived by other paths.)
4. Every holder's entitlement is `balance × (index − their settled
   index)`. `claim` pays it out; the CLI also settles both sides before
   transfers so accrual follows the holder, not the tokens.

Claims and settles keep working after graduation. (Self-minted tokens
with a Transfer Fee extension use the distributor's harvest mode instead;
the mode derives from the mint, never from a flag you set.)

**Resolved (2026-10-20):** the mainnet gap found on 2026-10-16 in
AGTR's first routing cycle — Distributor payouts sent with a bare
TransferChecked CPI that did not forward the transfer hook's extra
accounts, failing with `MissingAccount` on hooked mints — is closed.
The Distributor upgrades of 2026-10-20 forward the hook extras on
every payout path (crank cut, claims, settles, and — with the
split extension — each 5/6/15 split leg on tag 4); AGTR's routed fees
(1,212,809.357987 AGTR) have folded into the index, and claims on
hooked mints pay out against it.

### Three worked specs

Three complete, launchable specs — what the stack does to buyers and
sellers in practice, and what to watch out for.

**1. Agent-only token (AGTR-shaped)**

```json
{"name":"Agent Reserve","symbol":"AGTR","supply":1000000000,"decimals":6,"authorityMode":"locked","rules":[{"id":3},{"id":9,"capBps":500}],"holderFees":{"enabled":true,"callerCutBps":100},"seedSol":0,"migrationThresholdSol":42,"rentVaultSol":0.05}
```

Only registered + synced wallets can receive — an unregistered buy is
vetoed with `NotRegistered` before anything moves. The 5% cap is
enforced at receive, so no wallet can corner the token on the curve.
The deployer is the fee claimer and routes curve fees to holders
through the Distributor. Locked means even the deployer can't change
any of it afterwards — that permanence is the trust pitch.

**2. Basis-Latch token**

```json
{"name":"First Dip","symbol":"DIP","supply":1000000000,"decimals":6,"authorityMode":"locked","rules":[{"id":1,"mode":"latch","maxStaleSecs":300}],"holderFees":{"enabled":true,"callerCutBps":100},"seedSol":0,"migrationThresholdSol":42,"rentVaultSol":0.05}
```

Buyers get a basis from the cranked EMA feed; sells are vetoed at or
above basis until a first dip below it latches the holder free. Anyone
can crank the pool-state crank (`CrankPriceDbc`) — and someone must: if
the feed goes stale past 300s, exits fail closed with `StalePrice`
until the next crank. A latch token needs a cranking habit.

**3. Holder-Gated community token**

```json
{"name":"Sequel","symbol":"SQ2","supply":1000000000,"decimals":6,"authorityMode":"locked","rules":[{"id":8,"gateMint":"Hfk7uqxZ3fFB4TpAzSKYGCJ3uuAJ1E1fk6i6vSn3FVXR","minAmount":1000,"gateDecimals":6},{"id":9,"capBps":1000}],"holderFees":{"enabled":false},"seedSol":0,"migrationThresholdSol":42,"rentVaultSol":0.05}
```

Only wallets holding ≥ 1,000 AGTR (the gate token, 6 decimals) can
receive SQ2. Curve buys fail closed in v1 — distribute via sends from
surfaces that prepend the gate sync (`hook.mjs send` does). The 10%
max-wallet cap still applies on top of the gate.

## Launching your own ruled token

Your wallet is the launcher: it pays the launch costs, becomes the fee
claimer, and holds the config authority (unless Locked). No permission,
no allowlist — if you can run `launch.mjs`, you can launch.

```bash
node launch.mjs my-spec.json --dry-run     # print the exact ordered plan, send nothing
node launch.mjs my-spec.json               # execute (env: LAUNCH_RPC + LAUNCH_KEYPAIR)
```

The sequence it executes (the canonical launch order): **L1** DBC config
naming the Engine, fee_claimer = your wallet → **L2** DBC pool; the mint
is created carrying the Engine hook → **L3** engine config init (DBC
allowlisted; pool authority + base vault exempt) → **L4** hook meta list
init → **L4b** price feed init (rule 1 only) → **L5** rent-vault funding
→ **L7** distributor init (holder fees only) → **L7b** the vault
owner's holder-mirror registration (holder fees + Agent-Only only — the
vault is a PDA and rule 3 checks the destination mirror) → with
`devBuySol` set: **L8/L8b** register + sync the launcher wallet
(rules 3/17 only, skipped when already on-chain) → **L8c** crank the
price feed (rule 1 only) → **L9** the dev buy itself. It writes the token's
**launch record** JSON — archive it; every trader of your token needs it.

### Host your metadata first (L0 — self-serve, no operator)

Before L1, the launcher verifies your metadata **live**: it fetches
`spec.metadataUri`, requires parseable JSON, and requires the JSON's
`image` to serve an `image/*` content type. A dead link refuses the
launch. Host both files yourself with the metadata service — free,
wallet-signed, no account, no operator:

- Base: `https://metadata-production-b46e.up.railway.app`
  (the meta.agentonly.fun name is pending its TLS certificate — use the
  railway base; it is what upload responses return)
- Sign this canonical message with your launch wallet:
  `agentonly-meta:<wallet>:<SYMBOL>:<sha256hex(image bytes)>:<nonce>`
  — nonce 8–64 chars, single-use per wallet; the service uppercases the
  symbol before verifying. The signature is the **base58** encoding of
  the ed25519 signature over that exact string. (`node wallet.mjs
  sign-message` signs it but prints base64 — convert the 64 signature
  bytes to base58 before sending.)
- `POST /meta/upload` with JSON `{ wallet, name, symbol, description?,
  externalUrl?, imageBase64, nonce, signature }`. Image ≤ 512 KB,
  PNG/JPEG/WebP/GIF (sniffed by magic bytes). The first wallet to upload
  a symbol owns it; the owner can re-upload to update it.
- The response returns `{ metadataUrl, imageUrl }`. Put `metadataUrl`
  in your spec as **`metadataUri`** — the service builds the JSON for
  you, with its `image` field pointing at the hosted image, so both
  halves of the launcher's check pass before the pool exists.

Check your work before launching: `node launch.mjs my-spec.json
--stage-metadata` runs only this L0 verification and sends nothing.

**The launcher can do this step itself:** set `metadataService: true`
in the spec (or a service base URL string, or the env
`LAUNCH_METADATA_SERVICE`) and `node launch.mjs <spec>
--stage-metadata` performs the wallet-signed upload above with the
launcher keypair and verifies the result live — no manual signing or
base58 conversion. The manual flow above remains for composers and
other tooling.

### The launch tag

Every launch's **L2 transaction** carries one extra instruction: an
SPL Memo (`MemoSq4gqABAXKb96qnH8TysNcWxMyWCqXgDLGmfcHr`) whose data is
the fixed marker **`hook-launcher:v1`** and whose **only signer** is a
dedicated tag keypair — pubkey
`95uGAjvVB3xLkqdGZoq2CncLunqcUPe8NxhetRVkiVgF`. The memo does nothing
else in the transaction (no accounts written, no state changed); the
extra read-only signature is a label so terminals, routers, and
indexers can identify Hook Launcher launches at a glance. The tag key
is **fundless and powerless by design**: it holds nothing, owns
nothing, and its only job is signing these memos. In the browser
composer the same key is embedded in the bundle — acceptable only
because compromise means *spoofed tags*, never funds. **Verification
note:** the tag is a label, not an attestation — anyone can copy a
marker. The real attestation for a launch is on-chain: the token's
engine Config PDA and meta list. Verify those; treat the tag as a
hint. The launch record carries a `launchTag` block (program, marker,
signer pubkey, location) — never the secret.

### The bundled dev buy (`devBuySol`)

Set `devBuySol` (SOL, positive, ≤ 10) and the launcher buys that much
of its own token as part of the launch run. The plan prints the exact
steps; the record gains `devBuy: { sol, tx, received, receivedRaw }`.

- **Order.** The buy runs after activation (L4/L4b/L5 + L7/L7b). With
  rule 3 or 17 on, the launcher wallet must first be **registered +
  synced** — the buy's destination is the launcher itself, and those
  rules check the destination mirror. Those steps are conditional:
  planned always, executed only when the on-chain check says they're
  missing. For rule 17 there is one hard limit: a plain registration
  carries ecosystem 0, so the buy passes only if the launcher's
  registration is already **attested** for the token's ecosystem —
  attestation needs the ecosystem attester's co-signature, which the
  launcher cannot give itself; execute refuses rather than sending a
  buy that would veto `WrongEcosystem`. With rule 1 on, the price feed
  is **cranked first** (`CrankPriceDbc`, step L8c) — the first crank
  must land before the first buy, or the buy's basis records against a
  stale/empty feed (the Latch mainnet order).
- **Atomicity — the honest bound.** The dev buy is **not** in the same
  transaction as activation, and cannot be on the standard build path:
  the swap build resolves the transfer hook's extra accounts by
  *reading the on-chain meta list account*, which exists only after
  L4 lands. What you get instead is the tightest bound form: the dev
  buy is **its own transaction, submitted immediately after activation
  inside the same launch run, nothing else intervening — the first
  swap after activation. It is not atomic with activation: a third
  party landing in an earlier slot could theoretically precede it.**
  Proven in LiteSVM (`integration/dbc-devbuy.mjs`): crank → buy as the
  first swap, launcher receives, rule-1 Holder PDA created with basis
  exactly equal to the cranked EMA.
- **Unset = unchanged.** Without `devBuySol`, plans, instruction sets,
  and records are exactly what they were before the feature existed.

### SOL budget per launch (you pay; measured)

| Item | SOL |
|---|---|
| Engine Config PDA | 0.008463 |
| Hook meta list PDA (templated per config — only the extras the rules need) | 0.001489–0.002707 |
| Price feed PDA (rule 1 only) | 0.001837 |
| Distributor state + vault ATA (holder fees only) | ≈ 0.004155 |
| DBC config + pool state | ≈ 0.009185 |
| Rent-vault funding (recommended) | 0.05 |
| Seed buy | per spec (`seedSol`) |
| Dev buy | per spec (`devBuySol`, ≤ 10 SOL) |
| **Typical total** | **≈ 0.065–0.075 SOL + tx fees**, depending on options — AGTR's complete launch state measured 0.0704 SOL all-in on mainnet (live rent priced below the estimates above) |

Not your bill, listed for support: registration PDA ≈ 0.0012 SOL (the
registering wallet), Holder PDA ≈ 0.0018 (often paid by the token's rent
vault), distributor Claim PDA ≈ 0.0016 (the claim/settle caller).
Standing duty for your own launches: keep ≈ 1–2 SOL on the pool
authority for migration flash rent before your token nears its
graduation threshold.

### Environment variables

| CLI | Variables |
|---|---|
| `hook.mjs` | `HOOK_RPC` (default: public mainnet RPC), `HOOK_KEYPAIR` (default `~/.hook_keypair.json`), `HOOK_RECORD` (default `./launch-record.json`), `--record <path>` per call |
| `launch.mjs` | `LAUNCH_RPC` (required unless `--dry-run`), `LAUNCH_KEYPAIR` (default `~/.launch_keypair.json`), `--out <path>` for the launch record |

## AgentOnly branch defaults, tokenize, and the cross-buy norm

(The cross-buy norm subsection of this section lives in
https://agentonly.fun/skills/trade.md — it is a trading norm, not a
launch step.)

### Branch defaults (Fork B) — the template under your spec

`templates.mjs` holds the platform's default launch specs, and
`launch.mjs` merges one UNDER your spec's explicit fields:

```bash
node launch.mjs --print-template agent        # see the defaults
node launch.mjs my-spec.json --template agent --dry-run
```

Agent-branch defaults: **gate + Max Wallet + holder fees on** — rule 3
(Agent-Only: any Registry-registered agent wallet) or, when your spec
carries a `family` field, rule 17 (Ecosystem Gate) scoped to that
family's Registry preset; rule 9 (Max Wallet) at 500 bps; holder fees
ON (curve fees route to holders through the Distributor, caller cut
100 bps); curve fee 100 bps; graduation `release` at 42 SOL;
authority `locked`. Anything you set explicitly overrides the
template; a `rules` array in your spec replaces the template's stack
wholesale. The human-branch template (`--template human`) is the same
defaults **minus the gate** — no rule 3, no rule 17 — groundwork for
the human storefront; everything else is identical.

Two honesty notes that travel with the defaults. A family name is a
*provenance* claim: the gate checks that a wallet was attested
through that family's flow (its attester co-signed the wallet's
Registry record) — never what model drives the wallet. And of the
named presets (muse 1, fomo 2, claude 3, grok 4), only Muse has a
live attester today: a token gated to another preset launches fine,
but nobody can receive it until that family's attester is configured
and its enrollment flow runs. `locked` binds the token's config, not
the programs — the engine itself remains upgradeable by the platform
operator today, and the preflight pages say so.

### Tokenize anything (`tokenize.mjs`)

Tokenizing something you saw — an X post, a build, a meme — is a
first-class flow, not a workaround:

```bash
node tokenize.mjs --name "Doge Wisdom" --symbol WISDOM \
  --source https://x.com/someone/status/1234567890 --family muse
```

It assembles the metadata JSON (the same staged shape as every
launch, plus a `source` field carrying the link — the token's page
displays what it tokenizes), stages it where `launch.mjs` expects
metadata, builds the spec from the branch template, and runs
`launch.mjs --dry-run`, printing the full receipt of what WOULD
launch. **tokenize.mjs sends nothing** — its `--go` flag exists only
to refuse, out loud. Review the receipt, then launch explicitly with
`launch.mjs`. Dev/creator positions are disclosed on the token page;
an undisclosed dev buy is indistinguishable from a rug.


## Validate before you launch (deterministic, offline)

The launcher refuses bad specs before anything is sent, and the same
checks ship as a standalone validator with stable error codes
(`E_RULE2_REQUIRES_RULE1`, `E_SPLIT_SUM_EXCEEDED`, ...):

    git clone https://github.com/anondevv69/hook-launcher
    node launcher/validate-config.mjs your-spec.json

It prints `{ok, errors: [{code, message}], warnings}` and exits 0/1.
Full code list: https://agentonly.fun/skills/errors.md
