Files
zeekay 2e1b7dcaf8 feat: @luxwallet/create — stamp a branded Lux Wallet from one brand.json
Generator that scaffolds a ready-to-run branded Lux Wallet web app for a new
chain/org from a single BrandConfig. One command per new chain.

- CLI `create-lux-wallet <brand-ref> [outDir] [--force]` + programmatic
  generate(brand, outDir): validates the brand via @luxwallet/brand's
  defineBrand, then stamps apps/web/ (Vite + React) that mounts @luxwallet/ui
  via WalletUIProvider, authenticates with @hanzo/iam (HIP-0111), reaches
  chains through @luxwallet/sdk, and ships the brand's /brand.json.
- DRY runtime-brand.json strategy: ONE brand-neutral app shell, byte-identical
  across brands (enforced by test). Only public/brand.json + public/logo.svg
  differ. No per-brand forks. *.tmpl files get {{brand.*}} interpolation.
- Example brands lux/hanzo/zoo/pars with canonical @luxwallet/chains ids
  (lux-c 96369, zoo 200200, hanzo 36963, pars 494949) + _template.json.
- New chains: add to @luxwallet/chains registry, reference id in brand.chains[].
- MIT, zero GPL — never imports @luxfi/wallet. @luxwallet/* + @hanzo/* are
  workspace deps with a documented switch to npm versions once published.

Verified headlessly: CLI tsc --noEmit clean; 7/7 node:test pass (tree,
brand.json validity via defineBrand, no token leak, DRY shell identity, force,
resolution); sample generation of all four brands validates.
2026-06-23 15:28:28 -07:00

3.2 KiB

LLM.md — @luxwallet/create

Generator that stamps a branded Lux Wallet web app from one brand.json.

Invariants (do not break)

  • One app shell, runtime brand.json. The shell in templates/web/ is brand-NEUTRAL and byte-identical across brands (enforced by a test). The only per-brand artifacts are public/brand.json (validated BrandConfig) and public/logo.svg. NEVER add per-brand source forks. If you must vary behaviour per brand, drive it from a brand.json field read at runtime.
  • Brand schema is owned by @luxwallet/brand. Match its BrandConfig EXACTLY (src/index.ts there). Validate at the boundary via defineBrand (already done in generate() and parseBrand()). Do not re-declare the type.
  • Chains live in @luxwallet/chains. This generator does NOT define chains. A new chain = a registry entry there, then its id in a brand's chains[].
  • MIT, zero GPL. Never import @luxfi/wallet. Only @luxwallet/brand (MIT).
  • Auth is HIP-0111. The generated app uses @hanzo/iam (IamProvider + useIam) with redirectUri = origin + /auth/callback. No hand-rolled OAuth.

Layout

src/
  index.ts      public API: generate, loadBrand, parseBrand, listBundledBrands, brandsDir
  generate.ts   core: validate brand → walk templates/web → copy + interpolate {{brand.*}} → write public/brand.json
  cli.ts        create-lux-wallet <ref> [outDir] [--force]; optional leading "generate" word; TTY → prompts
  prompts.ts    dependency-free readline prompts for a brand-new chain's core fields
templates/web/  the ONE app shell; *.tmpl files get token interpolation, suffix stripped
brands/         lux/hanzo/zoo/pars + _template.json (copy-me)
test/           node:test via tsx — runs the BUILT dist/

Templating

  • Copy every file under templates/web/. Files ending .tmpl get {{brand.<dotted.path>}} replaced (unknown token → throw) and .tmpl stripped. Only package.json.tmpl and README.md.tmpl are templated today.
  • public/brand.json is written from the validated brand (pretty JSON), not a template.

Build / test / run

pnpm build        # tsc → dist/
pnpm typecheck    # tsc --noEmit (clean)
pnpm test         # build + tsx --test test/*.test.ts (7 tests)
pnpm dev -- lux /tmp/out   # run the CLI from source via tsx
node dist/cli.js generate lux /tmp/out

Local dep wiring gotcha

@luxwallet/brand is workspace:* in package.json (canonical). Standalone installs can't resolve workspace:*. For a throwaway dev install, temporarily point it at file:../sdk/packages/brand, pnpm install, then restore workspace:* before committing. The committed pnpm-lock.yaml reflects the file-dep dev install; that's expected until the package is on npm / the repo joins the sdk workspace.

Publishing gap

@luxwallet/* + @hanzo/* are unpublished. The generated app's package.json PINS the future npm versions with a //deps note and instructions to use workspace/path deps meanwhile. A full pnpm build of a generated app needs those packages (and the @hanzo/gui peer tree) installed — that's the only thing not verifiable headlessly here. Everything else (CLI typecheck, tree, brand.json validity, DRY shell) is covered by test/.