Commit Graph
81 Commits
Author SHA1 Message Date
zeekay 83e2706fb6 remove Stripe from onramp deep-link test fixtures (Stripe account disabled)
Stripe is no longer an available fiat on-ramp provider. Drop it from the
buy-link provider test fixtures; moonpay+coinbasepay still exercise the
multi-provider parse and case-normalization paths.
2026-06-24 12:34:20 -07:00
zeekay a86efad105 chore(deps): lockfile → @luxwallet/* from npm (no file:) 2026-06-24 11:16:40 -07:00
zeekay 48915e47db chore(deps): @luxwallet/chains + @hanzo/iam file: → published npm 2026-06-24 11:11:51 -07:00
zeekay 9d6c1d9a0c feat(web): chain registry from @luxwallet/chains + IAM login via @hanzo/iam
Chain metadata is now sourced from the canonical @luxwallet/chains registry
(MIT) instead of three duplicated hardcoded maps:
  - new lib/chains.ts adapter: chainLabel / evmChainDef / evmChainIds /
    registryChains, with a UI-only block-explorer overlay.
  - config/wagmi.ts, components/ChainSwitcher.tsx, lib/brand.ts all read it.
  - the wallet now recognises/holds every Lux Bridge chain: Lux C/X/P/Q/Z,
    sovereign L1s (Hanzo/Zoo/Pars/Sparkle Pony), external EVM (Ethereum,
    Arbitrum, Base, Polygon, Optimism, Avalanche) and non-EVM bridge families
    (Bitcoin, Solana, TON, XRP, Polkadot). brand.supportedChainIds still
    narrows the active wagmi/switcher surface per white-label.

IAM account login is delegated to the canonical @hanzo/iam SDK (MIT, HIP-0111
one-way auth) — lib/iam.ts is now a thin brand-aware facade over
@hanzo/iam/browser, keeping its existing surface (beginLogin/completeLogin/
getAccessToken/hasSession/clearSession) so store/session, lib/custody and the
callback route are unchanged. Adds a 'Sign in with <brand>' button to the
Welcome screen (additive, alongside local create/import).

The wallet's own keyring + signing path is untouched: this is the chain
metadata source + IAM account login, not external-wallet-connect.

Deps (file: for now, TODO npm): @luxwallet/chains, @hanzo/iam — both MIT,
zero new GPL.
2026-06-24 09:15:08 -07:00
zeekay 82e0d5512e fix(web): re-sync store chainId to brand.defaultChainId after loadBrandConfig (white-label default chain) 2026-06-23 16:27:05 -07:00
zeekay 3b1ad33433 test(backend): LIVE backend↔MPC custody e2e (not mock)
The not-mock counterpart to internal/api/e2e_test.go (which wires the real
custody adapter to a MOCK lux/mpc). This drives the wallet backend's /v1 API
against a REAL 3-node mpcd ensemble (consensus mode, the production code path)
over the live HTTP /keygen + /sign endpoints.

Path (a) full-OIDC: a local RSA-signed OIDC issuer + the real iam.Verifier
(standard discovery + JWKS + RS256 verify) + real org-scoping (owner claim) +
the real api.Server /v1 handlers + custody.NewMPC pointed at live node0. Proves
the full chain: lux.id verify → org scope → LIVE MPC threshold keygen + sign,
with no plaintext key in any backend response or MPC node log.

Exercises POST /v1/wallets (live keygen → real evm address), GET /v1/wallets,
POST /v1/wallets/{id}/sign (live threshold signature), backend-level idempotency
(same key → same signature), and the missing-idempotencyKey → 400 boundary.

//go:build integration. Builds mpcd from the lux/mpc repo (LUX_MPC_REPO, default
/Users/z/work/lux/mpc); skips cleanly if that repo is absent. Reuses the OIDC
helpers (newIDP/tokenForOrg/do) from api_test.go; stdlib-only ids (no new dep).

Run: go test -tags integration ./internal/api/ -run TestBackendLiveMPC -v
2026-06-23 16:24:27 -07:00
zeekay 611a3d77da docs(LLM): extend App(Wallet) spec — native binaries + download deployables, per-brand chainDefault, live MPC /sign 2026-06-23 16:00:04 -07:00
zeekay 10c926c22a feat(web): white-label custody wiring + per-brand deploy + download page 2026-06-23 15:51:02 -07:00
zeekay 64bd0f67c9 feat(backend): App(Wallet) MPC custody server (Go, no plaintext keys)
Add apps/backend — the wallet's custody server. The load-bearing Front-E gap
(repo was FE-only) is now closed: a wallet account model backed by MPC custody
where the private key is split t-of-n across the MPC nodes (lux →
mpc.lux.network) and NEVER assembled — this service stores only public keys +
addresses.

- internal/iam: lux.id OIDC bearer verification via JWKS (discovery + RS256/
  ES256, alg-confusion-safe — HMAC/none refused). Org from the 'owner' claim.
  golang-jwt/v5 (matches the lux ecosystem), stdlib JWKS fetcher (no extra dep).
- internal/custody: the Custodian port (a value the wallet owns) + HTTP adapter
  to lux/mpc's verified /keygen + /sign wire. Mirrors mpc client.Threshold's
  security contract (idempotency required, org-scoped). PQ-ready: mldsa65 is a
  first-class scheme (the PQ primitives stay in pkgs/wallet/.../pq + consensus —
  not duplicated). No in-process key fallback — unconfigured MPC is an error.
- internal/store: wallet↔org↔addresses (no key field). Org-scoped reads — a
  cross-org get is ErrNotFound (no existence disclosure).
- internal/api: /v1/* (no /api/ prefix). IAM-gated, org-scoped. POST/GET
  /v1/wallets, GET /v1/wallets/{id}, POST /v1/wallets/{id}/sign, GET /v1/health.
- cmd/wallet-backend: env config (white-label per tenant: IAM_ISSUER/AUDIENCE,
  MPC_ENDPOINT — shared hanzo-mpc or BYOMPC), JSON logs, graceful shutdown.

Deploy (native CI + operator, NO GHA): build-only .platform.yml → ghcr.io/
luxfi/wallet-backend on lux-build pools; lux operator rolls
apps/backend/k8s/wallet-backend.yaml (lux.cloud/v1 Service + KMSSecret for
MPC_SERVICE_TOKEN), ingress wallet-api.lux.network.

17 tests green (go test ./...): IAM (valid/no-org/wrong-aud/expired/alg-
confusion), custody (no-key-leak, idempotency-required, PQ-accepted, no-
fallback), API (auth-gate, org-isolation cross-org→404, create→sign), + a full
E2E through the real adapter→mock-MPC→real-server. Binary boots, /v1/health ok,
/v1/wallets 401 unauth. LLM.md captures the App(Wallet) spec for the unified
operator's App Kind.
2026-06-23 15:07:40 -07:00
Antje WorringandHanzo Dev e291c38b74 docs: tidy LLM.md indexes; CLAUDE.md -> LLM.md symlink convention
Co-authored-by: Hanzo Dev <dev@hanzo.ai>
2026-06-17 09:56:30 -07:00
zeekay fc08afc683 corona→corona: academic Corona now only in lp-220-p3q-corona 2026-06-11 10:28:44 -07:00
zeekay 0cc382741c scrub Liquidity branding + corona→corona (OSS brand hygiene) 2026-06-11 00:01:24 -07:00
Hanzo Devandzeekay aaae4e0278 scrub Avalanche naming residue: comment rename in non-EVM chain balance helper 2026-06-10 22:35:41 -07:00
e00da8f14f Add agent project docs (CLAUDE.md, LLM.md)
Co-authored-by: Hanzo Dev <dev@hanzo.ai>
2026-06-10 22:35:41 -07:00
Hanzo Devandzeekay 5d7a45aaec scrub: subnet/l2 → chain (canonical vocabulary, forward-only)
Wire-format codec IDs unchanged. CLI aliases deleted; chain is the
command. No backwards-compat shims, no deprecation comments.
2026-06-10 22:35:41 -07:00
Hanzo Devandzeekay 5daf0e5517 brand: rip <tenant> reference from chain comments (no crossover) 2026-06-10 22:35:41 -07:00
Hanzo AIandzeekay 111b7f6276 wallet/pq: concrete ML-DSA / ML-KEM / SLH-DSA + hybrid + HD + precompile encoders
Wires the canonical post-quantum stack per LP-4200 into the wallet:

  • mldsa.ts    — ML-DSA-44/65/87 providers (FIPS 204) over @noble/post-quantum
  • mlkem.ts    — ML-KEM-512/768/1024 KEM providers (FIPS 203)
  • slhdsa.ts   — SLH-DSA-128f/192f/192s/256f providers (FIPS 205)
  • hybrid.ts   — secp256k1∥ML-DSA + Ed25519∥ML-DSA containers; wire format
                  byte-compatible with luxfi/sdk wallet/keychain PQSigner
  • hdPq.ts     — BIP-32 → cSHAKE expandChildSeed → ML-DSA/SLH-DSA keypair
  • domain.ts   — FIPS-204 §5.4 ctx strings:
                    lux-evm-precompile-mldsa-v1   (0x012202)
                    lux-x-chain-utxo-v1
                    lux-p-chain-platform-mldsa-v1
                    lux-recovery-slhdsa-v1        (0x012203)
                    lux-handshake-mlkem-v1
  • precompile.ts — eth_call encoders for 0x012201/02/03 LP-4200 verifiers

The pkgs/wallet/pq surface decomplects four concerns that were previously
tangled in luxfi/sdk's PQSigner: scheme dispatch (providerFor), domain
separation (contextBytesFor), hybrid composition (encode/decodeHybrid),
HD derivation (derivePQIdentity). Each module is independently complete;
the index re-exports them as a single PQ public API.

Web facade lives at apps/web/src/lib/pq.ts; usePQIdentity() hook derives
the canonical ML-DSA-65 identity at m/44'/9000'/<chainID>'/0'/0'/0' from
the unlocked auth-slice mnemonic.

Tests: 71 passing across 8 suites
  domain: 4   mldsa: 11   mlkem: 7   slhdsa: 6
  hybrid: 9   hdPq: 9     precompile: 8   pqAccount: 17 (pre-existing)

Test runner: jest.pq.config.js (standalone — bypasses upstream-shaped
jest-expo preset still pending refactor; see LLM.md).

Domain separators pinned by domain.test.ts; cSHAKE customizations pinned
by pqAccount.test.ts (pre-existing). Replay-across-chains explicitly
tested via mldsa.test.ts and hybrid.test.ts cross-ctx verify cases.
2026-06-10 22:35:41 -07:00
Hanzo Devandzeekay efdbc6a717 docs: add LICENSING.md pointing at canonical Lux IP strategy
Single LICENSING.md file referencing the canonical three-tier IP and
licensing strategy at github.com/luxfi/.github/blob/main/profile/README.md.

LICENSE file is unchanged; this only adds a navigational pointer.
2026-06-10 22:35:41 -07:00
Hanzo AIandzeekay e85de16ef5 chore(wallet): bump @luxfi/wallet to 1.0.11
Ships PQAccount TS mirror (cSHAKE-256 AccountID derivation +
HD-path formatters) added in #3.
2026-06-10 22:35:41 -07:00
Hanzo AIandzeekay 8cd7edb960 fix(wallet/pq): satisfy --strict exhaustive-switch narrowing
The switch in walletSchemeName covers every WalletSchemeID enum
value, so TypeScript --strict narrows the default arm's `s` to
`never` and rejects `s.toString(16)`. Cast through `number` for
the forward-compat fallback path.

No behaviour change; KAT vector for AccountID derivation
(chainID=9000, scheme=0x42) still matches the Go side at
76a03630148103ec558cf4d8f7e8a2d8766ea205d96a4529249c3aaf9c5d078cc9f9fdb8f21a5e7ef6bb4c4ea29c9654.
2026-06-10 22:35:41 -07:00
Hanzo AIandzeekay d3807df45d wallet/pq: PQAccount TS mirror of luxfi/sdk wallet/account
TypeScript module under pkgs/wallet/src/features/wallet/pq/ that
mirrors the PQAccount shape, the cSHAKE-256 AccountID derivation, the
HD-path formatters, and the role/customization mappings defined on the
Go side in github.com/luxfi/sdk/wallet/account.

The TS side intentionally does NOT pull in an ML-DSA-65 implementation.
ML-DSA itself is software-in-browser; the cost of bundling FIPS 204 is
not worth paying for read-only consumers (UI list views, route
handlers). Consumers that need to sign in-browser supply a concrete
MLDSAProvider implementation (typically backed by @noble/post-quantum
mlDsa65 or a hardware-wallet bridge). Browser keys are session-scoped;
long-lived keys MUST live in a native wallet.

Wire compatibility: AccountID KAT cross-checked against the Go side.
For the canonical vector (chainID=9000, scheme=0x42, pubkey="test-
pubkey-for-accountid-kat-vector-stable-across-runs") both
implementations produce
76a03630148103ec558cf4d8f7e8a2d8766ea205d96a4529249c3aaf9c5d078cc9f9fdb8f21a5e7ef6bb4c4ea29c9654.

Adds @noble/hashes ^1.7.1 to pkgs/wallet dependencies (already
transitively present via @scure/bip32; declaration is for strict
node_modules resolution). pnpm-lock.yaml refresh on next install.

Patch-bump.
2026-06-10 22:35:41 -07:00
Hanzo AIandzeekay 9e53916781 chore: scrub uniswap — use @luxamm/* (lux fork) only
Replace every @uniswap/* package import with @luxamm/* (lux fork
already canonical for SDK pieces). Replace app.uniswap.org URLs with
lux.exchange, uniswap.org with lux.network. Replace brand strings
(Uniswap Wallet → Lux Wallet, Uniswap Extension → Lux Extension,
Uniswap Labs → Lux Labs Inc.) where they appear in user-facing UI.
Rename handleUniswapAppDeepLink → handleLuxAppDeepLink, also rename
focusOrCreateUniswapInterfaceTab → focusOrCreateLuxInterfaceTab.

Manifest host_permissions updated to lux.exchange and *.lux.network.

Lockfile regeneration: TODO — run pnpm install to refresh after this
commit. Transitive @uniswap/* references in pnpm-lock.yaml are
upstream-package internals.

Protocol-level identifiers (UniswapMethodHandler, UniswapMethods,
UniswapOpenSidebarRequest, handleUniswapX, UniswapXOrderDetails) are
preserved because they reference wire-protocol method names
(uniswap_openSidebar) and external SDK type exports — renaming would
break upstream contracts. iOS Uniswap target directory and
uniswapteam.slack.com / github.com/Uniswap/universe doc refs are
out-of-scope per directive (separate native-build cleanup).

Same hard-banishment rule as tamagui (per feedback memory).
2026-06-10 22:35:41 -07:00
Hanzo AIandzeekay dc12b4deed fix(extension): add missing getTsconfigAliases import + publicAssetsVariant const
wxt.config.ts referenced both but neither was imported/defined.
- Import getTsconfigAliases from ./config/getTsconfigAliases
- Define publicAssetsVariant from WXT_PUBLIC_ASSETS_VARIANT env (default: prod)

Without these, wxt build fails: getTsconfigAliases is not defined.
2026-06-10 22:35:40 -07:00
Hanzo AIandzeekay 2b7990fa0c fix(extension): add missing vite-plugin-svgr + commonjs + node-polyfills + tsconfig-paths
wxt.config.ts imports these but they were never declared in package.json
(probably stripped along with tamagui cleanup or a prior cleanup pass).
Without them, `pnpm exec wxt build` fails: Cannot find module 'vite-plugin-svgr'.
2026-06-10 22:35:40 -07:00
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