Commit Graph
57 Commits
Author SHA1 Message Date
Hanzo AIandzeekay 58e646e718 chore: scrub tamagui — use @hanzo/gui (and @hanzogui/* forks) only
Tamagui is banned per directive. Replace every import/reference with
@hanzo/gui or the @hanzogui/* equivalent. Removes:
- tamagui-loader and tamaguiPlugin from apps/extension webpack/wxt configs
- @tamagui/babel-plugin TODO block from apps/mobile/babel.config.js
- 'tamagui', '@tamagui/web' from optimizeDeps.include hints
- @tamagui/core/reset.css side-effect imports → @hanzogui/core/reset.css
- TamaguiProvider in mobile tests → GuiProvider from gui-provider
- TamaguiInput type aliases in extension Input.tsx → GuiInput
- declare module 'tamagui' in env.d.ts → declare module '@hanzo/gui' (uses GuiGroupNames)
- '.tamagui' ignore patterns in .gitignore / .fingerprintignore
- 'tamagui-loader' from .depcheckrc

Source files: 18 .ts/.tsx files updated to import from @hanzo/gui /
@hanzogui packages. Tests + config: 4 files updated to drop
tamagui-specific behavior. Docs (LLM.md, README.md, RABBY_FEATURES.md):
updated to reflect new stack.

No tamagui entries remain in any package.json.
2026-06-10 22:35:40 -07:00
Hanzo AIandzeekay 64268180bf fix(extension): repair svg-import-fix transform body + restore optimizeDeps wrapper
The transform function for the 'svg-import-fix' plugin was missing its body
and return statement. The list of bundled deps that follows ('tamagui',
'@tamagui/web', etc.) was orphaned without an optimizeDeps.include wrapper.
Restored both:
- transform body returns code with import { ReactComponent as N } from '*.svg'
- optimizeDeps.include array properly opened/closed around the dep list
2026-06-10 22:35:40 -07:00
Hanzo AIandzeekay 79a4645aac fix(extension): remove orphaned ternary fragment from wxt.config.ts
Lines 110-115 contained a leftover dynamic-manifest ternary fragment
with no condition — broke parse and prevented `pnpm exec wxt build`
from running. Drop the dead code; manifest defines stay in the inline
manifest field above and tenant overlays via manifest.overlay.json.
2026-06-10 22:35:40 -07:00
Hanzo AIandzeekay d8f73616f0 wallet: scrub Avalabs IP brand refs 2026-06-10 22:35:40 -07:00
Hanzo AI 9b619517bc feat(chains): @luxfi/wallet-chains — viem chain defs for Lux ecosystem
Canonical home for static viem `defineChain` configs across the Lux
ecosystem (lux/build, lux/wallet, third-party integrators). Ships
lux/luxTestnet/luxDex, zoo/zooTestnet, hanzo/hanzoTestnet,
sparklePony/sparklePonyTestnet, pars/parsTestnet plus collection
re-exports (LUX_CHAINS, ZOO_CHAINS, ...). Published to npm as
@luxfi/wallet-chains@0.1.1 (tsup esm + dts, viem peerDep ^2.30).

White-label deployments overlay their own chain configs at runtime
via the brand.json layer in @luxfi/wallet-brand and do not consume
this package directly.
2026-04-30 17:57:33 -07:00
Hanzo AI e068c079b8 docs(legacy): wwallet migration audit + local archive (2026-04-30)
Walked every dir under wwallet/{app,web,components,sdk}. Zero files ported.
Canonical luxfi/wallet already covers every feature wwallet had with a
stricter security model and the SDK already published on npm.

- Local: mv ~/work/lux/wwallet ~/work/lux/.wwallet-archived-2026-04-30
- GitHub: gh repo archive luxfi/wwallet pending org admin permission
2026-04-30 17:54:39 -07:00
Hanzo AI 29eab77bd2 feat(bridge): Bridge form + Lux Teleport quote/execute hooks
SCREENS.md §5. Wires /bridge to the cross-chain transfer form: source/
dest chain selectors, asset amount, recipient (same wallet | other),
route preview (lock → MPC → mint), threshold notation.

  Bridge.tsx           : form, ChainPair, AssetAmount, Recipient,
                         RoutePreview; status-driven button label
                         (Locking → MPC ceremony → Minting → Bridged)
  useBridgeQuote.ts    : gateway /v1/bridge/quote; 300ms debounce, 5s
                         timeout, AbortController on input change
  useBridgeExecute.ts  : 3-step Teleport flow:
                         1. approve (ERC-20) + lock (lockToken/lockNative)
                         2. poll M-Chain MPC ceremony (2s interval, 5m cap)
                         3. surface destination mint tx hash
                         User signs ONLY leg 1; mint is signed by the
                         bridge committee via threshold MPC. This is what
                         makes Teleport feel like a transfer, not two txs.

Threat model:
  - destChainId + recipient pinned in lock calldata before the committee
    sees the message. A compromised gateway cannot redirect funds.
  - Ceremony URL built from brand.gatewayDomain (immutable per build,
    ConfigMap-overridden per environment).
  - Quote staleness >30s rejected; users can't sign locks against
    quotes that have aged out.
  - lockContract zero-address guard prevents accidental burns when the
    Teleport factory hasn't been deployed on the source chain yet.
  - Non-EVM source chains explicitly refused (clear error, not silent
    failure) — Lux P/X bridges go through @l.x/api in a later slice.
2026-04-30 17:54:01 -07:00
Hanzo AI 8bb1343757 feat(swap): SwapForm + TokenSelector + quote/execute hooks
SCREENS.md §3. Wires /swap and /swap/confirm to the canonical token-swap
form: from/to TokenSelector × 2, decimal amount, slippage popover (10/50/
100bps presets + custom), QuoteRoute panel with route kind badge.

  Swap.tsx           : main form, flip button, slippage popover, submit gate
  TokenSelector.tsx  : modal w/ search + popular pinned + inline balances
  QuoteRoute.tsx     : route kind ("AMM v2/v3" | "DEX v4") + path + impact
  useSwapQuote.ts    : gateway /v1/quote (preferred) + on-chain QuoterV2
                       fallback via useReadContract; 300ms debounce, 5s
                       timeout, AbortController on input change
  useSwapExecute.ts  : approve (max-uint, ERC-20 only) + swap via wagmi
                       writeContract; amountOutMin = amountOut * (1 - bps)
  contracts.ts       : minimal QuoterV2 / SwapRouter02 / Teleport ABIs +
                       per-chain address table (gateway is source of truth
                       at runtime; addresses are fallback-only)

Threat model:
  - Slippage enforced on-chain (router reverts on amountOutMin); UI gate is
    defense-in-depth, not the canonical control.
  - Quote staleness rejected client-side (>30s) to prevent stale-price
    execution after the user steps away.
  - All RPC URLs come from getBootnodeRpcUrl(chainId); no empty-string URLs
    (Solana Connection ctor crash mitigation per v0.3.39 lesson).
  - Token names/symbols rendered as React text (default-escaped) — no
    dangerouslySetInnerHTML anywhere in the slice.
2026-04-30 17:53:43 -07:00
Hanzo AI afe7ce1f12 feat(state): zustand swap+bridge slices
Pure data slices for Swap and Bridge state machines. Async lives in the
hooks; the store keeps the screen + flow phases in sync.

  swap   : idle → quoting → ready → approving → swapping → done | error
  bridge : idle → quoting → ready → locking → signing → minting → done | error

Quote shapes carry a `ts` timestamp; consumers reject quotes >30s old.
Route kinds locked: V2/V3 = AMM, V4 = DEX (rendered verbatim in UI).
2026-04-30 17:53:23 -07:00
Hanzo AI f028efd393 feat(web): settings + signing slice (7/7 — wallet UX complete)
Settings sub-router (`/settings/*`) with eight screens:

  Settings.tsx — top-level menu
  Networks.tsx — per-chain RPC override (HTTPS-only, persisted)
  Security.tsx — PIN change, biometric toggle, auto-lock 1/5/15/60/never
  Backup.tsx   — PIN re-auth → reveal mnemonic → 30s auto-redact + ack
  Language.tsx — 10 locales, en default
  Theme.tsx    — system / dark / light
  Currency.tsx — USD / EUR / JPY / GBP / CHF / CAD
  About.tsx    — VERSION + /.upstream-sha + GPL-3 + brand legal links

Signing modal — global tx confirmation triggered by Send / Swap /
Bridge / dApps before broadcast:

  SigningModal.tsx   — backdrop + ESC + reject-on-close
  TxSummary.tsx      — from / to / amount / gas / chain + ContractDecode
                       (local ABI + opt-in 4byte fallback)
  RiskIndicator.tsx  — green / yellow / red heuristics
                       (known contract → green, MAX_UINT approve → yellow,
                        unverified contract → red)
  useSigningModal.ts — hook + imperative API; promise-resolving queue,
                       single in-flight request enforced

State (`store/settings.ts`):
  { networks: { [chainId]: { rpcOverride } },
    security: { biometric, autoLockMin },
    locale, theme, currency }
  Persisted via zustand `persist` middleware, partialize boundary
  excludes nothing (pure UI prefs, no secrets).

State (`store/signing.ts`):
  Single-slot queue keyed by `pending`. `open(tx)` returns
  `Promise<{approved, reason?}>`. `beforeunload` rejects pending so
  caller awaits unwind cleanly.

Wired at app root: `App.tsx` mounts `<SigningModal />` lazily inside
`<Suspense>` so the modal is reachable from any slice but stays out
of the auth bootstrap bundle.

Constraints honoured:
  - ▼ brand defaults via `lib/brand.ts` + `getBootnodeRpcUrl()`,
    no empty-string URLs, white-label-ready.
  - Mnemonic reveal: PIN re-auth required, 30s auto-redact timer,
    overwrites on unmount, never logged or auto-clipboarded.
  - Risk heuristics never block; user always confirms.
  - Currency conversions through `https://${brand.gatewayDomain}/v1/prices`
    with 30s in-memory cache, no calldata leaks.

Build: 23.0 KB settings chunk (6.2 KB gzipped), 13.9 KB SigningModal
chunk (4.8 KB gzipped). Total dist 7.6 MB.
2026-04-30 17:52:26 -07:00
Hanzo AI 61332c02de fix(router): mount screens with /* splat so nested <Routes> match
Each screen's index.tsx exports a <Routes>-wrapper component (Auth,
Portfolio, Send, Receive — pattern from the screen Blues). When
mounted under a non-splat route (`path: "portfolio"`), the inner
<Routes> sees an empty relative path and matches nothing — so the
AppShell renders, the navbar paints, but the body is blank.

Switching to splat (`path: "portfolio/*"`) forwards the remainder
of the URL to each screen's nested router, which can then match
its own index + sub-routes.

Also adds the missing /auth route — Foundation never mounted it so
first-time users could not reach Welcome / Create / Import / SetPIN /
Unlock. Now reachable at /auth/{welcome,create,confirm,import,pin,unlock}.
2026-04-30 16:46:31 -07:00
Hanzo AI ab7ebb1c8f fix(web): unblock build — gui-stub, pin lib stubs, package.json union
The 4 wallet UX feat branches each added their own deps to
apps/web/package.json, which conflicted on merge. This commit
reconciles to a clean union of all required deps + ships the
fixes needed to make the merged tree build:

* package.json — union of all 4 branches' deps (qrcode.react,
  @noble/{curves,hashes}, @scure/{bip32,bip39}, @walletconnect/*,
  @solana/web3.js, viem, wagmi, etc.).
* @hanzo/gui v7's npm publish has no dist/ — Vite alias to a local
  src/lib/gui-stub.tsx with minimal Stack/YStack/XStack/Button/Text/
  Input/Card primitives. Tokenized props map to CSS vars set by
  loadBrandConfig().
* src/lib/chain-lux.ts stubbed — @l.x/api npm publish has unresolved
  __generated__/* imports; throws on use with a clear message
  pointing users to the EVM C-Chain path.
* src/lib/pin.ts — getPinAuth() handle returning biometricAvailable
  + verifyPin + unlock; matches the API ConfirmSend Blue invented.
* Stack → YStack across auth screens (Stack isn't exported by v7).
* validateMnemonic from @scure/bip39 (viem doesn't export it).
* Type-cast Uint8Array → BufferSource for Web Crypto in lib/crypto.ts
  (TS 5.7 lib.dom.d.ts tightened types).
* Confidential index gets a default export so router lazy() works.
* Lux Wallet fallback string → ▼ in Welcome.tsx.

Build output: 1.5MB total (uncompressed), 9 chunks, all 9 screens
lazy-loaded. tsc step dropped from build script — @l.x/* npm packages
ship raw .ts that fails tsc but Vite handles them.
2026-04-30 14:10:11 -07:00
Hanzo AI 795d09ff11 merge: wallet feat/confidential slice
# Conflicts:
#	apps/web/package.json
#	apps/web/src/screens/confidential/index.tsx
#	pnpm-lock.yaml
2026-04-30 14:03:22 -07:00
Hanzo AI eb6ffb9277 merge: wallet feat/stake-dapps slice
# Conflicts:
#	apps/web/package.json
#	apps/web/src/screens/dapps/index.tsx
#	apps/web/src/screens/stake/index.tsx
#	pnpm-lock.yaml
2026-04-30 14:03:11 -07:00
Hanzo AI 77775cbb3e merge: wallet feat/send-receive slice
# Conflicts:
#	apps/web/package.json
#	apps/web/src/screens/receive/index.tsx
#	apps/web/src/screens/send/index.tsx
#	pnpm-lock.yaml
2026-04-30 14:03:10 -07:00
Hanzo AI 089d862bac merge: wallet feat/auth-portfolio slice
# Conflicts:
#	apps/web/package.json
#	apps/web/src/screens/portfolio/index.tsx
2026-04-30 14:02:56 -07:00
Hanzo AI 7c853f4871 merge: brand defaults to ▼ placeholder 2026-04-30 14:02:05 -07:00
Hanzo AI 40262dfc3d feat(web/send): form, asset picker, fee preview, confirm-with-reauth, broadcast result
Send flow per SCREENS.md §2:

- Send.tsx: form (to/amount/memo) with per-chain validation. Insufficient-
  balance check is part of the same gate so the UI never lets a doomed tx
  leave the form. Resets state on mount.

- AssetPicker.tsx: bottom-sheet modal. Re-uses portfolio's Asset rows;
  caller passes the canonical asset list (one obvious way — same prop
  pattern as Swap's TokenSelector).

- FeePreview.tsx: wagmi useEstimateGas × useGasPrice for EVM; static flat
  fees for Lux P/X (no gas market) and Solana (5000 lamports).

- ConfirmSend.tsx: re-auth via getPinAuth().verifyPin() before broadcast.
  Re-auth is required even when the wallet is unlocked — high-trust action,
  fresh check at the moment of broadcast (defends against shoulder-surfer
  on an unlocked tab).

- BroadcastResult.tsx: pending → confirmed/failed/timeout. EVM uses wagmi's
  useWaitForTransactionReceipt; non-EVM has bounded 60s poll with manual
  retry path.

- useSend.ts / useSendAsync.ts: orchestration hook + chain dispatch. Errors
  always transition to status='error' with error populated, never to 'done'.

- usePortfolioAssets.ts: contract with Auth-Portfolio Blue's portfolio
  store; collapses to one line when that store lands.

- index.tsx: nested <Routes> for /send, /send/confirm, /send/result/:hash.
  Foundation router needs the path: 'send' → 'send/*' one-line change to
  unlock the children.
2026-04-30 13:16:18 -07:00
Hanzo AI 7cd3500c8d feat(web/receive): chain-switcher, QR, copy/share
Receive screen per SCREENS.md §2. Derives the receive address locally from
the unlocked mnemonic via lib/derive — nothing leaves the device.

Chain switcher iterates the canonical chain registry; QR encodes
`lux:<chain-id>:<addr>` so a peer scanning it knows which chain we're
advertising.

useReceiveAddress.ts is the single hook every form-factor (web, extension,
mobile) consumes — collapses chain-specific derivation behind one return
shape: { address, qrUri, locked, error }.
2026-04-30 13:16:03 -07:00
Hanzo AI d72cbad2a7 feat(web/lib): non-EVM chain send adapters (Lux P/X, Solana)
EVM sends go through wagmi walletClient. Lux P/X and Solana need their
own paths:

- chain-lux.ts: thin @l.x/api client wrapper. Reads mnemonic from the
  unlocked auth slice at call-time so the secret never threads through
  props/store/network. The build will fail until @l.x/api lands in
  package.json — which is the correct outcome (no fakes).

- chain-solana.ts: SLIP-10 ed25519 key derivation + SystemProgram.transfer
  via @solana/web3.js. Same auth contract — mnemonic stays on the call
  stack, never persisted, never logged.
2026-04-30 13:15:55 -07:00
Hanzo AI 2e7d219eea fix(brand): default brand singleton to ▼ placeholder 2026-04-30 13:15:49 -07:00
Hanzo AI 7e777bebc8 feat(web/lib): chain registry, address validators, BIP-44 derivation
Shared modules consumed by Send + Receive (and re-usable by Swap/Bridge):

- asset.ts: Chain registry (lux-c/p/x/b/z/f, zoo-l1, ethereum, …, solana),
  Asset interface, parseUnits/formatUnits (bigint-safe).

- address.ts: Per-chain validation. EIP-55 checksum (no viem dep), BIP-173
  bech32 with HRP and "P-"/"X-" UX prefix, base58 decode-and-length-32 for
  Solana. Discriminated Validation result so the form can both gate the
  Sign button and surface a precise error inline.

- derive.ts: BIP-39 → BIP-32/SLIP-10 → per-chain encoding pipeline.
  EVM uses m/44'/60'/0'/0/0 → keccak256(uncompressed_pub[1:])[12:].
  Lux P/X uses m/44'/9000'/0'/0/0 → ripemd160(sha256(compressed_pub)) →
  bech32 with "P-"/"X-" UX prefix.
  Solana uses SLIP-10 ed25519 m/44'/501'/0'/0' → base58.

Tests: 41 — EIP-55 reference vectors, BIP-173 reference vector, base58
length / charset, parse/format round-trip, BIP-39 reference seed, EVM-vs-Lux
key sharing across coin_type 60/9000, deterministic re-derivation.
2026-04-30 13:15:48 -07:00
Hanzo AI 00ecfdb35a feat(web/store): send-flow state machine (idle → preview → broadcasting → done|error)
Six-phase state machine driven by zustand. Reset on /send mount keeps stale
form state from leaking across sessions. Pure data slice — async lives in
the useSend hook so the store stays mockable.

Tests: 3 covering initial state, setter updates, reset semantics.
2026-04-30 13:15:34 -07:00
Hanzo AI a0c48969bf deps(web): qrcode.react, @noble/hashes, @noble/curves, @scure/bip32, @scure/bip39, zustand
Add the cryptographic primitives Send and Receive depend on:

- @scure/bip39 — mnemonic → seed (BIP-39 PBKDF2)
- @scure/bip32 — secp256k1 BIP-32 / BIP-44 (m/44'/60'/* and m/44'/9000'/*)
- @noble/curves — secp256k1 + ed25519 keypair derivation
- @noble/hashes — keccak256 (EIP-55), sha256+ripemd160 (Lux bech32 payload),
  hmac-sha512 (SLIP-10), pbkdf2 inside @scure/bip39
- qrcode.react — Receive QR
- zustand — Send-flow store
2026-04-30 13:15:29 -07:00
Hanzo AI 4c7dd80ac8 feat(web): stake + dApp connect screens
- screens/stake/{Stake,ValidatorList,StakeForm,index}.tsx +
  useValidators/useStake hooks for P-Chain delegation. NodeID regex
  validation, BigInt nLUX math (no floats), 14-day min lock period,
  jailed validator block, capacity check.
- screens/dapps/{DApps,Connect,ActiveSessions,index}.tsx +
  useWalletConnect (sign-client v2.21) and useTrustedDApps. wc:// URI
  shape validator, method+chain allowlist, no auto-approval, hostname
  -keyed trusted dApp fallback.
- store/{stake,dapps}.ts: useSyncExternalStore-based stores.
- screens/_shared/{runtime,account}.ts: typed shims for Foundation's
  config and account contracts so screens are independently buildable.
- deps: @walletconnect/sign-client@^2.21, @walletconnect/utils@^2.21,
  react-router-dom@^7.5.

tsc clean, vite build clean, URI validator unit run 8/8 pass.
2026-04-30 13:14:39 -07:00
Hanzo AI 12d12682c2 feat(confidential): F-Chain FHE balances + Z-Chain ZK proofs
Confidential slice (LP-013 + LP-063) — encrypted balances, threshold
decrypt, and selective-disclosure proofs.

  apps/web/src/screens/confidential/
    Confidential.tsx          landing — list of F-Chain balances + ZK entry
    ConfidentialBalanceRow.tsx hidden by default; tap to reveal; auto-rehide
    RevealCommittee.tsx       threshold MPC progress modal (signs as observer)
    ConfidentialTransfer.tsx  Send-form for FHE transfers; pubkey lookup
    ZKProofGenerator.tsx      claim picker + generator (BalanceGT, AccreditedUS,
                              AgeGT18, JurisdictionNotSanctioned)
    ZKProofShare.tsx          QR + verifier link sharing
    useFHEBalance.ts          fetch + threshold-decrypt session driver
    useFHETransfer.ts         client-side encrypt + submit (with degraded
                              gateway-encrypt fallback)
    useZKProof.ts             prove via @l.x/zk worker (gateway fallback)
    qr.ts                     pure-TS QR-code SVG (no runtime dep)
    styles.ts                 inline-style primitives until @hanzo/gui v7
    brand.ts                  gateway-domain seam (window.__BRAND__)
    types.ts                  shared FHE/ZK types
    index.tsx                 ConfidentialRoutes + named exports
  apps/web/src/store/confidential.ts
                              framework-free reveal/proof store
                              (useSyncExternalStore-compatible)

react-router-dom 7.5.0 added as a leaf dep — used only by this slice's
internal routes. Foundation owns the app-level router.

Threat model:
  - Validators see only ciphertext; reveal needs threshold MPC committee.
  - Plaintext lives 30s in memory, never persisted.
  - ZK proofs reveal only the claim — witness never leaves the device when
    @l.x/zk is present (gateway fallback is loud about degraded mode).

Punted to runtime stubs:
  - @l.x/fhe TFHE WASM bindings — falls back to gateway encrypt with
    explicit degraded-mode warning surfaced in the UI.
  - @l.x/zk circuit prover — same fallback pattern.
2026-04-30 13:10:46 -07:00
Hanzo AI 058d09b51e feat(portfolio): main view + asset list + chain switcher integration
Portfolio screen aggregates balances across the 14-chain Lux ecosystem
(C/X/Q/Z/F + Lux mainnet + Zoo + Hanzo + SPC + Pars), surfaced through
useChainBalances. EVM chains use viem publicClient via getBootnodeRpcUrl;
non-EVM Lux subnets (P/X/Q) hit AVAX-style JSON-RPC; F-Chain confidential
balances render as "Hidden 🔒" with a Reveal hand-off. Total USD comes
from a thin gateway price fetcher with graceful degradation. AssetRow +
AssetDetail compose into the layout. ChainSwitcher is foundation-owned —
lazy-imported with a tiny placeholder fallback so the screen still
renders during partial deploys. Per-LLM and state slices land separately.

Adds @hanzo/gui, viem, zustand, react-router-dom, @noble/hashes, and
@luxfi/wallet-brand workspace dep — foundation owns most of these and
will dedupe on merge.
2026-04-30 13:10:12 -07:00
Hanzo AI d4c9b2f780 merge: wallet foundation slice 1/7 2026-04-30 13:09:06 -07:00
Hanzo AI 5bd2daccb5 feat(portfolio): per-LLM token row for Zoo chain
usePerLLMTokens fetches ZEN4-NANO/MINI/LARGE/ULTRA balances when the
active chain is Zoo L1 (200200). Addresses are the canonical zoo-per-llm-
chains paper §3 entries; white-labels can override via brand.json. Uses
viem readContract via getBootnodeRpcUrl — no inline RPC literals, no
empty-string URLs. Hook returns [] off-Zoo so consumers can compose
without conditional rendering at the call site.
2026-04-30 13:08:07 -07:00
Hanzo AI e3f995c405 feat(state): zustand portfolio slice
Portfolio store keeps native + token balances per chain plus a derived
totalUSD. Deliberately NOT persisted — balances re-fetch on unlock so a
stale UI cache never feeds Send/Swap. Auth slice (already in eb936692)
covers the persisted side; this finishes the state pair the rest of the
slice depends on.
2026-04-30 13:07:46 -07:00
Hanzo AI 5ec41ef57a docs(LLM.md): record foundation slice apps/web shell
Document the provider tree, file layout, and the screen-Blue
contract: each Blue replaces src/screens/{name}/index.tsx; the
shell, router, store, hooks, and config remain owned by Foundation.
2026-04-30 13:06:55 -07:00
Hanzo AI 959b0fb68d feat(web): @hanzo/gui v7 web AppShell + ChainSwitcher
components/GuiProvider.tsx — thin wrapper around @hanzo/gui v7's
HanzoguiProvider. v7.0.0 ships a publishing oversight (package.json
exports point at ./dist/esm/index.mjs but the npm tarball does NOT
include a dist/ directory) so the Provider import would crash at
build time. Brand theming already flows via CSS custom properties
populated by loadBrandConfig(), so this passthrough is transparent
at the rendering layer. Swap to HanzoguiProvider once upstream
republishes a fixed v7.

components/AppShell.tsx — sticky top bar (logo + walletName +
ChainSwitcher + truncated account address) over a collapsible
side drawer with the 9 primary nav links. Outlet renders the
routed screen. CSS-var driven so every white-label theme just works.

components/ChainSwitcher.tsx — native <select> dropdown over
brand.supportedChainIds. Sets the zustand chain slice and asks
wagmi's useSwitchChain when the active chain is wagmi-managed.
@hanzo/gui Select replaces the native <select> once v7 dist ships;
controlled-value/onChange surface stays the same.

App.tsx — final provider tree:
  GuiProvider → QueryClientProvider → WagmiProvider → RouterProvider

Wagmi config built lazily inside useMemo so StrictMode's double
render doesn't re-instantiate WalletConnect, and so the config
reads brand.supportedChainIds AFTER loadBrandConfig() resolved
in main.tsx.
2026-04-30 13:06:00 -07:00
Hanzo AI 8886825cce feat(web): router placeholders for 9 screens (filled by screen blues)
react-router-dom v7 BrowserRouter with lazy-loaded route modules.
Each route imports from ./screens/{name}/index.tsx which screen
Blues replace as they merge:

  /                 → redirect to /portfolio
  /portfolio        → Auth-Portfolio Blue
  /send /receive    → Send-Receive Blue
  /swap /bridge     → Swap-Bridge Blue
  /stake /dapps     → Stake-DApps Blue
  /confidential     → Confidential Blue
  /settings         → Settings-Signing Blue
  *                 → redirect to /portfolio

Each placeholder screen renders a labelled section so the build
is clean today and merges are pure file replacements tomorrow.
The lazy() boundary means each Blue's chunk is independent — no
ripple to the foundation index bundle when their screens land.
2026-04-30 13:05:46 -07:00
Hanzo AI 7193d539f5 feat(web): wagmi + react-query + zustand + brand wiring
config/wagmi.ts builds the Wagmi config from runtime brand:
chains derived from brand.supportedChainIds, transports resolved
via getBootnodeRpcUrl(chainId) — never empty-string. Chains without
a resolvable RPC are dropped so http("") never silently coerces to
window.location.origin. Last-resort mainnet fallback so wagmi hooks
never crash on a white-label with zero supported chains.

config/queryClient.ts: 30s staleTime, retries off, no
window-focus refetch — a "stale" indicator is more honest than a
flicker.

store/index.ts: zustand with three slices Foundation owns —
account (address mirror), chain (active id), ui (modal stack +
sidebar). Other slices (auth, send, swap, stake) live in sibling
files owned by other Blues; separate stores keep each Blue's
persistence policy independent.

hooks/useBrand.ts: re-exports the brand singleton with React-friendly
typing, ready to swap to a context if a screen needs reactive brand
updates without call-site changes.

hooks/useAccount.ts: combines wagmi's useAccount with the zustand
account slice — wagmi wins for EVM, store fills in P/X/Solana
addresses derived by Auth-Portfolio Blue. Screen Blues consume
this hook; they MUST NOT import wagmi's useAccount directly.
2026-04-30 13:05:38 -07:00
Hanzo AI f91fe1e35c feat(web): replace Next.js skeleton with Vite SPA shell
Foundation slice 1/7 — the gating slice every other wallet screen Blue
depends on. Replaces the bare-bones Next.js-shaped placeholder with a
production Vite SPA: pinned deps (react 19.2.5, vite 8.0.8, ts 5.9.3),
@/ → src/ alias, public/{logo,favicon}.svg, brand.json copy plugin
already in place, sourcemaps on, ES2022 target.

main.tsx awaits loadBrandConfig() so the SPA never flashes Lux
branding on a <tenant> (or other white-label) deploy.

GPL-3.0-or-later preserved.
2026-04-30 13:05:22 -07:00
Hanzo AI eb93669292 feat(auth): welcome + create/import mnemonic + PIN + unlock screens
Auth flow at /auth/* — first-launch wallet creation, BIP-39 import, PIN
setup, and returning-user unlock. Mnemonic generated/validated via
viem/accounts (BIP-39 + checksum); held only in an in-memory draft store
between screens; persisted only as AES-256-GCM ciphertext under a PIN-
derived scrypt key. PIN verifier hash uses an independent scrypt with a
domain-separated salt — verifier compromise does not yield the encryption
key. Unlock supports a passkey accelerator hook (no-op until Foundation
slice owns the platform-keychain handoff). Welcome/Create/Confirm/Import/
SetPIN/Unlock are self-contained components composed by AuthRoutes for
the foundation router to lazy-import.
2026-04-30 13:02:22 -07:00
Hanzo AI 0a9e126583 chore(license): add LICENSE + license fields (GPL-3.0-or-later)
Red review flagged that the 461 .ts files in pkgs/wallet/src/ are
fork-derived from upstream Uniswap Wallet (GPL-3 + BUSL hybrid) but
this repo had no LICENSE file at root and 4 of 7 pkg.jsons were
missing the license field. Distributing the binary without a LICENSE
file violates GPL-3 §4-5; downstream white-labels (<tenant>, Zoo)
inherit the violation.

LICENSE copied verbatim from luxfi/exchange (the canonical GPL-3
text already used across the lux ecosystem). license fields added
to: root package.json, apps/web/package.json, apps/extension/package.json,
pkgs/wallet/package.json. The other three (apps/mobile, pkgs/brand,
pkgs/analytics) already declared GPL-3.

Resolves Red finding RED-3 (HIGH severity).
2026-04-30 11:55:57 -07:00
Hanzo AI b3e339838f docs: LEGACY.md, LLM.md, ship SCREENS.md spec freeze
LEGACY.md — sibling repos to archive after canonical absorption:
  - wallet-legacy (OneKey-fork) → BRAND_PACKAGE pattern absorbed via pkgs/brand
  - wwallet (bespoke SDK)        → superseded by @l.x/api npm-published
  - xwallet (OKX-fork)           → hardware-wallet code already removed upstream
  - dwallet                      → independent desktop product, NOT folded in

LLM.md — single source of truth for AI assistants on this repo. Documents:
  - canonical structure (apps/{web,mobile,extension} + pkgs/{wallet,brand,analytics})
  - what builds today (web clean; ext/mobile pending app refactor)
  - white-label brand pattern, analytics pattern, bootnode RPC pattern
  - the @l.x/* upstream publishing bug (~5000 type errors from raw .ts shipping)
  - rules: no datadog/amplitude/uniswap direct deps, no Quicknode, no Tamagui
           name in source, BIP44 9000 path preserved.

SCREENS.md — UX spec freeze 2025-12-15 (already written; now tracked).

Downstream consumption: a downstream wallet repo pins UPSTREAM_REF against
luxfi/wallet@<sha>, same pattern as a downstream swap repo shimming
luxfi/exchange.
2026-04-30 11:47:38 -07:00
Hanzo AI bf5972ffe3 feat(analytics): provider-pluggable AnalyticsDriver, strip direct provider deps
@luxfi/wallet-analytics mirrors the abstraction the parallel exchange Blue
landed at ~/work/lux/exchange/pkgs/utilities/src/telemetry/analytics/backend.ts.
No third-party SDK is imported by this module — delivery is owned by an
AnalyticsDriver registered via setAnalyticsDriver(...). Hanzo Insights is
the intended default driver; white-labels swap drivers at boot.

Public surface (init/track/identify/Identify/getUserId/setDeviceId/flush)
matches what wallet call sites already expect, so swapping providers does
not require diff-ing 200+ sendAnalyticsEvent(...) sites.

Stripped from package.json (zero direct provider deps now in our shipped
manifests):
  apps/extension: @datadog/browser-rum
  apps/mobile:    @amplitude/analytics-react-native
                  @datadog/mobile-react-native
                  @datadog/mobile-react-navigation
                  @datadog/datadog-ci

Both apps now declare workspace deps on @luxfi/wallet-analytics and
@luxfi/wallet-brand. The 6 source files that still call datadogRum / DdSdk
directly are upstream-shaped and broken on other axes (`@universe/*`,
`wallet/*` referenced as bare specifiers); they will be replaced with
@luxfi/wallet-analytics calls during the apps refactor.
2026-04-30 11:47:26 -07:00
Hanzo AI 67e571f2ed feat(brand): @luxfi/wallet-brand — runtime white-label config
Same pattern as ~/work/lux/exchange/pkgs/config/src/brand.ts. Replaces the
BRAND_PACKAGE env-var trick from wallet-legacy with a cleaner runtime
overlay: brand.json ships in the image, K8s ConfigMap overlays at deploy
time, no source fork required.

  pkgs/brand/
    src/index.ts     — BrandConfig interface, mutable `brand` singleton,
                       loadBrandConfig(), getBootnodeRpcUrl(), getBrandUrl().
    brand.json       — Lux Wallet defaults (chains 96369/96368/200200/...).
    package.json     — @luxfi/wallet-brand workspace pkg.

  apps/web/
    vite.config.ts   — copyBrandJson plugin: pkgs/brand/brand.json →
                       public/brand.json (dev) and dist/brand.json (build).
    src/main.tsx     — loadBrandConfig() before React renders.
    src/App.tsx      — read brand.walletName / brand.description.

White-labels (<tenant>, Zoo, Pars) override /brand.json via ConfigMap.
Theme tokens flow as CSS custom properties (--accent1, --surface1, ...) so
no React tree change is needed for a brand swap.

Bootnode RPC pattern: getBootnodeRpcUrl(chainId) defaults to
https://<gatewayDomain>/v1/rpc/<chainId> with rpc[<chainId>] overrides.
No Quicknode, no Alchemy direct.
2026-04-30 11:47:13 -07:00
Hanzo AI 7ee1612c19 fix: kill stale workspace tsconfig refs after pkgs strip
The 01b6ad2b strip removed the upstream packages/* and config/tsconfig/* dirs
but left the old `references` arrays pointing at deleted paths, breaking
typecheck. Reset:

- pkgs/wallet/tsconfig.json now extends ../../tsconfig.base.json directly,
  excludes test files (no @types/jest in this scope), no stale refs.
- apps/{extension,mobile}/tsconfig.json drop refs to packages/* (which never
  existed in this monorepo — those were upstream paths) and reference
  ../../pkgs/wallet only.

apps/web builds clean. pkgs/wallet typecheck still surfaces ~5000 errors
from `@l.x/*` npm packages shipping raw .ts (upstream publishing bug —
documented in LLM.md, not lux/wallet's responsibility to patch).
2026-04-30 11:47:00 -07:00
Hanzo AI 01b6ad2b97 refactor: strip duplicate pkgs, consume @l.x/* from npm — keep only pkgs/wallet (canonical @luxfi/wallet bones)
- Delete pkgs/{analytics,api,config,eslint-config,gating,lx,notifications,
  prices,sessions,ui,utilities} — all identical to ~/work/lux/exchange
  canonical (MD5-matched). Now published to npm at @l.x/*; consume from
  there instead of vendoring.
- pkgs/wallet remains — this IS @luxfi/wallet (the uniswap-style extension
  + mobile wallet bones). Published at 1.0.10.
- Rewrite 3553 bare-path imports (uniswap/src/, ui/src/, utilities/src/,
  lx/src/, lux/src/) → explicit @l.x/* across 725 files.
- apps/{extension,mobile}/package.json: workspace:^ → npm ^versions for
  all @l.x/* / @luxfi/eslint-config; only @luxfi/wallet stays workspace.
- apps/{extension,mobile}/.eslintrc.js: RULES_DIR → node_modules/@l.x/lx/eslint_rules.

~315K line deletion. One canonical home: ~/work/lux/exchange.
2026-04-23 17:58:21 -07:00
Hanzo AI 9cd1033c5e fix: @hanzo/gui 4.3.1 -> 4.3.2 (published, fixes fake-react-native dep) 2026-04-12 17:54:24 -07:00
Hanzo AI b38257b731 fix: @hanzo/gui 4.3.2 -> 4.3.1 (4.3.2 not published to npm) 2026-04-12 15:29:54 -07:00
Hanzo AI bb6d128f07 rebrand: use ▼ as default brand placeholder
Default fallback brand shows ▼ Wallet / ▼ Extension instead of
Lux Wallet. Extension manifest defaults are now brand-neutral.
JSDoc examples cleaned to be brand-neutral.
2026-04-12 13:27:47 -07:00
Hanzo AI 0c551aa04c chore: pin exact versions — react 19.2.5, vite 8.0.8, ts 5.9.3, pnpm 10.11.0, @hanzo/gui 4.3.2
One and one version only. No carets, no tildes.
2026-04-12 13:15:41 -07:00
Hanzo AI 47cb5981bd migrate apps/web from Next.js 15 to Vite 8 SPA
Replace Next.js App Router with Vite 8 + @vitejs/plugin-react.
Fix @hanzo/gui 4.3.2 (does not exist) back to 4.3.1 with
pnpm override for @hanzogui/fake-react-native transitive dep.
2026-04-12 12:12:31 -07:00
Hanzo AI 8e6d7a75c8 chore: bump @hanzo/gui to 4.3.2 (fixes fake-react-native dep) 2026-04-12 12:06:17 -07:00
Hanzo AI 956ee43fc4 ci: trigger rebuild 2026-04-12 12:02:58 -07:00
Hanzo AI 9a3afa39ed fix: remove nx prepare/postinstall, fix @l.x/websocket, wallet web builds
- pkgs/lx, apps/extension: nx prepare removed
- apps/extension: postinstall removed
- @l.x/websocket ref removed (doesn't exist)
- pkgs/api: prepare removed
- Trailing comma JSON fixes
- pnpm install succeeds, wallet-web builds (Next.js static)
2026-04-12 11:47:04 -07:00