mirror of
https://github.com/luxfi/bridge.git
synced 2026-09-24 00:26:46 +00:00
Caught on a second pass: grep -I treats these as binary and skipped them, so the first sweep reported clean while they still named a host that no longer resolves.
706 lines
30 KiB
Markdown
706 lines
30 KiB
Markdown
# Lux Bridge
|
|
|
|
**Project**: Lux Bridge
|
|
**Organization**: Lux Network
|
|
|
|
## Project Overview
|
|
|
|
The Lux Bridge is a decentralized cross-chain bridge infrastructure that enables secure, trustless asset transfers between multiple blockchain networks using Multi-Party Computation (MPC) technology. It serves as the primary interoperability layer for the Lux ecosystem, connecting 15+ blockchain networks.
|
|
|
|
## Package layout — public vs internal
|
|
|
|
Two scopes, one canonical SDK entrypoint. Downstream consumers (Lux, Hanzo,
|
|
Zoo, any white-label) import `@luxfi/bridge` and nothing else.
|
|
|
|
| Path | npm name | Scope | Published |
|
|
|---|---|---|---|
|
|
| `pkg/bridge/` | `@luxfi/bridge` | public SDK | pending — see §Publishing |
|
|
| `pkg/core/` | `@luxfi/core` | public shared types | yes (last 10.0.5, 2025-05-09) |
|
|
| `pkg/threshold/` | `@luxfi/threshold` | public MPC SDK | pending — see §Publishing |
|
|
| `pkg/utila/` | `@luxfi/utila` | public utila client | yes (last 3.0.0, 2024-11-21) |
|
|
| `pkg/settings/` | `@luxbridge/settings` | private workspace | no |
|
|
| `pkg/ui/` | `@luxbridge/ui-automation` | private workspace | no |
|
|
| `app/bridge/` | `@luxbridge/lux-tenant` | private workspace | no |
|
|
| `app/explorer/` | `@luxbridge/explorer` | private workspace; **migration to `ghcr.io/luxfi/explorer` planned** — see §Explorer migration | no |
|
|
| `app/server/` | `@luxbridge/server` | private workspace | no |
|
|
|
|
Rules:
|
|
- `@luxfi/bridge` is the ONLY package consumers import. It re-exports
|
|
`mountBridge`, `Bridge`, and the config/brand types. Internals are hidden.
|
|
- `@luxbridge/*` packages are workspace-only. They are not published and must
|
|
not be imported by anything outside this repo.
|
|
- `@luxfi/{core,threshold,utila}` are generic Lux building-block libraries —
|
|
bridge-agnostic, may be reused elsewhere in the Lux ecosystem.
|
|
|
|
### Canonical brand sourcing — `@luxfi/brand` is the only source
|
|
|
|
Every Lux-ecosystem tenant app — bridge, exchange, id, pay, etc. — pulls
|
|
its brand identity from **one** package: `@luxfi/brand` (source repo:
|
|
`~/work/lux/brand`). No Lux-specific brand strings, colors, logos, or
|
|
support email addresses are baked into the SDK or any other downstream
|
|
artefact.
|
|
|
|
How the bridge tenant does it (`app/bridge/src/bridge.config.ts`):
|
|
|
|
```ts
|
|
import luxBrandJson from '@luxfi/brand/brand.json'
|
|
const { shortName, primaryColor, supportEmail } = luxBrandJson.brand
|
|
|
|
export const bridgeConfig: BridgeConfig = {
|
|
apiHost: env('BRIDGE_API_HOST', 'https://api.bridge.lux.network')!,
|
|
brand: {
|
|
name: `${shortName ?? 'Lux'} Bridge`, // composed at runtime
|
|
primaryColor: primaryColor ?? '#000000',
|
|
supportEmail: supportEmail ?? '[email protected]',
|
|
},
|
|
// …
|
|
}
|
|
```
|
|
|
|
Rules:
|
|
|
|
- The **SDK** (`pkg/bridge/`) is **jurisdiction-neutral**. It declares
|
|
*no* Lux-specific brand. Tenants supply brand at mount time via
|
|
`BridgeConfig.brand`. The SDK has no direct dependency on
|
|
`@luxfi/brand` — that'd be a layering violation (Zoo, Hanzo, or any
|
|
white-label tenant must be able to consume `@luxfi/bridge` without
|
|
pulling Lux brand metadata into their bundle).
|
|
- **App-specific endpoints** (`api.bridge.lux.network`, `docs.lux.network/bridge`)
|
|
belong to the bridge product and are independent of `@luxfi/brand`'s
|
|
`appDomain` (`lux.exchange` — that's the exchange product's domain).
|
|
- **SSR / no-window fallbacks** in the SDK derive from `cfg.apiHost`
|
|
rather than hardcoding a Lux URL — see `deriveAppOrigin` in
|
|
`pkg/bridge/src/app/lib/wagmi-config.ts`.
|
|
|
|
Other Lux apps follow the same pattern: `~/work/lux/exchange` reads
|
|
the full brand block; `~/work/lux/id` reads `name` + `primaryColor` +
|
|
`appDomain`; etc. If you find a hardcoded brand string in any Lux app,
|
|
it's a leak — file it as a follow-up issue and route the value through
|
|
`@luxfi/brand`.
|
|
|
|
### History — what got folded together
|
|
|
|
- **`app/bridge3/`** (formerly `@luxbridge/app-v3`) was **folded into
|
|
`pkg/bridge/src/app/`** at commit `abed909` ("feat(pkg/bridge): inline
|
|
bridge UI under src/app/", phase 1.5). The old lazy-loaded workspace
|
|
cycle that produced the blank-page bug is gone. `Bridge.tsx` no longer
|
|
references any sibling workspace package.
|
|
- **`app/bridge/`** was collapsed to a single canonical tenant build at
|
|
`6bc55b2` ("phase2-r2: collapse to canonical app/bridge tenant"). It is
|
|
now `@luxbridge/lux-tenant` (~120 LOC: `bridge.config.ts`, `main.tsx`,
|
|
`index.html` + Vite config), not the multi-variant Next.js app shown in
|
|
stale diagrams.
|
|
|
|
## SDK mount pattern
|
|
|
|
```ts
|
|
import { mountBridge } from '@luxfi/bridge'
|
|
|
|
mountBridge({
|
|
config: {
|
|
apiHost: 'https://api.bridge.lux.network',
|
|
env: 'mainnet',
|
|
brand: { name: 'Lux Bridge', primaryColor: '#0066ff' },
|
|
},
|
|
})
|
|
```
|
|
|
|
Mirrors `mountExchange` from the upstream exchange SDK. One declarative entry,
|
|
build-time brand config, no hostname detection inside the SDK.
|
|
|
|
## Publishing
|
|
|
|
`.github/workflows/publish.yml` fires on `v*` tag push, runs on the
|
|
self-hosted `lux-build` runner. Runs `pnpm publish -r --access public
|
|
--no-git-checks` — pnpm walks the workspace, skips `private: true`
|
|
packages, and publishes any `@luxfi/*` package whose version is not yet
|
|
on npmjs.org. Bump versions in PRs; tag once merged.
|
|
|
|
**Status as of 2026-05-22**: the workflow is wired and hardened (fail-fast
|
|
on missing `NPM_TOKEN`/`NODE_AUTH_TOKEN`; token baked into the
|
|
`NPM_CONFIG_USERCONFIG` path because pnpm doesn't expand `${VAR}` in
|
|
.npmrc) but `@luxfi/bridge` and `@luxfi/threshold` are still 404 on
|
|
npmjs.org. Confirmed by `@luxfi/core` 10.0.5 and `@luxfi/utila` 3.0.0
|
|
both pre-dating every `v1.1.x` tag — no v1.1.x release has ever shipped
|
|
to npm. Most likely missing `NPM_TOKEN` secret on the `luxfi/bridge` repo
|
|
(Settings → Secrets and variables → Actions) OR an unavailable
|
|
`lux-build` runner pool. The v1.1.10 fail-fast guard will surface either
|
|
clearly on the next run.
|
|
|
|
## Explorer migration
|
|
|
|
`app/explorer/` (`@luxbridge/explorer`, Next.js standalone) is on a
|
|
deprecation path. The canonical replacement is **`ghcr.io/luxfi/explorer`**
|
|
— a single Go binary at `~/work/lux/explorer` that:
|
|
|
|
- Indexes EVM chains via [`luxfi/indexer`](https://github.com/luxfi/indexer)
|
|
- Exposes per-chain GraphQL via [`luxfi/graph`](https://github.com/luxfi/graph)
|
|
- Serves the embedded SPA from [`luxfi/explore`](https://github.com/luxfi/explore) via `go:embed`
|
|
|
|
Routes (served from one binary, port-flexible):
|
|
|
|
| Path | Purpose |
|
|
|---|---|
|
|
| `/` | SPA (embedded; SPA-routing fallback) |
|
|
| `/envs.js` | Runtime `window.ENV = {…}` |
|
|
| `/v1/indexer/{slug}/*` | Per-chain explorer REST API |
|
|
| `/v1/graph/{slug}/{subgraph}/graphql` | Per-chain, per-subgraph GraphQL |
|
|
| `/v1/explorer/realtime` | WebSocket realtime hub |
|
|
|
|
Migration plan: deploy `luxfi/explorer` alongside `app/explorer/`,
|
|
hostname-cutover (`bridge-explorer.lux.network`) on K8s, delete the
|
|
Next.js app after one-week soak.
|
|
|
|
The bridge backend (`app/server/`) does not depend on `app/explorer/`;
|
|
the migration is UI-only.
|
|
|
|
## Architecture Summary
|
|
|
|
### Core Technology Stack
|
|
- **SDK**: `@luxfi/bridge` — TypeScript source-published; React 18 + Tamagui
|
|
(`@hanzo/gui` v7 + `@hanzogui/*` peer-optional primitives); wagmi 2 + viem 2
|
|
+ `@tanstack/react-query` 5 for wallet; `@luxfi/threshold` for native MPC
|
|
threshold sign sessions
|
|
- **Tenant apps**: Vite + React 18.3.1 (`app/bridge/` = `@luxbridge/lux-tenant`,
|
|
`app/explorer/` = `@luxbridge/explorer`)
|
|
- **Backend**: Node.js + Express + Prisma (`app/server/` = bridge API at
|
|
`api.bridge.lux.network`)
|
|
- **MPC Framework**: 2-of-3 threshold signature scheme using `github.com/luxfi/mpc`
|
|
(Go); client-side session helper via `@luxfi/threshold` (TS)
|
|
- **Smart Contracts**: Solidity (OpenZeppelin), deployed on multiple chains
|
|
- **Infrastructure**: Docker, Kubernetes, PostgreSQL, NATS, Consul, Vault (KMS)
|
|
- **Authentication**: Lux ID (Casdoor) for unified auth
|
|
- **Runtime config**: `window.__ENV` (templated from `BRIDGE_*` env at container
|
|
boot by nginx docker-entrypoint) with build-time `import.meta.env.VITE_*`
|
|
fallback. One image, N envs.
|
|
|
|
### System Architecture
|
|
```
|
|
┌──────────────────────────────────┐
|
|
Tenant app │ @luxfi/bridge (pkg/bridge/) │
|
|
(app/bridge/, │ exports: Bridge, mountBridge, │
|
|
app/explorer/) │ BridgeConfig, types │
|
|
imports SDK + │ │
|
|
brand: │ Bridge UI is INLINED at │
|
|
@luxfi/bridge ─▶ │ pkg/bridge/src/app/ │
|
|
@luxfi/brand │ (no @luxbridge/app-v3, no │
|
|
│ workspace cycle, no lazy load) │
|
|
│ │
|
|
│ Direct deps: │
|
|
│ @hanzo/gui, @luxfi/threshold, │
|
|
│ wagmi, viem, react-query │
|
|
└─────────────┬────────────────────┘
|
|
│
|
|
┌────────────────┴─────────────────┐
|
|
▼ ▼
|
|
┌───────────────────────┐ ┌──────────────────────┐
|
|
│ app/server │ │ @luxfi/threshold │
|
|
│ Bridge API │ │ MPC threshold SDK │
|
|
│ Express + Prisma │ │ (consumed by BOTH │
|
|
│ api.bridge.lux.network│ │ SDK + server) │
|
|
└──────────┬────────────┘ └──────────┬───────────┘
|
|
│ │
|
|
▼ ▼
|
|
┌─────────────────────┐ ┌──────────────────┐
|
|
│ b-chain │ │ m-chain (public │
|
|
│ Lux primary network │ │ MPC, m-chain) │
|
|
│ consensus + state │ │ │
|
|
└─────────────────────┘ │ + optional │
|
|
│ private cluster │
|
|
│ (treasury fees) │
|
|
│ │
|
|
│ + optional │
|
|
│ layered cosign: │
|
|
│ Utila │
|
|
│ Fireblocks │
|
|
└──────────────────┘
|
|
```
|
|
|
|
### MPC Network Configuration
|
|
- **Node Count**: 3 nodes (2-of-3 threshold) on m-chain (public MPC)
|
|
- **Ports**:
|
|
- HTTP API: 6000-6002
|
|
- gRPC: 9090-9092
|
|
- NATS: 4223
|
|
- Consul: 8501
|
|
- **Security**: TLS 1.3, mutual authentication, HSM integration
|
|
- **Protocols**: `cggmp21` (default), `frost`, `bls`, `doerner` (classical);
|
|
`pulsar` (MLWE), `corona` (RLWE), `magnetar` (lattice research variant) —
|
|
PQ-safe, leaderless, permissionless-safe by design.
|
|
|
|
### Standalone mode — running the bridge without the Lux platform
|
|
|
|
The bridge can boot with the absolute minimum: Postgres + bridge-server +
|
|
bridge-ui. Every other Lux service (IAM / KMS / m-chain MPC / cosigners)
|
|
is **optional at runtime** — the backend graceful-degrades when the
|
|
corresponding env block is unset and picks up the integration the moment
|
|
the block appears.
|
|
|
|
```
|
|
docker compose -f compose.standalone.yml up --build
|
|
# bridge UI http://localhost:3001
|
|
# bridge API http://localhost:5000/healthz
|
|
# postgres localhost:5433 (user=bridge, pass=bridge, db=bridge)
|
|
```
|
|
|
|
What's intentionally NOT in `compose.standalone.yml` (but is in
|
|
`compose.local.yml`):
|
|
|
|
| Service | Why standalone can skip it |
|
|
|---|---|
|
|
| Casdoor (Lux ID) | bridge does not require auth in dev mode |
|
|
| Vault (KMS) | `fetchCosignerSecret` falls back to env-var pattern when `BRIDGE_KMS_URL` is unset |
|
|
| NATS | only `mpc-service.ts` consumes it; backend logs a warn and skips its initialise() |
|
|
| Consul | same — `mpc-service.ts` only |
|
|
| 3-node MPC cluster | `LuxMPCClient` is constructed on demand; native MPC sign requires it but cosigner-layered swaps don't |
|
|
|
|
To enable any of the optional layers, set the matching env block on the
|
|
`bridge-server` service in `compose.standalone.yml` (commented stubs are
|
|
already there). The bridge picks them up on next request — no restart
|
|
required for cosigner config, only `BRIDGE_MPC_URL` requires a restart (cached at server start).
|
|
|
|
### ZAP serialization — canonical home is `~/work/zap`, npm scope `@zap-proto`
|
|
|
|
The bridge currently uses plain JSON over HTTP for every wire call (mpcd,
|
|
KMS, IAM, cosigner adapters, f-chain). It does **not** depend on any
|
|
binary RPC codec. If a future surface needs Cap'n-Proto-style binary
|
|
RPC (e.g. mpcd → bridge events stream), use **ZAP** — the canonical
|
|
Lux / Hanzo zero-copy app protocol.
|
|
|
|
| Aspect | Canonical |
|
|
|---|---|
|
|
| Source repo | **`~/work/zap`** (not `~/work/lux/zap` or `~/work/hanzo/zap` — those are forks / legacy clones being consolidated upstream) |
|
|
| GitHub org | `zap-proto` |
|
|
| npm scope | `@zap-proto/*` |
|
|
| TS package | `@zap-proto/zap` (lives at `~/work/zap/zap-js`) — pure-TS codec, no third-party Cap'n Proto deps. The package.json once listed `capnp-es` in `dependencies` but no source file imports it; that dep is stale and slated for removal in the next ZAP TS publish |
|
|
| WASM binding | `@zap-proto/wasm` (`~/work/zap/zap-wasm`) |
|
|
| Go / Rust | wire-compatible siblings; same ZAP framing |
|
|
|
|
**Rules for the bridge** (and any other downstream consumer):
|
|
|
|
- **Never `pnpm add capnp-es`** to add binary RPC. If we need Cap'n-Proto-
|
|
style framing, use `@zap-proto/zap` (pure TS, no third-party dep).
|
|
The whole point of ZAP is to be brand-neutral and wire-compatible
|
|
across Go / Rust / TS without pulling in the upstream Cap'n Proto JS
|
|
ecosystem.
|
|
- The `@zap-proto/*` scope is brand-neutral. Don't publish `@luxfi/zap-*`
|
|
or `@hanzoai/zap-*` variants — they'd fork the wire format and break
|
|
cross-stack RPC.
|
|
- Source of truth for the wire format: `~/work/zap/README.md` (Go) +
|
|
`~/work/zap/zap-js/src/protocol.ts` (TS). Match either.
|
|
|
|
### Foreign-brand bridge tenants — out of this repo
|
|
|
|
The canonical Lux cross-chain bridge protocol lives at `~/work/lux/teleport`
|
|
(Warp 2.0 envelopes + m-chain custody, LP-021v2 / LP-7330). The
|
|
user-facing UI + SDK is `~/work/lux/bridge` (this repo) and ships ONE
|
|
Lux-branded tenant (`app/bridge/`). Foreign-brand bridge surfaces
|
|
(Hanzo, Zoo, Pars, third-party) MUST live in their own repos and own
|
|
their brand assets there — typically a thin shim repo (e.g.
|
|
`zooai/bridge-shim`) that consumes `@luxfi/bridge` plus its own brand
|
|
package. No foreign-brand tenant manifests, logos, or color tokens
|
|
belong in `luxfi/bridge`. The SDK is the integration point; tenants
|
|
are thin and live where their brand lives.
|
|
|
|
### Native Lux services — m-chain (MPC) + KMS + f-chain
|
|
|
|
The bridge backend talks to the canonical Lux ecosystem services via
|
|
HTTP clients in `app/server/src/clients/`:
|
|
|
|
- **`lux-iam.ts`** — `LuxIAMClient.mint(audience)` mints OAuth2
|
|
`client_credentials` JWTs against Lux IAM (`https://iam.lux.network`).
|
|
Per-audience cache with early refresh. The bridge mints one token for
|
|
`aud=lux-kms` and another for `aud=lux-mpc`; both rotate independently.
|
|
- **`lux-kms.ts`** — `LuxKMSClient.getSecret(path)` reads from
|
|
`~/work/lux/kms` (`https://kms.lux.cloud`). Bearer JWT auth, path
|
|
layout `bridge/cosigners/{utila|fireblocks}/{publicId}/{sa_pem|secret_pem}`.
|
|
Replaces every env-var fallback in `domain/cosigners.ts::fetchCosignerSecret`.
|
|
- **`lux-mpc.ts`** — `LuxMPCClient.keygen({...})` / `.sign({...})` /
|
|
`.status()` against `mpcd` (`https://mpc.lux.network`). This is THE
|
|
m-chain entrypoint — the canonical Lux MPC daemon at `~/work/lux/mpc`,
|
|
speaking the same wire protocol as the Go client at
|
|
`~/work/lux/kms/pkg/mpc/client.go`. Supports the full Lux protocol set
|
|
(CGGMP21 / FROST / BLS / SR25519 / Pulsar / Corona / Magnetar / Doerner).
|
|
Per-call 120s timeout matches mpcd's threshold-completion ceiling.
|
|
|
|
Configuration via env (bridge backend startup):
|
|
|
|
```
|
|
BRIDGE_IAM_ISSUER https://iam.lux.network
|
|
BRIDGE_KMS_URL https://kms.lux.cloud
|
|
BRIDGE_KMS_ORG lux
|
|
BRIDGE_IAM_CLIENT_ID lux-bridge
|
|
BRIDGE_IAM_CLIENT_SECRET (from KMS at boot OR --kms-bootstrap-secret)
|
|
BRIDGE_MPC_URL https://mpc.lux.network
|
|
```
|
|
|
|
When `BRIDGE_KMS_URL` is unset (local dev), `fetchCosignerSecret` falls
|
|
back to per-tenant env vars. Production deployments MUST set the KMS
|
|
env block — env-var fallback is dev-only.
|
|
|
|
### Lux software frontends (administrative UIs)
|
|
|
|
Three Lux ecosystem services ship their own administrative UIs separate
|
|
from the bridge tenant SDK:
|
|
|
|
| Service | Frontend repo / path | Stack | Domain |
|
|
|---|---|---|---|
|
|
| **m-chain (mpcd)** | `~/work/lux/mpc/dashboard/` (`@luxfi/mpc-dashboard`) | Next.js 15 + React 19 + zustand | `mpc.lux.network/dashboard` |
|
|
| **KMS** | `~/work/lux/kms/frontend/` (`@hanzo/kms-frontend`) | Vite + React | `kms.lux.cloud` |
|
|
| **f-chain (fhed)** | **DOES NOT EXIST** — see below | — | — |
|
|
|
|
The bridge does **not** embed these dashboards; they're separate
|
|
operator surfaces.
|
|
|
|
### f-chain frontend — what it would look like
|
|
|
|
`~/work/lux/fhe` (the FHE daemon `fhed`) currently has no dedicated UI.
|
|
The bridge tenant's `TRANSFERS` panel surfaces per-swap f-chain
|
|
attestation status (a `CosignerStep` row when the bridge backend
|
|
processes a swap with `fchain: { publicUrl, scheme }` config), but
|
|
that's per-bridge-tenant, not a cluster-wide operator surface.
|
|
|
|
A standalone f-chain dashboard, when built, would mirror the
|
|
mpc-dashboard layout and surface:
|
|
|
|
1. **Cluster status** — `GET /v1/fhe/cluster/status`: peer count, FHE
|
|
scheme registry, share health per node.
|
|
2. **Public key registry** — `GET /v1/fhe/publickey`: published FHE
|
|
public keys per scheme (CKKS / BGV / BFV), with an export button.
|
|
3. **Encryption sessions** — recent `/v1/fhe/encrypt` calls and their
|
|
ciphertext bindings (txHash → ciphertext_hex), filterable by
|
|
requesting service (bridge / pay / id / ...).
|
|
4. **Reshare history** — `/v1/fhe/cluster/reshare` events.
|
|
5. **Health probe** — `GET /v1/fhe/health` rolled up to per-node status.
|
|
|
|
Recommended scaffold (when scheduled):
|
|
|
|
```
|
|
~/work/lux/fhe/dashboard/ # new Next.js app, mirror of mpc/dashboard
|
|
package.json @luxfi/fchain-dashboard
|
|
app/ # Next.js app router
|
|
components/cluster/ # ClusterStatus, PeerList
|
|
components/keys/ # PublicKeyRegistry, ExportButton
|
|
components/sessions/ # EncryptHistory, CiphertextViewer
|
|
lib/fchain-client.ts # mirrors lux-mpc.ts / lux-kms.ts shape
|
|
```
|
|
|
|
### Optional layered cosigners (since SDK v1.0.3)
|
|
|
|
External MPC custodians can be layered ON TOP of the native threshold network
|
|
as additional cosigners. The bridge backend enforces 2-of-2 (native + layered)
|
|
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 SDK declares config only; the secret half (Utila JWT / Fireblocks
|
|
secret key) lives on `app/server/` (sourced from KMS at boot). No secret
|
|
material is ever shipped in the page bundle.
|
|
|
|
```ts
|
|
mountBridge({
|
|
config: {
|
|
apiHost: 'https://api.bridge.lux.network',
|
|
env: 'mainnet',
|
|
mpc: {
|
|
publicUrl: 'https://mpc.lux.network',
|
|
protocol: 'cggmp21',
|
|
// optional — layered cosigner #1
|
|
utila: { orgId: 'tenant-x', clientId: 'lux-bridge' },
|
|
// optional — layered cosigner #2 (both may be enabled together)
|
|
fireblocks: { apiKey: 'pub-key-id', vaultAccountId: '0' },
|
|
},
|
|
},
|
|
})
|
|
```
|
|
|
|
Per-swap, the SDK forwards a `cosigners[]` array of public identifiers
|
|
(`orgId`, `clientId`, `apiKey`, vault ids) to `POST /api/swaps`. The
|
|
backend pairs each entry with a KMS-held secret and completes the cosign
|
|
on behalf of the tenant.
|
|
|
|
## Supported Networks & Assets
|
|
|
|
### Chain support — ground truth (v1.1.41, 2026-06-26)
|
|
|
|
The `cmd/bridge` MPC pipeline routes by address family (`internal/mchain` AddressType
|
|
enum + switch dispatch — there is NO polymorphic Chain interface). Per-chain reality
|
|
(stale "TODO"/"coming soon" comments elsewhere lag the code — this table is authoritative):
|
|
|
|
| Family | Inbound (depositcheck) | Outbound (assemble+sign+broadcast) | Config | Status |
|
|
|--------|------------------------|------------------------------------|--------|--------|
|
|
| ETH/EVM | done (eth_getBalance / ERC-20) | done (hand-rolled RLP, no go-ethereum) | enabled | full, live |
|
|
| BTC | done (Blockstream REST) | code-complete | deposit-only | withdrawal gated on mpcd Schnorr |
|
|
| SOL | done (getBalance) | code-complete | disabled both | gated on mpcd Ed25519/FROST |
|
|
| TON | done (TON Center) | code-complete | disabled both | gated on mpcd Ed25519 |
|
|
| XRP | **done — rippled `account_info`, validated ledger, drops** (`internal/depositcheck`) | done — DestinationTag plumbed into the SHA-512Half signing digest; X-addr rejected | **testnet on; mainnet gated** | secp256k1 (same curve as ETH/BTC) → no mpcd gap |
|
|
|
|
XRP is the only non-EVM family with no external mpcd blocker. XRP per-swap deposit
|
|
addresses must hold the ≥10-XRP AccountRoot reserve (per-XRP minUSD floor set); the
|
|
reserve is a known capital-leak follow-up (shared-deposit + dest-tag is the robust model).
|
|
|
|
### Withdrawal/deposit gate — AUTHORITATIVE (v1.1.41)
|
|
`Config.WithdrawalEnabled` / `DepositEnabled` (cmd/bridge/config.go) are enforced
|
|
SERVER-SIDE at create (403), sign (skip = kill-switch), broadcast (hold in-flight),
|
|
and operator inject (403) — not just the SPA `/currencies` list. Match is
|
|
case-INSENSITIVE with default-DENY for wired non-EVM families (so an asset-case
|
|
variant `xrp` or network-case `bitcoin_testnet` can't dodge a disabled row into a
|
|
release pool — the gate key aligns with the network-only/case-folded signer routing).
|
|
|
|
### Mainnet-readiness follow-ups (pre-existing, bridge-wide; tracked, NOT XRP-specific)
|
|
- `/admin/swaps/:id/{reset,inject-raw-tx}` have no auth middleware (bounded by 64-bit
|
|
random swap IDs + the now-gated inject). Gate behind operator token / mTLS.
|
|
- `GET /v1/bridge/swaps` has no tenant scoping (IDOR).
|
|
- depositcheck is the SOLE runtime payout gate (single hardcoded RPC, no quorum) —
|
|
multi-RPC quorum / on-chain proof is the platform hardening. Point only at trusted RPCs.
|
|
- mainnet XRP withdrawal stays `isWithdrawalEnabled:false` until the above + a live
|
|
testnet tag-carrying-payout proof.
|
|
|
|
### Blockchain Networks (15+)
|
|
**Layer 1**: Lux, Ethereum, BSC, Polygon, Fantom
|
|
**Layer 2**: Arbitrum, Optimism, zkSync Era, Polygon zkEVM, Base, Blast
|
|
**Other**: TON, Solana (gated on mpcd Ed25519), XRP (testnet live), Cosmos (via IBC)
|
|
|
|
### Supported Assets
|
|
**Stablecoins**: USDT, USDC, DAI, ZUSD
|
|
**Native Tokens**: LUX, ETH, BNB, MATIC
|
|
**Wrapped Assets**: ZETH, ZBNB, ZPOL, ZTON (ERC20B standard)
|
|
|
|
## Key Smart Contracts
|
|
|
|
### Core Contracts
|
|
- `Bridge.sol` - Main bridge contract with deposit/withdraw logic
|
|
- `LuxVault.sol` - Asset vault for Lux Network
|
|
- `ETHVault.sol` - Ethereum vault contract
|
|
- `ZooVault.sol` - Multi-asset aggregated vault
|
|
- `LERC4626.sol` - Tokenized vault shares
|
|
|
|
### Wrapped Token Contracts (ERC20B)
|
|
All wrapped tokens follow the ERC20B standard with mint/burn capabilities:
|
|
- Location: `/contracts/contracts/zoo/`
|
|
- Examples: Z.sol, ZETH.sol, ZUSD.sol, ZBNB.sol
|
|
|
|
## API Endpoints
|
|
|
|
### REST API (Port 5000)
|
|
- `GET /api/v1/status` - Bridge operational status
|
|
- `GET /api/v1/chains` - Supported blockchains
|
|
- `GET /api/v1/assets` - Supported tokens
|
|
- `POST /api/v1/quote` - Get transfer quote
|
|
- `POST /api/v1/transfer` - Initiate transfer
|
|
- `GET /api/v1/transfer/{id}` - Transfer status
|
|
|
|
### WebSocket API
|
|
- Subscribe to transfer updates
|
|
- Real-time status notifications
|
|
- Event streaming for bridge operations
|
|
|
|
## Essential Commands
|
|
|
|
### Development
|
|
```bash
|
|
# Clone and install
|
|
git clone https://github.com/luxfi/bridge
|
|
cd bridge
|
|
pnpm install
|
|
|
|
# Install MPC tools
|
|
make install-mpc
|
|
|
|
# Start infrastructure
|
|
make up
|
|
|
|
# Start MPC nodes
|
|
make start-mpc-nodes
|
|
|
|
# Start services
|
|
cd app/bridge && pnpm dev # UI on :3001
|
|
cd app/server && pnpm dev # API on :5000
|
|
```
|
|
|
|
### Infrastructure Management
|
|
```bash
|
|
# Infrastructure
|
|
make up # Start all infra services
|
|
make down # Stop all services
|
|
make logs # View aggregated logs
|
|
|
|
# MPC Operations
|
|
make start-mpc-nodes # Start 3-node MPC network
|
|
make stop-mpc-nodes # Stop MPC nodes
|
|
make generate-mpc-keys # Generate new MPC keys
|
|
lux-mpc-cli status # Check MPC status
|
|
|
|
# Development
|
|
pnpm dev # Start development servers
|
|
pnpm build # Build for production
|
|
pnpm test # Run test suite
|
|
pnpm lint # Run linters
|
|
|
|
# Deployment
|
|
make deploy-testnet # Deploy to testnet
|
|
make deploy-mainnet # Deploy to mainnet
|
|
```
|
|
|
|
## Architecture
|
|
|
|
The bridge uses a multi-layered architecture:
|
|
|
|
1. **Presentation Layer**: React/Next.js UI for user interaction
|
|
2. **API Layer**: Node.js server handling business logic
|
|
3. **MPC Layer**: Distributed nodes for threshold signatures
|
|
4. **Blockchain Layer**: Smart contracts on multiple chains
|
|
5. **Infrastructure Layer**: Supporting services (DB, messaging, KMS)
|
|
|
|
### Data Flow
|
|
1. User initiates transfer on source chain
|
|
2. Bridge contract locks assets in vault
|
|
3. Event emitted and detected by MPC nodes
|
|
4. MPC nodes reach consensus and generate signature
|
|
5. Signed message relayed to destination chain
|
|
6. Destination contract mints wrapped tokens
|
|
7. User receives tokens on destination chain
|
|
|
|
## Key Technologies
|
|
|
|
- **Multi-Party Computation (MPC)**: Distributed key management
|
|
- **Threshold Signatures**: 2-of-3 security model
|
|
- **Smart Contracts**: Solidity with OpenZeppelin
|
|
- **Message Queue**: NATS for inter-node communication
|
|
- **Service Discovery**: Consul for dynamic configuration
|
|
- **Key Management**: HashiCorp Vault
|
|
- **Authentication**: Casdoor (Lux ID)
|
|
- **Database**: PostgreSQL for state management
|
|
- **Container Orchestration**: Docker/Kubernetes
|
|
|
|
## Development Workflow
|
|
|
|
### Local Development
|
|
1. Start infrastructure: `make up`
|
|
2. Initialize MPC network: `make start-mpc-nodes`
|
|
3. Deploy contracts: `cd contracts && npx hardhat deploy`
|
|
4. Start API server: `cd app/server && pnpm dev`
|
|
5. Start tenant UI: `cd app/bridge && pnpm dev` (Vite on :3001)
|
|
6. SDK dev: edit `pkg/bridge/src/` — tenant Vite picks up changes live
|
|
through the workspace symlink. No publish needed for local iteration.
|
|
|
|
### Testing
|
|
- Unit tests: `pnpm test:unit`
|
|
- Integration tests: `pnpm test:integration`
|
|
- Contract tests: `cd contracts && npx hardhat test`
|
|
- E2E tests: `pnpm test:e2e`
|
|
|
|
### Deployment
|
|
1. Build artifacts: `pnpm build`
|
|
2. Deploy contracts: `make deploy-{network}`
|
|
3. Update configuration
|
|
4. Deploy services via CI/CD
|
|
5. Verify deployment: `make verify-deployment`
|
|
|
|
## Configuration
|
|
|
|
### Environment Variables
|
|
```env
|
|
# Network
|
|
NETWORK=mainnet|testnet|local
|
|
CHAIN_ID=31337
|
|
|
|
# Database
|
|
DATABASE_URL=postgresql://user:pass@localhost:5433/bridge
|
|
|
|
# MPC
|
|
MPC_NODE_COUNT=3
|
|
MPC_THRESHOLD=2
|
|
|
|
# KMS (Vault)
|
|
VAULT_ADDR=http://localhost:8200
|
|
VAULT_TOKEN=<token>
|
|
|
|
# Authentication (Lux ID)
|
|
CASDOOR_ENDPOINT=http://localhost:8000
|
|
```
|
|
|
|
### Service Ports
|
|
- Bridge UI (tenant `app/bridge/`): **3001** (Vite dev/preview server)
|
|
- Bridge API (`app/server/`): 5000 (Express)
|
|
- Bridge explorer (`app/explorer/`, legacy): 3002 (Next.js)
|
|
- Bridge explorer (`ghcr.io/luxfi/explorer`, replacement): port-flexible
|
|
- MPC Nodes: 6000-6002 (HTTP), 9090-9092 (gRPC)
|
|
- PostgreSQL: 5433 (bridge), 5434 (auth)
|
|
- Vault (KMS): 8200
|
|
- Lux ID: 8000
|
|
- NATS: 4223
|
|
- Consul: 8501
|
|
|
|
## Security Best Practices
|
|
|
|
1. **Never commit secrets** - Use environment variables
|
|
2. **Validate all inputs** - Prevent injection attacks
|
|
3. **Use secure communication** - TLS 1.3 everywhere
|
|
4. **Implement rate limiting** - Prevent DDoS
|
|
5. **Regular security audits** - Contract and code reviews
|
|
6. **Monitor everything** - Logs, metrics, alerts
|
|
7. **Incident response plan** - Be prepared for emergencies
|
|
|
|
## Monitoring & Debugging
|
|
|
|
### Health Checks
|
|
- Bridge API: `curl http://localhost:5000/health`
|
|
- MPC Nodes: `curl http://localhost:6000/health`
|
|
- Infrastructure: `http://localhost:8501/ui` (Consul)
|
|
|
|
### Debugging Commands
|
|
```bash
|
|
# Check MPC status
|
|
lux-mpc-cli status --url http://localhost:6000
|
|
|
|
# View logs
|
|
tail -f logs/mpc-node-*.log
|
|
|
|
# Database queries
|
|
psql $DATABASE_URL -c "SELECT * FROM transfers WHERE status='pending';"
|
|
|
|
# NATS monitoring
|
|
nats-top -s localhost:4223
|
|
```
|
|
|
|
## Documentation
|
|
|
|
### Comprehensive Documentation Site (Nextra)
|
|
- **Location**: `~/work/lux/bridge/docs/` (Next.js + Nextra)
|
|
- **Pages**: `docs/pages/*.mdx`
|
|
- **Build**: `cd docs && pnpm install && pnpm build`
|
|
- **Dev**: `cd docs && pnpm dev` → http://localhost:3000
|
|
|
|
### Key Documentation Files
|
|
- `docs/LLM.md` — this file, canonical AI/onboarding doc (single source of truth)
|
|
- `docs/LUX-ID-INTEGRATION.md` — Casdoor-based unified auth integration
|
|
- `pkg/bridge/README.md` — consumer-facing SDK docs (`mountBridge`, `BridgeConfig`)
|
|
- Top-level `README.md` — quick-start + architecture pointer (kept consistent with this file)
|
|
|
|
The legacy migration .md files (BRIDGE-STATUS, LOCAL-SETUP, the four MPC
|
|
migration notes, DEPLOYMENT, CI-CD-DOCKER-IMAGES) were deleted in v1.1.12
|
|
(commit `b04e805`, issue #391) — they described an architecture from
|
|
before the SDK rationalization and confused new readers. Migration
|
|
history lives in `git log` and the GH release notes.
|
|
|
|
## Context for All AI Assistants
|
|
|
|
This file (`LLM.md`) is symlinked as:
|
|
- `AGENTS.md`
|
|
- `CLAUDE.md`
|
|
- `QWEN.md`
|
|
- `GEMINI.md`
|
|
|
|
All files reference the same knowledge base. Updates here propagate to all AI systems.
|
|
|
|
## Rules for AI Assistants
|
|
|
|
1. **ALWAYS** update LLM.md with significant discoveries
|
|
2. **NEVER** commit symlinked files (AGENTS.md, CLAUDE.md, etc.) - they're in .gitignore
|
|
3. **NEVER** create random summary files - update THIS file
|
|
|
|
---
|
|
|
|
**Note**: This file serves as the single source of truth for all AI assistants working on this project.
|