Commit Graph
50 Commits
Author SHA1 Message Date
zeekay 5bd5fab83e feat(pkg/bridge): @hanzo/gui 7.0.0 -> 7.2.2 + dual-name compat (SDK 1.0.5 stable)
User asked to confirm the rebuild is up to date with @hanzo/gui framework
and to run both Vite tenant + Next.js explorer locally to verify. Playwright
caught a regression: bumping @hanzo/gui to 7.2.2 (latest published) made
the bridge tenant render a blank page with 'createGui is not a function'.

Root cause: @hanzo/gui's published dist artefacts use the upstream Tamagui
symbol names verbatim (createHanzogui / HanzoguiProvider / getDefault-
HanzoguiConfig). The consumer-side scripts/patch-hanzogui-exports.cjs
ONLY runs when dist/ is empty — which was true for 7.0.0 (broken
publish) but is false for 7.2.x (real dist). So 7.2.2 exposes the
upstream Hanzogui* names and the bridge SDK code (which calls the
renamed createGui / GuiProvider / getDefaultGuiConfig) finds undefined
exports.

Fix: SDK accepts BOTH naming surfaces. pkg/bridge/src/mount.ts and
Bridge.tsx try the renamed symbol first (works on 7.0.0 + patch), fall
back to the upstream Hanzogui* name (works on 7.2.x dist). Throw a
clear error if neither is exported.

Playwright in-browser verification after the fix:
  Title:           'LUX Bridge'
  #bridge-root:    1 child rendered (Tamagui tree)
  Console errors:  0  (only theme-token warnings, harmless)
  UI rendering:    header (LUX Bridge + Connect Wallet) ✓
                   swap form (FROM Lux Network → TO Ethereum) ✓
                   TRANSFERS panel ✓
                   footer (Network: mainnet · Docs · [email protected]) ✓

Also bumped:
  - @hanzo/gui  ^7.0.0 → ^7.2.2 (latest published)
  - @luxfi/bridge  1.0.4 → 1.0.5

Plus an incidental fix to app/explorer/components/SwapHistory/StatusIcons.tsx
(pre-existing operator-precedence bug: 'a' + b ?? '' had its ?? branch
unreachable; wrapped className ?? '' in parens). Found while attempting
to run the legacy explorer locally; we're deprecating that whole app
per #387 but the type error blocked next dev / next build cold.

Tests:
  - SDK pkg/bridge:   58/58 passing
  - SDK typecheck:    clean
  - app/server:       77/77 passing (unchanged)
