Add @luxwallet/brand — the ONE source of white-label truth (MIT, zero deps,
mirrors the runtime brand.json pattern without depending on GPL
@luxfi/wallet-brand). BrandConfig type + defineBrand() validator + LUX_BRAND
/HANZO_BRAND examples + mutable brand singleton with getBrand()/loadBrandConfig()
(fetch /brand.json, merge in place, apply CSS theme tokens, fail safe) + emitted
brand.schema.json (draft-07).
rpc: add rpcConfigFromBrand(brand) so a brand's gateway.rpcBaseUrl flows into
getRpcUrl without rpc depending on @luxwallet/brand (structural shape). Default
gateway unchanged.
umbrella: re-export brand as @luxwallet/sdk/brand + 'brand' namespace.
Bump @luxwallet/{brand,rpc,sdk} to 0.1.0. Tests: brand 12, rpc 11 (+2). All 7
packages typecheck; verify-clean OK (no GPL, no @noble PQ).
2.2 KiB
@luxwallet/brand
The ONE source of white-label truth for the Lux Wallet stack. MIT, zero dependencies.
A brand is plain runtime config — identity, IAM endpoint, gateway host, theme
tokens, and the chains to enable. Any org white-labels the wallet by shipping a
brand.json; nobody forks a package.
import { getBrand, loadBrandConfig, defineBrand, LUX_BRAND } from "@luxwallet/brand";
// 1. Load the deployment's /brand.json once, before first render.
await loadBrandConfig(); // fetches /brand.json, merges over the singleton
// 2. Everything reads the live brand.
const b = getBrand();
b.name; // "Lux Wallet"
b.gateway.rpcBaseUrl; // "https://api.lux.network"
// 3. Author a brand in code when you don't want a JSON file.
const acme = defineBrand({
id: "acme",
name: "Acme Wallet",
shortName: "Acme",
domain: "acme.example",
iam: { serverUrl: "https://acme.id", clientId: "acme-wallet", scopes: ["openid", "profile", "email"] },
gateway: { rpcBaseUrl: "https://api.acme.example" },
theme: { accent1: "#FF0000" },
chains: ["lux-c-mainnet"],
});
Runtime model
- A deployment ships
/brand.json(its overrides over a base brand) — a K8s ConfigMap mount or a static file. - The host calls
loadBrandConfig()before first render. - Code reads the
brandsingleton, whichloadBrandConfigmutates IN PLACE so already-imported references see the live brand. - Theme tokens become CSS custom properties (
--lw-<token>) on web; native readsgetBrand().themedirectly.
loadBrandConfig FAILS SAFE: any fetch failure keeps the current (default Lux)
brand so local/offline boots still work.
Schema
brand.schema.json (JSON Schema draft-07) validates a brand.json. Regenerate
it from the TypeScript type with pnpm --filter @luxwallet/brand emit.
Secrets
A brand is PUBLIC config. IAM client secrets, WalletConnect ids, and API keys never live in a brand — they come from KMS, injected server-side.
Wiring into RPC
@luxwallet/rpc reads the gateway from a brand without depending on this
package:
import { getRpcUrl, rpcConfigFromBrand } from "@luxwallet/rpc";
import { getBrand } from "@luxwallet/brand";
getRpcUrl(96369, rpcConfigFromBrand(getBrand()));