2026-06-17 09:31:07 -07:00
|
|
|
# `@luxfi/wallet`
|
2026-04-30 11:47:38 -07:00
|
|
|
|
2026-06-23 15:07:40 -07:00
|
|
|
**Project**: `luxfi/wallet` — canonical Lux Wallet upstream (web + mobile + extension + backend).
|
2026-04-30 11:47:38 -07:00
|
|
|
**Org**: Lux Industries Inc. (`luxfi`).
|
2026-06-23 15:07:40 -07:00
|
|
|
**Status**: `apps/web` builds clean. `apps/backend` (Go MPC custody server) builds
|
|
|
|
|
+ tests green (17 tests). `apps/{extension,mobile}` retain upstream-shaped src
|
|
|
|
|
that requires an app-level refactor to compile against current `@l.x/*` npm
|
2026-04-30 11:47:38 -07:00
|
|
|
packages. The canonical bones (`pkgs/{wallet,brand,analytics}`) are stable.
|
|
|
|
|
|
2026-06-23 15:07:40 -07:00
|
|
|
> The freshly-scaffolded MIT TS shared core lives in a SEPARATE org —
|
|
|
|
|
> `github.com/luxwallet/*` (`@luxwallet/{chains,rpc,crypto,keyring,tx,sdk}`,
|
|
|
|
|
> + `connect`, `ui`, `desktop`, native ios/android/mobile-rn). That is the
|
|
|
|
|
> cross-target shared core; `luxfi/wallet` here is the GPL product monorepo
|
|
|
|
|
> (web/extension/mobile FE + this Go custody backend + PQ stack). The backend's
|
|
|
|
|
> custody port mirrors `@luxwallet/keyring`'s account model + the lux/mpc
|
|
|
|
|
> `client.Threshold` security contract.
|
|
|
|
|
|
2026-04-30 11:47:38 -07:00
|
|
|
## Canonical structure
|
|
|
|
|
|
|
|
|
|
```
|
|
|
|
|
luxfi/wallet/
|
|
|
|
|
├── apps/
|
|
|
|
|
│ ├── web/ — Vite 8 SPA, React 19, brand-aware. Builds in <100ms.
|
2026-06-23 15:07:40 -07:00
|
|
|
│ ├── backend/ — Go MPC custody server (App(Wallet) backend). See below.
|
2026-04-30 11:47:38 -07:00
|
|
|
│ ├── extension/ — Chrome/Firefox MV3 (upstream-shaped; app-refactor pending).
|
|
|
|
|
│ └── mobile/ — React Native + Expo (upstream-shaped; app-refactor pending).
|
|
|
|
|
├── pkgs/
|
|
|
|
|
│ ├── wallet/ — `@luxfi/wallet` — canonical wallet feature bones.
|
|
|
|
|
│ ├── brand/ — `@luxfi/wallet-brand` — runtime white-label config.
|
|
|
|
|
│ └── analytics/ — `@luxfi/wallet-analytics` — provider-pluggable analytics.
|
|
|
|
|
├── SCREENS.md — UX spec freeze (2025-12-15).
|
|
|
|
|
└── LEGACY.md — Sibling repos (wallet-legacy, wwallet, xwallet) to archive.
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
## Build commands (verified working)
|
|
|
|
|
|
|
|
|
|
```bash
|
|
|
|
|
pnpm install --no-frozen-lockfile
|
|
|
|
|
pnpm --dir apps/web build # Vite SPA — clean. Output in apps/web/dist/.
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
`apps/{extension,mobile}` build via Nx targets that depend on workspace
|
|
|
|
|
packages still upstream-shaped (`@universe/*`, `wallet/*` direct paths) and
|
|
|
|
|
will not type-check or build until refactored to consume the npm-published
|
|
|
|
|
`@l.x/*` and the workspace `@luxfi/wallet`. Track in this file when fixed.
|
|
|
|
|
|
2026-04-30 13:06:55 -07:00
|
|
|
## Foundation slice (apps/web shell)
|
|
|
|
|
|
|
|
|
|
`apps/web` is the Vite SPA shell. Provider tree:
|
|
|
|
|
|
|
|
|
|
```
|
|
|
|
|
GuiProvider → QueryClientProvider → WagmiProvider → RouterProvider
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
- `src/main.tsx` — awaits `loadBrandConfig()` before first render.
|
|
|
|
|
- `src/App.tsx` — wraps the four providers above.
|
|
|
|
|
- `src/router.tsx` — react-router-dom v7 with lazy-loaded screen modules.
|
|
|
|
|
- `src/components/AppShell.tsx` — top bar + side drawer + Outlet.
|
|
|
|
|
- `src/components/ChainSwitcher.tsx` — native `<select>` over `brand.supportedChainIds`.
|
|
|
|
|
- `src/components/GuiProvider.tsx` — passthrough today; swap to `HanzoguiProvider`
|
|
|
|
|
once `@hanzo/gui@7` republishes its missing `dist/` artifact.
|
|
|
|
|
- `src/config/wagmi.ts` — `buildWagmiConfig()` reads `brand.supportedChainIds`
|
|
|
|
|
and resolves transports via `getBootnodeRpcUrl(chainId)`. Chains without a
|
|
|
|
|
resolvable RPC are dropped — never `http("")`.
|
|
|
|
|
- `src/config/queryClient.ts` — 30 s stale, retries off, no focus refetch.
|
|
|
|
|
- `src/store/index.ts` — zustand `account` / `chain` / `ui` slices owned by
|
|
|
|
|
Foundation. Other slices (`auth`, `send`, `swap`, `stake`) are sibling files
|
|
|
|
|
owned by other Blues.
|
|
|
|
|
- `src/hooks/useAccount.ts` — wagmi + zustand union; the only `useAccount`
|
|
|
|
|
screens should import.
|
|
|
|
|
- `src/hooks/useBrand.ts` — `() => brand` for React-friendly typing.
|
|
|
|
|
|
|
|
|
|
Screen Blues fill `src/screens/{name}/index.tsx`; today each is a labelled
|
|
|
|
|
placeholder so the build is clean and merges are pure file replacements.
|
|
|
|
|
|
2026-06-23 15:51:02 -07:00
|
|
|
## Custody + lux.id auth wiring (apps/web)
|
|
|
|
|
|
|
|
|
|
The web app talks to the MPC custody backend (`apps/backend`, `wallet-api.<brand>`)
|
|
|
|
|
authenticated via lux.id. One way, three thin layers:
|
|
|
|
|
|
|
|
|
|
- `src/lib/iam.ts` — lux.id OIDC Authorization-Code + **PKCE** (Web Crypto only,
|
|
|
|
|
no dep). Issuer + clientId come from the brand (`getIamConfig()`); the
|
|
|
|
|
callback is `/auth/callback`. Tokens live in **sessionStorage** (survive a tab
|
|
|
|
|
reload, gone on close; never localStorage, never logged). `getAccessToken()`
|
|
|
|
|
refreshes on expiry. The Go backend verifies this same issuer + audience and
|
|
|
|
|
derives the org from the token's `owner` claim.
|
|
|
|
|
- `src/lib/custody.ts` — typed client for `/v1/wallets`, `/v1/wallets/{id}`,
|
|
|
|
|
`/v1/wallets/{id}/sign`. Matches `internal/api/api.go` + `custody.*` JSON
|
|
|
|
|
exactly. Attaches `Authorization: Bearer`; **never sends org** (server-side
|
|
|
|
|
only). `signPayload` requires a non-empty `idempotencyKey` (anti-replay) and
|
|
|
|
|
refuses to send without one. Validates returned EVM addresses (EIP-55) at the
|
|
|
|
|
boundary. `fetchImpl` is injectable for tests.
|
|
|
|
|
- `src/store/session.ts` — lux.id session + custody wallets + `signWithCustody`.
|
|
|
|
|
`App.tsx` calls `hydrate()` on boot (restore token → list wallets).
|
|
|
|
|
- `src/screens/signing/SigningModal.tsx` — on confirm, a tx carrying
|
|
|
|
|
`custody.payloadHash` requests the MPC signature and resolves
|
|
|
|
|
`SigningResult.signature`; a custody failure resolves `reason:"error"` (no
|
|
|
|
|
silent noop). The local-key path (no `custody` field) is unchanged. **Tx
|
|
|
|
|
broadcast is a typed seam** — the modal returns the signature; assembling +
|
|
|
|
|
broadcasting the signed tx via `getBootnodeRpcUrl` is the calling slice's job
|
|
|
|
|
(not yet wired into Send/Swap; no `// TODO`, the seam is explicit).
|
|
|
|
|
|
|
|
|
|
Brand gained three explicit fields (in `pkgs/brand/src/index.ts` + every
|
|
|
|
|
`brand.json`): `walletApi` (custody base URL), `iamIssuer`, `iamClientId`
|
|
|
|
|
(`<org>-wallet`), plus an optional `downloads` manifest. Helpers:
|
|
|
|
|
`getWalletApiUrl()`, `getIamConfig()`.
|
|
|
|
|
|
|
|
|
|
## /download screen (per-brand host)
|
|
|
|
|
|
|
|
|
|
`src/screens/download/` renders the per-brand native-download page served at
|
|
|
|
|
`wallet.<brand>/download` (client-routed; the SPA server's index.html fallback
|
|
|
|
|
covers it). Logo + name from the `brand` singleton; a grid of macOS/Windows/
|
|
|
|
|
Linux/iOS/Android/extension reads `brand.downloads` (binary `url` or `storeUrl`
|
|
|
|
|
+ `version` + `checksumUrl` for "Verify signature"); a missing platform shows
|
|
|
|
|
"Coming soon". Binaries are published by the native CI fleet (NO GHA).
|
|
|
|
|
|
|
|
|
|
## Tests (apps/web) — Node built-in runner
|
|
|
|
|
|
|
|
|
|
`pnpm --dir apps/web test`. Runner = Node's stdlib `node:test` (the repo's one
|
|
|
|
|
way; no vitest dep) via `tools/ts-resolve.mjs`, a synchronous resolve hook that
|
|
|
|
|
maps bundler-style extensionless `.ts` imports for the loader (Node 22+ strips
|
|
|
|
|
types natively). The script covers `src/{lib,store,screens/dapps}/**/*.test.ts`
|
|
|
|
|
(63 tests). `src/screens/_shared/runtime.test.ts` is a JSX import probe for
|
|
|
|
|
`tsc --noEmit` (its own header), not a runtime test — it stays in `typecheck`.
|
|
|
|
|
New suites: `lib/custody.test.ts` (12 — bearer attached, no-org, exact
|
|
|
|
|
endpoints/bodies, idempotency required, boundary validation, error mapping),
|
|
|
|
|
`lib/brand.test.ts` (6 — each brand overlay → correct name/chain/logo/walletApi/
|
|
|
|
|
issuer, total swap, downloads present).
|
|
|
|
|
|
2026-04-30 11:47:38 -07:00
|
|
|
## White-label brand pattern (canonical)
|
|
|
|
|
|
|
|
|
|
Brand config flows at runtime, not build time. Same pattern as
|
|
|
|
|
`~/work/lux/exchange/pkgs/config/src/brand.ts`.
|
|
|
|
|
|
|
|
|
|
```
|
|
|
|
|
@luxfi/wallet-brand/brand.json ─copy→ apps/web/dist/brand.json
|
|
|
|
|
│
|
|
|
|
|
↓
|
|
|
|
|
K8s ConfigMap mount overlays
|
|
|
|
|
│
|
|
|
|
|
↓
|
|
|
|
|
loadBrandConfig() fetches /brand.json
|
|
|
|
|
│
|
|
|
|
|
↓
|
|
|
|
|
mutates `brand` singleton + CSS vars
|
|
|
|
|
│
|
|
|
|
|
↓
|
|
|
|
|
React renders against final brand
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
White-labels (Liquidity, Zoo, Pars) override `/brand.json` via a K8s
|
|
|
|
|
ConfigMap — no source fork required. Theme tokens are CSS custom properties
|
|
|
|
|
(`--accent1`, `--surface1`, etc.) so the same DOM tree renders any brand.
|
|
|
|
|
|
|
|
|
|
## Analytics pattern (canonical)
|
|
|
|
|
|
|
|
|
|
`@luxfi/wallet-analytics` provides a provider-pluggable `AnalyticsDriver`
|
|
|
|
|
interface. No third-party SDK loaded by default. Hanzo Insights is the
|
|
|
|
|
intended default driver (registered at app bootstrap). White-labels swap
|
|
|
|
|
drivers at boot via `setAnalyticsDriver(...)`. Surface matches what wallet
|
|
|
|
|
call sites expect (`init`/`track`/`identify`/`Identify`/...).
|
|
|
|
|
|
|
|
|
|
## RPC pattern (canonical)
|
|
|
|
|
|
|
|
|
|
`getBootnodeRpcUrl(chainId)` from `@luxfi/wallet-brand` is the **only** way
|
|
|
|
|
to resolve an RPC endpoint. Default is `https://<gatewayDomain>/v1/rpc/<chainId>`
|
|
|
|
|
with `runtimeConfig.rpc[<chainId>]` overrides honored first. No Quicknode,
|
|
|
|
|
no Alchemy direct. White-labels point at their own gateway (e.g.,
|
|
|
|
|
`gw.lux.exchange/v1/rpc/96369`).
|
|
|
|
|
|
|
|
|
|
## Direct dependency hygiene
|
|
|
|
|
|
|
|
|
|
- **Zero `@datadog/*` direct deps** in any app `package.json` or `pkgs/*`.
|
|
|
|
|
(Transitive deps via `@l.x/utils` and `@l.x/lx` remain — those are upstream
|
|
|
|
|
packages and will be cleaned in their own pkg releases.)
|
|
|
|
|
- **Zero `@amplitude/*` direct deps**.
|
2026-05-06 20:51:41 -07:00
|
|
|
- **Zero `@luxamm/*` direct deps** (forked to `@luxamm/*` per commit `81b2ba3e`).
|
2026-04-30 11:47:38 -07:00
|
|
|
- **Zero `getQuicknodeEndpointUrl` references** in our source — only the
|
|
|
|
|
string literal `'quicknode'` exists in error-matching test fixtures.
|
|
|
|
|
|
|
|
|
|
## Workspace catalog (`pnpm-workspace.yaml`)
|
|
|
|
|
|
|
|
|
|
`@hanzogui/*-fork.1` aliases for React Native packages. The `@hanzogui` prefix
|
2026-05-06 19:23:29 -07:00
|
|
|
is the internal umbrella name. Product brand for the GUI library is `@hanzo/gui`.
|
2026-04-30 11:47:38 -07:00
|
|
|
|
|
|
|
|
## Known transitive type errors (NOT our bug)
|
|
|
|
|
|
|
|
|
|
`@l.x/api`, `@l.x/lx`, `@l.x/utils`, `@l.x/config` ship as raw `.ts` files
|
|
|
|
|
and reference a rootless `utilities/src/*` module that doesn't exist on disk.
|
|
|
|
|
This means `tsc --noEmit` against `pkgs/wallet/` traces into `node_modules/.pnpm/@l.x+*`
|
|
|
|
|
and produces ~5000 errors. **These are upstream-publishing bugs in the `@l.x/*`
|
|
|
|
|
packages** — they need to ship `.d.ts` artifacts and self-resolved imports.
|
|
|
|
|
Filed as a follow-up; lux/wallet does not patch upstream packages.
|
|
|
|
|
|
|
|
|
|
The web SPA builds because Vite tree-shakes — only used surface is touched.
|
|
|
|
|
|
|
|
|
|
## Sibling repos (see LEGACY.md)
|
|
|
|
|
|
|
|
|
|
- `~/work/lux/wallet-legacy` — OneKey-fork lineage. BRAND_PACKAGE pattern absorbed → archive.
|
|
|
|
|
- `~/work/lux/wwallet` — bespoke SDK line. Superseded by `@l.x/api` → archive.
|
|
|
|
|
- `~/work/lux/xwallet` — OKX-fork lineage. Hardware support already removed → archive.
|
|
|
|
|
- `~/work/lux/dwallet` — Desktop product. **Independent**, do not fold in.
|
|
|
|
|
|
2026-06-23 15:07:40 -07:00
|
|
|
## `apps/backend` — App(Wallet) custody server (Go)
|
|
|
|
|
|
|
|
|
|
The wallet's BACKEND. A small Go HTTP service that gives the wallet a custody
|
|
|
|
|
account model with **MPC custody — NO plaintext keys, ever**. The private key
|
|
|
|
|
is split t-of-n across the MPC nodes (lux → `mpc.lux.network`) and is never
|
|
|
|
|
assembled; this service stores only public keys + addresses.
|
|
|
|
|
|
|
|
|
|
```
|
|
|
|
|
apps/backend/
|
|
|
|
|
├── cmd/wallet-backend/main.go — env config, graceful shutdown, JSON logging
|
|
|
|
|
└── internal/
|
|
|
|
|
├── iam/ — lux.id OIDC bearer verify (JWKS discovery, RS256/ES256,
|
|
|
|
|
│ alg-confusion-safe). Org from the `owner` claim. (golang-jwt/v5)
|
|
|
|
|
├── custody/ — the custody PORT (Custodian iface) + MPC HTTP adapter to
|
|
|
|
|
│ lux/mpc (/keygen + /sign). Mirrors lux/mpc client.Threshold's
|
|
|
|
|
│ security contract (idempotency required, org-scoped). PQ-ready
|
|
|
|
|
│ (Scheme mldsa65 is first-class).
|
|
|
|
|
├── store/ — wallet↔org↔addresses (no key field). Org-scoped reads (a
|
|
|
|
|
│ cross-org get is ErrNotFound — no existence disclosure).
|
|
|
|
|
└── api/ — /v1/* HTTP (no /api/ prefix). IAM-gated, org-scoped:
|
|
|
|
|
POST /v1/wallets · GET /v1/wallets · GET /v1/wallets/{id} ·
|
|
|
|
|
POST /v1/wallets/{id}/sign · GET /v1/health (open).
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
Run: `cd apps/backend && go test ./...` (17 tests). Build standalone with
|
|
|
|
|
`GOWORK=off go build ./cmd/wallet-backend` (the monorepo go.work also lists it).
|
|
|
|
|
Deploy: build-only `.platform.yml` (repo root) → `ghcr.io/luxfi/wallet-backend`;
|
|
|
|
|
the lux operator rolls `apps/backend/k8s/wallet-backend.yaml` (lux.cloud/v1
|
|
|
|
|
Service + KMSSecret for `MPC_SERVICE_TOKEN`), ingress `wallet-api.lux.network`.
|
|
|
|
|
|
|
|
|
|
White-label is per-tenant + per-env injection — `IAM_ISSUER` (lux → lux.id),
|
|
|
|
|
`IAM_AUDIENCE` (`<org>-wallet`), `MPC_ENDPOINT` (lux → mpc.lux.network; shared
|
|
|
|
|
hanzo-mpc or BYOMPC). Same binary, any brand. PQ crypto is NOT duplicated here —
|
|
|
|
|
ML-DSA/SLH-DSA live in `pkgs/wallet/.../pq` and on the consensus/precompile side;
|
|
|
|
|
the custody layer just carries `mldsa65` as a scheme through to MPC.
|
|
|
|
|
|
|
|
|
|
## App(Wallet) spec — for the unified operator (`App` Kind)
|
|
|
|
|
|
|
|
|
|
The unified web3 operator's `App` Kind deploys the wallet, white-labeled by
|
2026-06-23 16:00:04 -07:00
|
|
|
domain. The wallet App is **three deployable groups**, one OSS core, branded
|
|
|
|
|
per tenant. The product KIND (`wallet`) is orthogonal to its SOURCE
|
|
|
|
|
(`native` = our OSS default | `override` = a tenant fork): a tenant points
|
|
|
|
|
`source.repo`/`source.ref` at any OSS wallet to replace our native one — same
|
|
|
|
|
decomplect as pluggable-MPC.
|
2026-06-23 15:07:40 -07:00
|
|
|
|
2026-06-23 16:00:04 -07:00
|
|
|
| Group | What | Artifact | Host (per brand) |
|
|
|
|
|
|-------|------|----------|------------------|
|
|
|
|
|
| Web wallet | `apps/web` Vite SPA, runtime `brand.json` (no source fork) | `ghcr.io/luxfi/wallet-web` | `wallet.<brand>` |
|
2026-06-23 15:07:40 -07:00
|
|
|
| Custody backend | `apps/backend` Go MPC custody server | `ghcr.io/luxfi/wallet-backend` | `wallet-api.<brand>` |
|
2026-06-23 16:00:04 -07:00
|
|
|
| Native binaries | `luxwallet/*` shells (desktop/ios/android/extension) over `@luxwallet/sdk` | signed installers (per platform), GitHub Release | `wallet.<brand>/download` |
|
2026-06-23 15:07:40 -07:00
|
|
|
|
2026-06-23 16:00:04 -07:00
|
|
|
`App(Wallet)` CR shape (operator renders Service(s) + Ingress + KMSSecret;
|
|
|
|
|
`chainDefault` and the brand `configMap` are the only per-tenant deltas):
|
2026-06-23 15:07:40 -07:00
|
|
|
|
|
|
|
|
```yaml
|
|
|
|
|
apiVersion: lux.cloud/v1
|
|
|
|
|
kind: App
|
2026-06-23 16:00:04 -07:00
|
|
|
metadata: { name: lux-wallet } # <brand>-wallet
|
2026-06-23 15:07:40 -07:00
|
|
|
spec:
|
2026-06-23 16:00:04 -07:00
|
|
|
kind: wallet # curated view: balances/send/receive/PQ identity
|
|
|
|
|
source: { mode: native } # native luxwallet | { mode: override, repo, ref }
|
|
|
|
|
brand: lux # selects the brand.json overlay + native artifacts
|
2026-06-23 15:07:40 -07:00
|
|
|
web: { image: ghcr.io/luxfi/wallet-web, host: wallet.lux.network }
|
|
|
|
|
backend:{ image: ghcr.io/luxfi/wallet-backend, host: wallet-api.lux.network }
|
2026-06-23 16:00:04 -07:00
|
|
|
download: { host: wallet.lux.network/download } # per-brand signed-binary page
|
|
|
|
|
iam: https://lux.id # per-tenant (lux→lux.id; hanzo→hanzo.id; zoo→zoo.id)
|
|
|
|
|
mpc: https://mpc.lux.network # per-tenant: shared hanzo-mpc | BYOMPC
|
|
|
|
|
chainDefault: 96369 # per brand: lux 96369 · hanzo 36963 · zoo 200200
|
2026-06-23 15:07:40 -07:00
|
|
|
```
|
|
|
|
|
|
2026-06-23 16:00:04 -07:00
|
|
|
Per-brand defaults (the only values that differ; everything else is one image):
|
|
|
|
|
|
|
|
|
|
| Brand | `chainDefault` | web host | iam | gateway |
|
|
|
|
|
|-------|----------------|----------|-----|---------|
|
|
|
|
|
| lux | 96369 (C-Chain) | wallet.lux.network | lux.id | api.lux.network |
|
|
|
|
|
| hanzo | 36963 (Hanzo L1) | wallet.hanzo.ai | hanzo.id | api.hanzo.ai |
|
|
|
|
|
| zoo | 200200 (Zoo) | wallet.zoo.ngo | zoo.id | api.zoo.network |
|
|
|
|
|
|
|
|
|
|
Custody = the `MPC` Kind (shared hanzo-mpc or BYO). The wallet App references
|
|
|
|
|
{iam, mpc} endpoints; the operator wires the KMSSecret (`MPC_SERVICE_TOKEN`).
|
|
|
|
|
The backend custody adapter speaks the lux/mpc HTTP wire `POST /keygen` +
|
|
|
|
|
`POST /sign` (mpcd internal API, default `:9800`, bearer `MPC_INTERNAL_API_KEY`).
|
|
|
|
|
**`/sign` is LIVE** (lux/mpc `cmd/mpcd`, idempotent + t-of-n-quorum-gated, the
|
|
|
|
|
threshold signature never assembles a key) — the backend↔MPC sign path is a
|
|
|
|
|
real threshold signature, not a mock.
|
2026-06-23 15:07:40 -07:00
|
|
|
|
2026-06-23 15:51:02 -07:00
|
|
|
**Web deploy manifests** — `apps/web/k8s/` is a Kustomize base + 3 brand
|
|
|
|
|
overlays (`overlays/{lux,hanzo,zoo}`). Base = `lux.cloud/v1 Service` for
|
|
|
|
|
`ghcr.io/luxfi/wallet-web` (port 3000, `imagePullSecrets: ghcr-luxfi`,
|
|
|
|
|
`/` health). Each overlay sets `namespace`, ingress host
|
|
|
|
|
(`wallet.{lux.network,hanzo.ai,zoo.ngo}`), `brand: <b>` label, the pinned image
|
|
|
|
|
tag, and a `configMapGenerator` over that dir's `brand.json` (`brand` volume →
|
|
|
|
|
`/public/brand.json`, `disableNameSuffixHash` since the volume ref is in a
|
|
|
|
|
custom CR). The brand `brand.json` files in `overlays/<b>/` are the SINGLE
|
|
|
|
|
source — the brand-swap test reads them directly. Build: `kubectl kustomize
|
|
|
|
|
apps/web/k8s/overlays/<b>`. Serving = house `ghcr.io/hanzoai/spa` (scratch,
|
|
|
|
|
:3000, SPA fallback → index.html, so `/download` client-routes). Apps/web image
|
|
|
|
|
+ backend image both built by `.platform.yml` (now a 2-entry build list).
|
|
|
|
|
|
2026-06-23 16:00:04 -07:00
|
|
|
**Native binaries + download** — the signed installers are PER-BRAND artifacts
|
|
|
|
|
(a signed installer is immutable, so brand = logo + `chainDefault` is BAKED at
|
|
|
|
|
build, unlike the web app's runtime brand.json). Each `luxwallet/*` shell has a
|
|
|
|
|
canonical `.github/workflows/release.yml` (tag `v*` / dispatch) that builds on
|
|
|
|
|
the arcd fleet (NO GHA) with a `BRAND` matrix axis (lux|hanzo|zoo, brand data
|
|
|
|
|
resolved from `@luxwallet/brand` → `@luxwallet/chains`), then hands the unsigned
|
|
|
|
|
artifact to the `hanzoai/ci-signing/.github/workflows/sign-<plat>.yml@v1`
|
|
|
|
|
reusable workflow and publishes the signed artifact + checksum to a GitHub
|
|
|
|
|
Release. Platform → arcd pool → signer:
|
|
|
|
|
|
|
|
|
|
| Platform | shell | arcd pool | ci-signing | artifact |
|
|
|
|
|
|----------|-------|-----------|------------|----------|
|
|
|
|
|
| macOS | desktop (Electron) | `luxfi-macos-arm64` | `sign-macos` | notarized `.dmg` |
|
|
|
|
|
| Windows | desktop | `luxfi-windows-amd64` | `sign-windows` | Authenticode `.exe` |
|
|
|
|
|
| Linux | desktop | `luxfi-linux-{amd64,arm64}` | `sign-linux` | `.AppImage` + `.asc` |
|
|
|
|
|
| iOS | ios (Xcode/Swift) | `dbc-luxfi-macos` | `sign-ios` | App-Store `.ipa` |
|
|
|
|
|
| Android | android (Gradle/Kotlin) | `luxfi-linux-amd64` | `sign-android` | signed `.aab` |
|
|
|
|
|
| Extension | extension (MV3) | `luxfi-linux-amd64` | `publish-{chrome,firefox,safari}` | store pkg + `.xpi` |
|
|
|
|
|
|
|
|
|
|
The `/download` screen (`apps/web/src/screens/download/`) renders the per-brand
|
|
|
|
|
page (brand logo + name) from a `downloads` block in `brand.json`
|
|
|
|
|
(`{ <platform>: { url|storeUrl, version, checksumUrl } }`); missing platforms
|
|
|
|
|
show "Coming soon". The native artifacts' Release-asset URLs feed that block,
|
|
|
|
|
so `wallet.<brand>/download` is the signed-binary host for every platform.
|
|
|
|
|
|
2026-04-30 11:47:38 -07:00
|
|
|
## Rules for AI Assistants
|
|
|
|
|
|
|
|
|
|
1. **NEVER** write random summary files — update `LLM.md` only.
|
|
|
|
|
2. **NEVER** commit symlinked files (.AGENTS.md, CLAUDE.md, etc.) — they're in `.gitignore`.
|
2026-05-06 20:51:41 -07:00
|
|
|
3. **NEVER** introduce direct `@datadog/*`/`@amplitude/*`/`@luxamm/*` deps —
|
2026-04-30 11:47:38 -07:00
|
|
|
use the abstractions in `pkgs/analytics` and `@luxamm/*`.
|
|
|
|
|
4. **NEVER** reference `getQuicknodeEndpointUrl` — use `getBootnodeRpcUrl`.
|
|
|
|
|
5. **NEVER** hardcode brand strings (`"Lux Wallet"`, `"lux.network"`) in app code —
|
|
|
|
|
read from the `brand` singleton in `@luxfi/wallet-brand`.
|
2026-05-06 19:23:29 -07:00
|
|
|
6. **NEVER** write the banned forked-UI brand — use "@hanzo/gui" / `@hanzogui/*` umbrella.
|
2026-04-30 11:47:38 -07:00
|
|
|
7. **ALWAYS** preserve BIP44 path 9000 for Lux P/X chain addresses in any
|
|
|
|
|
key derivation (60 for EVM C-chain).
|
|
|
|
|
8. **ALWAYS** keep the GPL-3.0-or-later license header — wallet inherits it.
|
2026-05-19 11:39:58 -07:00
|
|
|
9. **ALWAYS** route PQ crypto through `pkgs/wallet/src/features/wallet/pq/` —
|
|
|
|
|
ML-DSA / ML-KEM / SLH-DSA / hybrid / HD-PQ derivation / domain separators /
|
|
|
|
|
precompile encoders all live there. Re-export from `apps/web/src/lib/pq.ts`.
|
|
|
|
|
|
|
|
|
|
## PQ stack (LP-4200, FIPS 203/204/205)
|
|
|
|
|
|
|
|
|
|
Canonical entry: `pkgs/wallet/src/features/wallet/pq/index.ts`. Web facade:
|
|
|
|
|
`apps/web/src/lib/pq.ts`. React hook: `apps/web/src/hooks/usePQIdentity.ts`.
|
|
|
|
|
|
|
|
|
|
| Module | Purpose |
|
|
|
|
|
|-----------------|--------------------------------------------------------------|
|
|
|
|
|
| `pqAccount.ts` | shape, cSHAKE AccountID, MLDSAProvider iface, HD-path strings |
|
|
|
|
|
| `domain.ts` | FIPS-204 ctx strings (evm-precompile, x-chain-utxo, …) |
|
|
|
|
|
| `mldsa.ts` | ML-DSA-44/65/87 providers via @noble/post-quantum |
|
|
|
|
|
| `mlkem.ts` | ML-KEM-512/768/1024 KEM providers |
|
|
|
|
|
| `slhdsa.ts` | SLH-DSA-128f/192f/192s/256f providers |
|
|
|
|
|
| `hybrid.ts` | Ed25519∥ML-DSA + secp256k1∥ML-DSA containers (luxfi/sdk-compat)|
|
|
|
|
|
| `hdPq.ts` | BIP32 → expandChildSeed → ML-DSA/SLH-DSA keypair |
|
|
|
|
|
| `precompile.ts` | eth_call wrappers for 0x012201/02/03 |
|
|
|
|
|
|
|
|
|
|
Tests run via `pnpm --dir pkgs/wallet exec jest --config jest.pq.config.js`
|
|
|
|
|
(standalone — bypasses the upstream-shaped jest-expo preset that's still
|
|
|
|
|
pending refactor). 71 tests passing as of 2026-05-18.
|
2026-04-30 11:47:38 -07:00
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
**Single source of truth** for AI assistants on `luxfi/wallet`. Update this file
|
|
|
|
|
when behavior changes; never spawn parallel `.md` documents.
|