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.
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.
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
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.
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.
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.
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.
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).
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.
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'.
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.
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
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.
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.
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
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.
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}.
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.
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.
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 }.
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.
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.
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.