2026-05-22 21:18:56 -07:00
zeekay d86ead4f93 feat: Cloud HSM (GCP real, AWS/Azure/Vault stubs) + f-chain native cosigner (#386)
User feedback was that my first Cloud HSM pass had three architectural
weaknesses: (1) per-provider top-level kinds vs a single cloud_hsm
discriminator, (2) SA-JSON-in-KMS auth pattern, (3) missing first-class
algorithm + structured error codes. Plus the orthogonal point that for
bridge.lux.network specifically the right topology is m-chain (native
MPC) + optional f-chain (FHE attestation) and NO external cosigners.

This commit applies all of that.

SDK (@luxfi/bridge 1.0.4 → 1.0.5):

  - BridgeCloudHsmConfig — single, uniform shape:
      provider: 'gcp_kms' | 'aws_kms' | 'azure_key_vault' | 'vault_transit'
      keyRef: <self-describing per-provider URI form>
      algorithm: 'secp256k1_ecdsa_sha256' | 'ed25519' | 'rsa_pss_sha256'
      identityHint?: <non-secret workload-identity scope hint>
    Replaces the three separate gcpKms / awsKms / azureKv blocks from
    the first pass. Tenants layer multiple HSMs via the cloudHsm array
    on BridgeMPCConfig.

  - BridgeFChainConfig — Lux Network native FHE attestation:
      publicUrl: <f-chain cluster URL>
      scheme?: 'ckks' | 'bgv' | 'bfv'
    Lives alongside m-chain on the Lux primary network. Cosigns the
    same txHash that m-chain signed via FHE-secured key material so
    the message never crosses a plaintext threshold quorum. PQ-safe.

  - Wire shape (bridge-api.ts CosignerIntent) collapses the three
    cloud_hsm variants into one canonical record with the provider
    sub-discriminator. Adds the fchain wire kind.

  - useTransfers iterates cfg.mpc.cloudHsm[] + emits the optional
    fchain intent.

Backend (app/server):

  - CloudSigner interface matches user's exact spec:
      readonly provider: CloudHsmProvider
      sign(input: CloudSignInput): Promise<CloudSignResult>
    where CloudSignInput / CloudSignResult carry the structured metadata
    (keyRef, digest, algorithm, identityHint, requestId / signature,
    publicKeyRef, provider, keyVersion, signedAt).

  - CloudSignerError class with stable CloudSignerErrorCode union:
      permission_denied | key_not_found | key_disabled
        → maps to CosignResult.status = 'rejected'
      algorithm_mismatch | invalid_digest
        → maps to 'rejected' (terminal config errors)
      rate_limited | network_error | provider_unavailable
        → maps to 'failed' (retryable infra errors)
    statusForErrorCode() does the mapping; the dispatcher never has to
    string-match on error messages again.

  - Real GCP CKMS adapter:
      ✅ Workload-identity ONLY (no SA-JSON-in-KMS fallback). The
         backend trusts the cloud's native identity layer.
      ✅ identityHint is non-secret context, not credentials.
      ✅ Algorithm allow-list — only secp256k1_ecdsa_sha256 wired today;
         ed25519 / rsa_pss_sha256 reject early with algorithm_mismatch.
      ✅ Error mapping covers gRPC codes 5/7/8/9/14/16 + string
         fallbacks. mapGcpError centralizes it.

  - AWS KMS / Azure Key Vault / Vault Transit adapters are stubs that
    return CloudSignerError 'provider_unavailable' with a clear marker.
    Each stub has a full implementation sketch in comments so the next
    dev (or me, next session) drops in @aws-sdk/client-kms / etc. and
    wires through cleanly.

  - f-chain (FHE attestation) stub — waiting on @luxfi/fchain workspace.

  - Persistence: cloud_hsm rows persist as 'cloud_hsm:<provider>' in
    CosignerStep.kind to avoid a Prisma migration. public_id = key_ref;
    api_host = identity_hint; vault_id = algorithm. rowToIntent inverts
    cleanly. First schema migration round will split into dedicated
    columns; the compound kind is a stable round-trippable hack.

Tests: 77/77 passing.
  pkg/bridge:   58/58 (unchanged)
  app/server:   16 validator (now covers all 4 cloud providers + fchain)
              + 10 Fireblocks
              + 13 Utila
              +  7 persistence
              + 19 GCP KMS + Cloud HSM stubs + fchain
              + 12 new cloud-HSM validator cases

Lux Network topology (bridge.lux.network and all Lux-branded tenants):
  - Primary: m-chain (native CGGMP21 / FROST threshold MPC)
  - Optional: f-chain (FHE attestation, also native to Lux)
  - NO external cosigners (Utila / Fireblocks / Cloud HSM) by default
  - These cosigner blocks exist on BridgeMPCConfig for white-label
    tenants whose policy requires institutional / cloud-HSM cosign.

User correction record:
  > 'Use a provider sub-discriminator, not separate top-level kinds' ✓
  > 'Do not fetch SA JSON from KMS at signing time' ✓
  > 'Keep browser SDK config declarative only' ✓
  > 'Separate SDK config from backend signer runtime config' ✓
  > 'Normalize algorithms explicitly' ✓
  > 'Shared interface should return structured metadata' ✓
  > 'Preserve provider-specific error classification' ✓
  > 'rely on just b-chain and m-chain ... f-chain for FHE' ✓ (fchain
    config is optional + native; external cosigners are opt-in only)
2026-05-22 20:27:02 -07:00
zeekay 5fd07f09b3 feat(pkg/bridge): jurisdiction-neutralize SDK (1.0.3 -> 1.0.4)
User pointed out that @luxfi/brand should be used 'for all lux apps /
projects' — confirming the canonical brand sourcing principle. Audit
pass against the bridge stack found two leaks of Lux-specific identity
INTO the jurisdiction-neutral SDK:

1. `pkg/bridge/package.json` declared `@luxfi/brand` as a direct
   dependency — but the SDK source never imports it. Dead dep and a
   layering violation: Zoo / Hanzo / any white-label consumer of
   `@luxfi/bridge` shouldn't pull Lux brand metadata into their bundle.
   Dropped.

2. `pkg/bridge/src/app/lib/wagmi-config.ts` had a hardcoded SSR
   fallback URL `https://bridge.lux.network` for the WalletConnect
   metadata block. On client-side hydration this is overridden by
   `window.location.origin`, but the first SSR-render frame was
   leaking the Lux hostname into Zoo / Hanzo tenant builds.

   Replaced with `deriveAppOrigin(cfg.apiHost)` — strips the leading
   `api.` subdomain from the configured API host so the fallback is
   tenant-derived. `api.bridge.lux.network` → `bridge.lux.network`,
   `api.bridge.zoo.network` → `bridge.zoo.network`. Catch-all
   fallback is `https://localhost` if the URL fails to parse.

3. `docs/LLM.md` gains a 'Canonical brand sourcing' subsection
   documenting the principle:
   - `@luxfi/brand` (~/work/lux/brand) is the ONLY source of Lux
     brand identity for ALL Lux-ecosystem apps (bridge, exchange,
     id, pay, etc.)
   - SDK is jurisdiction-neutral; tenants supply brand at mount time
   - App-specific endpoints (api.bridge.lux.network) are independent
     of brand.json's appDomain (lux.exchange)
   - Other Lux apps follow the same pattern; any hardcoded brand
     string in a Lux app is a leak — file as a follow-up issue.

SDK 1.0.3 -> 1.0.4. Additive + neutralization only — no functional
behavior change for Lux consumers; Zoo / Hanzo SSR frames now show
their own hostname instead of bridge.lux.network.

Tests: 58/58 SDK unchanged.
2026-05-22 17:41:39 -07:00
zeekay 5f79986acc test(pkg/bridge): 5 cosigner SDK tests — wire-shape + no-secret-leak (issue #390 partial)
Covers the SDK side of the layered cosigner contract while issue #386's
backend implementation is in progress:

  1. Omits cosigners[] entirely when no layered providers configured
     (backwards compat — no behavior change for existing tenants).
  2. Forwards utila intent in snake_case, public IDs only.
  3. Forwards fireblocks intent in snake_case, public IDs only.
  4. Forwards both utila + fireblocks together (belt + suspenders).
  5. Omits optional sub-fields (api_host, vault_id) when unset — no
     'foo: undefined' on the wire.

Each test that forwards an intent also asserts the full request body
contains zero secret-like substrings (private_key, api_secret, jwt,
token, service_account_private_key) — defense in depth against any
future regression where a consumer's bridge.config.ts accidentally
forwards a secret.

Pairs with the 16 server-side validator tests in app/server/src/domain/
__tests__/cosigners.test.ts — together they pin the wire contract from
both ends.

Total SDK tests: 53 → 58. All passing.
2026-05-22 00:26:50 -07:00
zeekay 36c90054d2 feat(pkg/bridge): optional Utila + Fireblocks layered cosigners (1.0.2 -> 1.0.3)
External MPC custodians can now be layered ON TOP of the native Lux
threshold network as additional cosigners. The bridge backend enforces
2-of-2 (native + listed cosigner) before releasing settlement. Use when
tenants are already on Utila or Fireblocks for institutional custody and
need regulated-cosigner gating without giving up the native threshold
property.

Browser-safe by construction: the SDK declares only PUBLIC identifiers
(orgId, clientId, apiKey id, vault ids). The Utila JWT and the
Fireblocks secret key live on app/server/ (sourced from KMS at boot).
No secret material is ever shipped in the page bundle.

Surface:

  BridgeMPCConfig {
    publicUrl?, privateUrl?, protocol?       (existing native MPC)
    utila?:      BridgeUtilaConfig           (new, optional)
    fireblocks?: BridgeFireblocksConfig      (new, optional)
  }

publicUrl is now optional too — pure-external custody (utila/fireblocks
only, no native threshold) is supported. The runMpcSignSession helper
throws if it's called without publicUrl, and useTransfers gates the
client-side sign session on its presence.

Per-swap, the SDK forwards a cosigners[] array to POST /api/swaps
carrying just the public IDs. The backend pairs each entry with its
KMS-held secret half and completes the cosign. Both blocks may be set
together (belt + suspenders) — backend enforces all listed.

Tests: 53/53 passing. Typecheck clean. No new runtime deps in pkg/bridge
(utila/fireblocks SDKs live on the server, not the browser bundle).
2026-05-21 18:40:15 -07:00
zeekay 59f9e78215 pkg/bridge: bump @luxfi/bridge 1.0.1 -> 1.0.2 (Phase 3 R3 — real MPC wallet wiring) 2026-05-21 17:36:00 -07:00
zeekay c4542a08dd pkg/bridge: useTransfers POSTs real swaps + polls server, BridgeApp installs WagmiProvider
useTransfers replaces the setTimeout phase progression with the real
flow:
  1. POST ${cfg.apiHost}/api/swaps with the order (Idempotency-Key
     header set to the local transfer id, server snake_case body).
  2. Subscribe via GET /api/swaps/:id polling at 2s; map the server's
     free-form status string → TransferPhase via statusToPhase()
     (signing/broadcasting/completed/failed detection).
  3. On entry into the signing phase, start an MPC threshold session
     via runMpcSignSession() when the server record carries the
     keyId + messageHash. Progress is patched onto the Transfer.mpc
     field so the UI can render real signer activity.
  4. On unmount or clear(): abort every per-transfer controller so
     polls + MPC sessions stop cleanly.

Destination address defaults to the connected wallet (self-send across
chains is the common case); tenants needing sweep-to-treasury flows
pass an explicit address. New cases marked failed cleanly: no wallet
connected, unsupported chain pair, server hard-error.

submit() kept as a back-compat alias for initiate() so SwapForm.tsx
(off-limits — Phase 3 R2.5 is in parallel on components/) doesn't have
to retouch. The richer destinationAddress field is optional.

BridgeApp.tsx wraps its inner tree in <WagmiProvider> +
<QueryClientProvider>. The Config is built once per mount from
BridgeConfig.wallet via buildWagmiConfig(); the QueryClient defaults
disable refetch-on-focus/reconnect (bridge quotes are request-scoped
and shouldn't refresh behind the user's back).

4 tests cover initiate POST shape, the wallet-not-connected failure
mode, the unsupported-chain-pair failure mode, and clear() aborting
polls.
2026-05-21 17:28:05 -07:00
zeekay e429f20719 pkg/bridge: replace useSwap static rate matrix with real backend quotes
useSwap now hits ${cfg.apiHost}/api/quote (GET, query params per the
server's app/server/src/routes/quote.ts) with a 300ms debounce. Maps
bridge chain ids → server internal_name (ETHEREUM_MAINNET / LUX_MAINNET
/ etc, suffix-swapped for testnet/devnet env). In-flight requests are
aborted via AbortController on every keystroke so the backend isn't
flooded.

New SwapState fields: quoting (boolean) + quoteError (string | null).
quoteError surfaces 5xx + network errors to the UI rather than blanking
the quote silently. AbortError is suppressed (newer keystroke).

The previous static USD-rate matrix is gone — pricing now comes from
the server's getTokenPrice oracle. The Quote shape is preserved
(outAmount/feeUsd/destGas/etaText/rate) so SwapForm doesn't need to
change; minOut is added for slippage protection on submission.

5 tests cover debounce, same-chain noop, invalid input, server error,
and reverse-swap state.

Also flips vitest config to happy-dom so hook tests can mount under
a real DOM-ish environment (jsdom would also work; happy-dom starts
faster on cold init).
2026-05-21 17:28:05 -07:00
zeekay 50ccb1dd23 pkg/bridge: replace useWallet stub with real wagmi-backed wallet hook
useWallet now wraps wagmi's useAccount + useConnect + useDisconnect +
useSignMessage + useSwitchChain. Connector preference: WalletConnect
(when wallet.walletConnectProjectId is supplied) → injected (MetaMask,
Rabby, etc.) → Coinbase Wallet → first available.

Adds disconnect(), switchChain(bridgeId), signMessage(msg) to
WalletState — capabilities the SwapForm submission flow + future MPC
co-signing will need.

DEV fallback preserved: when import.meta.env.DEV is true AND no
connector is registered, the hook still throws a clear error
("install MetaMask, or configure wallet.walletConnectProjectId") rather
than silently faking a connection. The display-only DEADBEEF address
literal is kept only for the gated DEV branch — production builds
tree-shake it out (the gate is statically evaluable).

7 tests cover the wagmi delegation and the EVM-only switchChain guard.
2026-05-21 17:28:05 -07:00
zeekay 6eb9713402 pkg/bridge: add bridge backend API client + MPC threshold session helper
bridge-api.ts wraps the Express bridge server's /api/{quote,swaps,...}
routes (see app/server/src/routes). Pure functions, no React. Errors
throw BridgeApiError({ status, body }) so callers can surface or
retry. chainIdToInternalName maps SDK chain ids → server enum
("evm:1" → "ETHEREUM_MAINNET", suffix-rewritten for testnet/devnet).

mpc-session.ts wraps @luxfi/threshold's ThresholdClient with a
progress-reporting wrapper used by useTransfers to surface real
MPC ceremony state to the UI instead of a setTimeout placeholder.
Protocol defaults to cggmp21 (secp256k1, EVM-compatible); respects
BridgeMPCConfig.protocol when set (pulsar/corona/magnetar for the
post-quantum path).

14 tests across both modules: fetch shape, query/body construction,
error mapping, JSON-RPC happy-path + failure + abort.
2026-05-21 17:28:05 -07:00
zeekay 895f768ac8 pkg/bridge: add wagmi+viem+react-query, build wagmi Config from BridgeConfig.wallet
Pulls in wagmi 2.15.5 (matches lux/exchange/apps/web), viem 2.30.5,
@tanstack/react-query 5.90.20 as direct dependencies of @luxfi/bridge.
Also wires @luxfi/threshold as a workspace dep for the MPC layer used by
useTransfers (separate commit).

New module pkg/bridge/src/app/lib/wagmi-config.ts:
  - buildWagmiConfig(cfg) — pure function, creates a wagmi Config scoped to
    the BridgeApp mount with injected + Coinbase + optional WalletConnect
    connectors (WC only when wallet.walletConnectProjectId is set, so
    Reown's appkit doesn't 403 on missing project id for white-label
    deploys that haven't provisioned one).
  - bridgeIdToWagmiChainId / wagmiChainIdToBridgeId — boundary helpers
    between the SDK's namespaced chain ids ("evm:1") and wagmi's numeric
    ids. Non-EVM bridge ids return null — those legs sign via MPC, not
    wagmi.
  - Chain selection respects wallet.supportedChainIds (filter) and
    wallet.defaultChainId (hoisted to chains[0], wagmi's default for the
    unconnected state).

9 tests cover the config builder + the chain-id boundary mappers.
2026-05-21 17:28:05 -07:00
zeekay 73fa005ef9 pkg/bridge: bootstrap @hanzo/gui runtime + provider in mountBridge
Phase 3 R2 swapped <button>/<input> primitives onto @hanzo/gui's Button
and Input components in SwapForm, WalletConnect, and AssetInput, but the
runtime bootstrap was never added. The library's primitives read from
globalThis.__guiConfig at render time and throw Error("Err0") when null,
and the JSX tree also needs a GuiProvider for theme context — without
both, the SDK mounts to a blank page.

Wire both inside the SDK so consumers don't need to know about them:

  - mount.ts: call createGui(getDefaultGuiConfig()) before React render,
    idempotent (skips when globalThis.__guiConfig already populated).
  - Bridge.tsx: wrap <BridgeApp /> in <GuiProvider defaultTheme="dark">.

@hanzogui/config-default added as a direct dependency (was transitive
only). Imports cast to unknown to bridge the source/types mismatch
introduced by the monorepo's postinstall rename of upstream
createHanzogui/HanzoguiProvider → createGui/GuiProvider on .ts files
but not the shipped .d.ts.
2026-05-21 17:00:40 -07:00
zeekay aba2019111 merge: phase1 SDK config blocks (auth/kms/wallet/mpc + PQ protocol union)
# Conflicts:
#	pnpm-lock.yaml
2026-05-21 16:26:47 -07:00
zeekay 59b227ec55 docs: document the react-native-web alias requirement for @luxfi/bridge consumers (red nit)
Red flagged a P1 documentation gap: downstream tenants of @luxfi/bridge
(starting with zooai/bridge) need the same react-native-web alias +
TAMAGUI_TARGET define that app/bridge/vite.config.ts uses, or Rolldown
fails to resolve react-native from Tamagui's runtime. Adding:

- pkg/bridge/README.md: new "Consuming this SDK from a Vite app" stanza
  with the canonical resolve.alias + dedupe + define block
- app/bridge/vite.config.ts: header note pointing downstream consumers
  at the README

No code changes. No new dependencies.
2026-05-21 16:18:48 -07:00
zeekay 93b0e311ce phase3-r2: swap <button> and <input> primitives onto @hanzo/gui
Mechanical primitive rotation across the inlined bridge UI:

  - 4 <button> sites -> @hanzo/gui Button
    * WalletConnect: Connect Wallet button
    * WalletConnect: Connected address pill (disconnect)
    * SwapForm: Reverse-chains button (the central ⇄)
    * SwapForm: Submit/bridge button

  - 1 <input> site -> @hanzo/gui Input
    * AssetInput: amount field (uses onChangeText, the Tamagui-native
      string handler, instead of the React onChange(e) signature)

Inline-style props (style={...}) are preserved verbatim on the new
components -- they round-trip through Tamagui's web-mode style merge.
Phase 3 R2.5 will lift those into theme tokens.

Out-of-scope per the R2 dispatch:
  - <select> (ChainSelector, AssetInput asset picker) -- stays native
  - Card / Stack / Text / Header / Footer -- still plain HTML
  - title="..." HTML tooltips dropped: Tamagui Button's stack-based
    types do not expose the DOM title attribute. aria-label is
    preserved on every site so screen-reader behavior is unchanged.

Verification:
  - cd pkg/bridge && pnpm typecheck  -> clean
  - cd app/bridge && pnpm build      -> clean (750 modules)
  - Bundle size (app/bridge index chunk): 25.99 kB -> 106.83 kB raw
    (gzip 9.01 -> 38.86 kB). Delta is the Tamagui runtime; expected
    and within the dispatch's "~150 KB acceptable" budget.
2026-05-21 16:10:00 -07:00
zeekay 85dbb8b345 phase3-r2: wire @hanzo/gui as a runtime dependency
The SDK now ships with @hanzo/gui + @hanzogui/{core,helpers,constants,
themes,animate,animate-presence,button,input} as direct dependencies
(moved out of the optional peerDependencies block). Phase 3 R2 starts
swapping native primitives onto Tamagui-based @hanzo/gui components,
so consumers no longer have to install those packages themselves.

Three plumbing changes make this actually bundle in a consumer:

  1. scripts/patch-hanzogui-exports.cjs (vendored from lux/exchange):
     the published @hanzo/[email protected] + @hanzogui/*@7.0.0 packages ship
     without a populated dist/, so their package.json main/module/exports
     point at folders that do not exist. The patch rewrites those entries
     to point at the included src/ at install time. Root postinstall hook
     runs it deterministically on every pnpm install.

  2. app/bridge/vite.config.ts: alias react-native -> react-native-web,
     dedupe react/react-dom/react-native-web, set TAMAGUI_TARGET=web and
     __DEV__=false. Minimum viable Tamagui wiring (mirrors the wiring
     lux/exchange ships in a much larger config).

  3. app/bridge/package.json: add react-native-web@^0.19.0 dependency
     so the alias above resolves.

Result: pnpm install in a fresh checkout produces a working build with
zero manual setup. `pnpm build` in app/bridge succeeds.
2026-05-21 16:09:47 -07:00
zeekay 486b201bb0 pkg/bridge/app/hooks/useWallet: gate stub wallet behind import.meta.env.DEV (red review) 2026-05-21 16:00:46 -07:00
zeekay d07b70d192 feat(pkg/bridge): sever @luxbridge/app workspace cycle (v1.0.1)
Bridge.tsx no longer lazy-imports @luxbridge/app — it imports the
inlined `BridgeApp` from `./app/` directly. The workspace dep on
`@luxbridge/app` is removed from pkg/bridge/package.json, and the
type shim `src/luxbridge-app.d.ts` plus the tsconfig path alias are
gone with it.

Phase 2 produced a 91-byte JS bundle because its slimmed `app/bridge`
package re-exported `App: FC = () => null` to satisfy this lazy
import. With the cycle severed, that workaround is unnecessary and
the SDK's bundle is what a tenant build actually consumes — see the
new app/lux-bridge consumer (next commit) emitting a 25.96 KB chunk
with real `BridgeApp`, `createRoot`, and all UI strings.

package.json deltas:
  - version: 1.0.0 -> 1.0.1
  - dependencies: drop @luxbridge/app + @luxbridge/settings (both
    workspace-only)
  - peerDependencies: add @hanzo/gui ^7 + the @hanzogui/* v7
    primitive set as OPTIONAL peers. Phase 3 R2 will swap the
    inline-styled primitives in src/app/components/ for these; until
    then they're optional so `pnpm add @luxfi/bridge` doesn't drag
    in 30 unused packages.

tsconfig.json: drop the `@luxbridge/app` path mapping.

README.md: replace the workspace-internals diagram with the actual
inline layout under src/app/.
2026-05-21 15:03:38 -07:00
zeekay 9b12b124eb feat(pkg/bridge): inline bridge UI under src/app/
Phase 1.5 — sever the @luxbridge/app workspace cycle that Phase 2
exposed as a P0 blank-page bug. The SDK now ships its own bridge UI
inline; there is no runtime dependency on any sibling tenant package.

Layout:

  pkg/bridge/src/app/
    BridgeApp.tsx           top-level <BridgeApp/>
    components/             Header, SwapForm, ChainSelector, AssetInput,
                            WalletConnect, TransferStatus, Card
    hooks/                  useWallet, useSwap, useTransfers
    lib/                    chains, assets, format
    styles/theme.css        minimal CSS variables (brand-* hooks)

Features covered:
  - Header with brand block + wallet connect
  - Swap form: From/To chain selector, asset input + select, quote
    panel (rate / fee / ETA), reverse button, submit
  - Wallet connect button + connected-address pill
  - Transfer status feed with MPC-phase rendering (pending → signing
    → broadcasting → completed)

Design constraints:
  - PQ-safe: no classical-crypto assumptions anywhere. The MPC signer
    layer (wired in Phase 3 R3) plugs Ringtail-lattice + ECDSA-CMP
    hybrid; nothing here defeats post-quantum.
  - Leaderless / permissionless: chain + asset registries are static
    client-side data, not bound to any central authority. Quotes are
    deterministic display mocks; the real backend (also leaderless)
    arrives in Phase 3 R3.
  - Phase 3 R2 swap target: components are seam-isolated so swapping
    inline-styled `<div>` / native `<select>` for @hanzo/gui v7
    primitives is mechanical. The shape of BridgeApp.tsx stays stable.

Stats: 15 new files, 1336 LoC. Renders identical output to the
former @luxbridge/app App component for the four MVP features.
2026-05-21 15:03:16 -07:00
zeekay 3d0da53ddd docs+manifest: align protocol references with new union (NIT from red review) 2026-05-21 14:57:19 -07:00
zeekay e03574dc57 pkg/{bridge,threshold}: protocol union — drop lss+ringtail, add pulsar/corona/magnetar/doerner (PQ-safe leaderless)
Align the SDK Protocol union with the canonical thresholdd protocol set
(cggmp21, frost, pulsar, corona, bls, doerner) plus magnetar from the
study/ research variant. lss and ringtail are removed entirely — lss
never had a leaderless-safe round and ringtail folded into the pulsar/
corona lattice family.

Classical: cggmp21 (ECDSA secp256k1), frost (EdDSA ed25519), bls, doerner (2-of-2).
Post-quantum: pulsar (MLWE), corona (RLWE), magnetar (research variant).

- pkg/bridge/src/types.ts: BridgeMPCConfig.protocol widened with JSDoc
- pkg/threshold/src/types.ts: Protocol type widened with matching JSDoc
- pkg/threshold/src/client.test.ts: 'lss' callsites → 'cggmp21'
- pkg/bridge/src/__tests__/config.test.ts: parametric it.each test
  walking all 7 protocols, confirms setConfig({mpc:{protocol}}) accepts each

Verified: pnpm typecheck + pnpm test green on pkg/bridge (14/14) and
pkg/threshold (8/8).
2026-05-21 14:47:20 -07:00
zeekay 09ca480783 pkg/bridge: widen MPC protocol union, add setConfig precedence guard (red review)
- BridgeMPCConfig.protocol now mirrors @luxfi/threshold Protocol union:
  'lss' | 'cggmp21' | 'frost' | 'bls' | 'ringtail'
- setConfig throws on conflicting clientId / iamOrg between top-level
  legacy fields and auth.{clientId,orgSlug} so misconfigured tenants
  fail loudly at boot rather than silently picking one source.
- vitest wired into pkg/bridge with 7 tests covering precedence guard.
2026-05-21 14:26:02 -07:00
zeekay 0b4e889b91 feat(pkg/bridge): add auth/kms/wallet/mpc config blocks to BridgeConfig
Extend the @luxfi/bridge SDK contract so tenant apps wire identical
auth/KMS/wallet/MPC config across `<Exchange>` and `<Bridge>`. The new
blocks intentionally mirror the canonical `<Exchange>` prop shape from
@luxfi/exchange — same field names, same semantics.

New optional types in pkg/bridge/src/types.ts:
- BridgeAuthConfig  (provider, issuer, clientId, idHost?, orgSlug?)
- BridgeKMSConfig   (url)
- BridgeWalletConfig (walletConnectProjectId?, defaultChainId?, supportedChainIds?)
- BridgeMPCConfig   (publicUrl, privateUrl?, protocol?)

BridgeConfig gains optional `auth`, `kms`, `wallet`, `mpc` fields.
Legacy `clientId` / `iamOrg` flagged @deprecated but preserved — no
existing consumer breaks.

Bridge.tsx + mount.ts comments updated to note the full config (incl.
new blocks) is seeded synchronously via setConfig before children read.
No runtime code change required — setConfig already stores the whole
object, so new fields automatically flow through to getConfig consumers.

README updated with a complete tenant example wiring all four new
blocks, plus per-block field tables.

Typecheck: clean (tsc --noEmit).
2026-05-21 14:17:45 -07:00
zeekay 778ff148c3 feat(pkg/bridge): add @luxfi/bridge SDK + publish workflow
Rationalize the bridge workspace around one canonical SDK entry:

  - @luxfi/bridge        (NEW)  public SDK consumers import
  - @luxbridge/*          private workspace internals
  - @luxfi/{core,threshold,utila} generic Lux building-block libs

@luxfi/bridge re-exports mountBridge(), <Bridge />, BridgeConfig, and
BrandConfig. The runtime lazy-imports @luxbridge/app as the actual UI
implementation. Mirrors the mountExchange() pattern from
@partner/exchange — declarative config-in, brand metadata applied
to document, React tree rendered into #bridge-root.

Workspace housekeeping:
  - pkg/settings: 0.1.0 -> 1.0.0, marked private
  - pkg/ui (ui-automation), app/server, app/bridge3: marked private
  - app/explorer: 0.1.0 -> 1.0.0
  - root bridge package: 1.0.0 -> 1.1.0
  - app/bridge: add main/types/exports so the SDK can dynamic-import the App component

Publish workflow at .github/workflows/publish.yml fires on v* tag push,
runs pnpm publish -r --access public --no-git-checks. pnpm skips
private packages automatically. Public packages: @luxfi/bridge, @luxfi/core,
@luxfi/threshold, @luxfi/utila.
2026-05-21 14:02:03 -07:00
zeekay da46fbdc1a feat: add Polkadot/Substrate support to bridge
New chain support:
- Polkadot mainnet (DOT, 10 decimals)
- SS58 address encoding/decoding (zero-dependency, blake2b)
- Substrate JSON-RPC client (raw fetch, no @polkadot/api dep)
- Deposit detection following existing polling pattern
- SCALE extrinsic builder + MPC sr25519 signing interface
- LDOT/ZDOT wrapped tokens on Lux/Zoo

Files:
- substrate-address.ts — SS58 encode/decode (13 tests)
- substrate-client.ts — balance, nonce, broadcast (7 tests)
- substrate-deposit.ts — DOT deposit detection
- substrate-signer.ts — MPC signing integration
- Updated: networks.ts, mpc-wallet.ts, swap-pairs.ts, network-type.ts
2026-03-18 14:09:27 -07:00
zeekay be3a4b7a75 fix: build workspace deps in CI, bump @luxfi/core for xrp type
- Dockerfile: copy and build pkg/core before app/server
- CI: build workspace dependencies before typecheck
- server.yml: use root context for Docker build
- Bump @luxfi/core to 10.0.6 (adds xrp NetworkType)
2026-03-05 23:58:32 -08:00
zeekay e9dbf11a1d feat: native MPC wallet support, replace Utila, enable all chains
- Replace Utila/Fireblocks wallet creation with native MPC API
  (new mpc-wallet.ts calls our MPC cluster directly via HTTP)
- Add native deposit checking via blockchain RPCs
  (EVM eth_getBalance, BTC Blockstream API, SOL getBalance, TON toncenter)
- Enable deposits on BTC (testnet+mainnet), SOL (devnet+mainnet),
  TON (testnet+mainnet)
- Add XRP Ledger as new chain (testnet+mainnet) with NetworkType "xrp"
- Add LXRP wrapped token to Lux/Zoo network configs
- Add XRP swap pairs (XRP <-> LXRP <-> ZXRP)
- Fix BTC testnet decimals (was 18, now 8)
- Update swap-pairs: SOL/TON now include native token pairs
2026-03-05 23:41:13 -08:00
zeekay 65512492c3 feat: enable LUX<>ZOO bridging, 0% in / 1% out fees, pod hardening
- Enable deposits for native LUX and ZOO currencies on both chains
- Fee model: 0% deposit, 1% withdrawal (MPC wallet accumulates fees)
- Add securityContext to bridge MPC nodes: non-root, read-only rootfs,
  drop ALL capabilities, seccomp RuntimeDefault
- Add /tmp emptyDir for read-only rootfs compatibility
2026-03-05 16:03:27 -08:00
zeekay 93238aee94 remove firebase: kill transitive dep, clean up dead code
- Remove @hanzo/auth dependency (bridge uses JWT, not Firebase Auth)
- Remove @hanzo/commerce from peer deps and catalog
- Delete firebase-stub.js (unused build-time shim)
- Delete disabled mobile.tsx_ header component
- Add webpack aliases to prevent firebase from being bundled
- Add .dockerignore for faster Docker builds
- Simplify Dockerfile.bridge-ui (remove redundant config patching)
- Add pnpm peerDependencyRules to ignore unused optional deps
2026-02-27 15:59:25 -08:00
zeekay 5557c00eae Add docs 2025-12-24 14:19:52 -08:00
zeekay 2a5bda0506 Update project 2025-08-03 18:45:59 -05:00
zeekay a1b78ae8cb Integrate new KMS + MPC services 2025-08-03 15:52:32 -05:00
venuswhispers 60ea4f592c updated: mint authority of Asset type 2025-01-20 16:52:49 -06:00
Artem AshandGitHub 1828a67793 hmr fix; will receive card impl; (#331)
* added server url and version to VITE_ env
  impl getAssetPrice()
  simplified store
fixed total value display;
added loading state to network cb
* Fixed hmr issue (now using link() function in rout.tsx vs import);
Initial debounced and animated ReceiveCard;
set 1rem to 16px in lux-global-non-next.css
2024-12-31 12:01:10 -08:00
venuswhispers 36ac37c1be updated: luxfi/core 10.0.2 2024-12-31 08:26:14 +08:00
Artem AshandGitHub 57336ef58d moved swap pairs to settings; improved swap logic (#319)
* domain logic for swap form (using mobx reactions)
* swap pairs are not in server; contracts are now in server (no api yet)
2024-12-29 15:08:12 -08:00
artem ash eb480724b6 server and new-bridge are using types only from @luxfi/core
new-bridge is correctly loading its settings from the backend
extraneous types and objects have been removed from new-bridge
2024-12-24 18:33:13 -08:00
artem ash c70e39ee72 start of server encapsulation 2024-12-22 19:52:31 -08:00
a3c6af02b6 new bridge ui (on remix) - initial (#304)
* initial remix boilerplate runs
* remix: font support
* metadata and favicons
* network and tokens domain model;
* network-combobox; 
* favicon bug;
* new combobox interface
* network combobox typescript issues
* reverse button, etc
* amount input and token selection
* renamed remix-bridge to new-bridge
* @hanzo/[email protected]; new scripts
* lockfile

---------

Co-authored-by: Zach Kelling <[email protected]>
2024-12-18 10:46:15 -08:00
zeekay 06afcc7f6f Add our custom network IDs 2024-11-28 13:55:15 -06:00
zeekay 9513c8bcfb Add archive example 2024-11-23 13:32:13 -06:00
zeekay 6cd36e33a3 Add example to create BTC, TON, SOL wallets 2024-11-23 13:30:14 -06:00
zeekay 13f3bf7bac Simplify test 2024-11-21 18:14:31 -06:00
zeekay a844b246ba Switch to rollup for packages 2024-11-21 18:13:37 -06:00
zeekay ca5db32e4f Update @luxfi/utila 2024-11-21 14:34:40 -06:00
zeekay 4506217188 Fix utila build 2024-11-21 14:30:36 -06:00
zeekay 581073eccd Add utila 2024-11-21 14:25:46 -06:00
venuswhispers 22120fadd9 update: deploy new contracts & mpc-nodes 2024-11-18 00:55:35 +08:00
zeekay db98fad594 Use catalog everywhere 2024-11-14 13:30:25 -08:00
zeekay f34110e107 Update project structure, split out settings to pkg 2024-11-13 17:52:39 -08:00