@latchprotocol/create-app
v0.1.1
Published
The Latch app template: scaffold a DEX, a Launchpad or both on LatchProtocol's shared core. npm create @latchprotocol/app. MIT, one config file, no core deployment.
Maintainers
Readme
@latchprotocol/create-app
The Latch app template: scaffold a DEX, a Launchpad or both on LatchProtocol's
shared core. You get a Vite + React front end, one config file
(latch.config.ts: chain, fee wallet, branding, features) and a verify script.
You deploy no core contracts: your pools live in the Vault and pool managers Latch
already runs on the chain you pick. Set the fee wallet, restyle if you want, ship.
MIT, and so is everything it emits.
npm create @latchprotocol/app my-app -- \
--chain base \
--fee-wallet 0xYourTreasury \
--fee-bps 25 \
--features both # dex | launchpad | both
cd my-app
npm install
npm run latch:verify # read every configured address back off chain
npm run devnpx @latchprotocol/create-app my-app … does the same thing. The -- after the
directory is npm's: it passes the flags through npm create to the scaffolder.
Run it with no flags and it prompts. Add --yes and it never will: every option
has a flag, and anything with no safe default is an error rather than a guess.
The same scaffolder runs as latch init in @latchprotocol/cli,
and it is also importable: import { runCreate } from "@latchprotocol/create-app",
then await runCreate(["my-app", "--chain", "base", "--yes"]) returns the exit code.
What it produces
A Vite + React app, one config file, and two scripts. No contracts.
my-app/
latch.config.ts chain · fee wallet · branding · feature flags · tokens
src/
config/ the config's shape, Latch's address book, validation
lib/ chain reads: pools, tokens, launches, fees
components/ the shell and the four data states
routes/ Swap · Pools · Launch · Launch wizard · Fees
theme.ts config colours -> CSS custom properties
scripts/
latch-verify.mjs turn every configured address into a fact or a failure
latch-deploy.mjs resolve and write addresses. Sends no transaction, ever.Everything a tenant changes to go to market is in latch.config.ts. Restyling
is a colour edit there — no stylesheet in the template hardcodes a colour.
Shared core, not a full fork
The tenant does not deploy Vault, CLPoolManager or BinPoolManager. Their
pools live in the core Latch already has deployed and verified on the chosen
chain.
That is cheaper and faster for them — no nineteen-contract deployment, no
verification, no audit question — and it is the only model in which Latch's
protocol fee is enforceable, because protocolFeeController on a shared pool
manager belongs to Latch governance and is capped at MAX_PROTOCOL_FEE = 4000
pips (0.4%) by core.
A full fork is permitted by the GPL, owes nothing, and gets no shared registry.
It is supported here only in the sense that chain.contracts lets a tenant point
the front end at their own addresses. The generated README says so plainly rather
than pretending the choice does not exist.
Between the two: chain.router. A tenant on the shared core can point every
swap — the DEX surfaces and a launch's buy path, which share one widget adapter
— at their OWN router while the Vault, the pool managers, the kits, the lockers
and the registry stay Latch's. Absent or null (the default, and what the
scaffolder writes) means Latch's router from the SDK address book, with no
configuration at all. It is a first-class option rather than the contracts
escape hatch on purpose: wanting your own router is not the same claim as
wanting your own core, and a tenant should not have to use the full-fork
mechanism to say the smaller thing. Setting both to different addresses is a
blocking config error — one of them would otherwise lose silently. The router
must still be Latch's UniversalRouter ABI wired to the same Vault and Permit2,
because the widgets encode that command set; latch:verify checks the code, the
router's vault(), paused() and owner(), and says whose router is in use.
Latch's routed-fill fee travels with Latch's router, so a tenant on their own
neither pays nor collects it — the generated README states that rather than
leaving it to be discovered.
Nothing to deploy, and that is deliberate
Both launch kits take every launch parameter as a call argument, so one
instance serves every tenant: launches differ by their arguments, not by
their bytecode. LaunchpadKitV2 (locked launches) additionally keeps a
per-tenant config the tenant writes itself — the integrator wallet, its share
of locked-LP fees and its launch fee — and refuses a launch that restates it
differently; the kit's owner cannot touch a tenant's config. LaunchGuardHook's
only authority is a per-pool launchOwner, and RevShareHook is configured
entirely per pool. So latch:deploy resolves and verifies rather than
deploying, latch:tenant prints the one transaction that stores a tenant's
terms (LatchPad.configure from the pad's owner when chain.launchTenant is a
LatchPad — the recommended, transferable form with an on-chain brand — or
setTenantConfig from the wallet otherwise), and --own-kit prints a forge
command for a tenant who wants their own instance anyway.
Where the SDK address book records no shared kit for a chain,
chain.launchpadKitV2 and chain.launchpadKit start null and the launch
screens say so plainly instead of rendering an example. The generated README
documents the options.
Latch utilities, on by default
The emitted app also ships Team splits (/splits, and a "pay the creator share to a
split" option in the launch wizard), locks (/locks, lock badges on pools and launched
tokens) and airdrops (/airdrops: multisend and claimable Merkle drops from a CSV, and a
claim page that verifies the claims file against the drop's root). Each is a flag in
features; addresses come from the SDK address book per chain, and a contract recorded
null renders "coming soon on ". The contracts charge their own flat fees, read
live and shown before signing; the template adds no fee of its own to any of them.
The template carries its own tests (npm test), run in a generated project.
Licensing — the rule this package exists under
This package and everything it emits are MIT, and a tenant may close-source
their fork. That holds for exactly one reason: the emitted app reaches the
protocol through the MIT @latchprotocol/sdk and through ABIs, never through GPL
Solidity. An ABI call is not linking and does not create a derivative work.
| Layer | Licence |
|---|---|
| This scaffolder and its template | MIT |
| @latchprotocol/sdk, /widgets, /connect | MIT, independently authored |
| Latch core, periphery, router, Latch's hooks | GPL-2.0-or-later |
Never import GPL Solidity, or anything generated from it, into
template/. Not sources, not build artifacts, not a hex bytecode string.
test/scaffold.test.ts enforces this: it greps every emitted source file for an
import reaching into packages/core, packages/periphery, packages/router, a
GPL hook package, or any .sol path, and fails the build if it finds one. The
rule is a test, not a paragraph.
The same rule is why latch:deploy --own-kit prints a command instead of running
one. Shipping the kit's bytecode would put GPL code inside an MIT distribution.
The template is a real app
Every file under template/ typechecks and builds exactly as it sits on disk,
against defaults pointing at a chain Latch is deployed on. Substitution replaces
valid values with other valid values, in two shapes:
name: '__LATCH_APP_NAME__', // placeholder inside a string literal
id: 4663 /* __LATCH_CHAIN_ID__ */, // real literal, tagged by a commentThe tagged form is what lets the template compile before substitution — and a
template nobody can build is a template nobody reviews. After substitution the
marker comment is gone, and scaffold() fails loudly if any marker survives.
Flags
| Flag | Meaning |
|---|---|
| --chain <name\|id> | robinhood / 4663, base / 8453, or sepolia / 11155111. Required. hyperevm / 999 is accepted too: the app renders one "Coming soon on HyperEVM" page until the SDK address book records the chain. |
| --fee-wallet <address> | Where the front end's swap fee is paid. Required when --fee-bps > 0. |
| --fee-bps <n> | Fee in basis points of the swap output, 0..100. Default 0. |
| --features <set> | dex (swap, pools, locks, airdrops), launchpad (launchpad, splits, locks, airdrops), both (everything), or a comma list of swap,pools,launchpad,splits,locks,airdrops. |
| --name <string> | Product name. Defaults from the directory name. |
| --package-manager <pm> | npm / pnpm / yarn / bun. Only changes the printed next steps. |
| --yes | Never prompt. |
The fee ceiling of 100 bps mirrors MAX_INTEGRATOR_FEE_BPS in
@latchprotocol/widgets, which is what actually builds the calldata. If the two
ever disagree, the widget is authoritative.
Other Latch packages
@latchprotocol/sdk— TypeScript SDK: pool keys and ids, the hook permission bitmap, typed events, the address book, market reads and the Launchpad builders@latchprotocol/connect— the wallet layer: a Latch-themed wrapper over RainbowKit and wagmi, with the Latch chain list@latchprotocol/widgets— embeddable swap, liquidity and launch widgets with integrator fee attribution@latchprotocol/cli— thelatchcommand: launches, hosted Launchpad and DEX sites, airdrops, splits, locks andlatch doctor@latchprotocol/create-hook— the hook starter:npx @latchprotocol/create-hook my-latch --template dynamic-fee
Website: latches.fun · X: @Latchesdotfun
No invented data
The template inherits the rule the Latch dapp runs under: every figure is read
from chain, and what cannot be read is named rather than filled in. Four visually
distinct states — loading, error, empty, not-configured — live in
src/components/States.tsx, and there is no fifth state where a screen shows an
example.
Two consequences worth knowing before extending the template:
@latchprotocol/widgetsexports aLaunchWidgetthat this template does not use. It is bound to a proposed token-sale interface that no deployed Latch contract implements. The launch screens here are written against the realLAUNCHPAD_KIT_ABIinstead, and they render a fee schedule rather than a sale progress bar, because a Latch launch is a pool with a decaying fee and has no cap to progress toward.- The pool table discloses capability, never safety. It says what a hook is able to do, from its permission bitmap. A registry listing is not an audit and a bitmap is not a review.
