zeekay eabb1392dc feat(app/server): real Utila cosigner implementation + 13 tests (issue #386)
Replaces the runUtila STUB with a real evm_personal_sign attestation
flow against @luxfi/utila Connect-RPC. After this commit the layered
cosigner feature is feature-complete for BOTH providers — no stubs
remain in app/server/src/domain/cosigners.ts.

Flow:
  1. Build per-tenant gRPC client via createGrpcClient + serviceAccount-
     AuthStrategy with { email: intent.org_id, privateKey: () => secretPem }
     — mirrors the env-singleton client in domain/utila.ts but isolates
     each tenant's auth context so multiple tenants cosign in parallel
     without singleton contention.

  2. initiateTransaction({
       parent: vaults/<intent.vault_id>,
       details: { details: { case: 'evmPersonalSign', value: {
         fromAddress: intent.client_id, messageHex: opts.txHash
       } } },
       priority: NORMAL, note: 'lux-bridge cosign swap=<id>'
     })

     vault_id is REQUIRED — utila scopes initiateTransaction by parent
     vault. Missing vault_id returns 'failed' before any RPC.

  3. Poll getTransaction(name) every 1.5s, partition Utila's
     TransactionState_Enum (proto path:
     pkg/utila/src/lib/gen/utila/api/v1/transactions_pb.ts) into:

       approved   SIGNED=4, AWAITING_PUBLISH=5, PUBLISHED=6,
                  MINED=7, CONFIRMED=13
       rejected   DECLINED=9, CANCELED=11, DROPPED=12, EXPIRED=15
       failed     FAILED=8, REPLACED=10

     If state is in 'approved' but evmMessage.signature isn't populated
     yet (Utila publishes the sig in a later state transition), keep
     polling — don't bail. Intermediates (AWAITING_APPROVAL=2,
     AWAITING_POLICY_CHECK=14, AWAITING_SIGNATURE=3) also keep polling.

  4. 60s timeout ceiling (UTILA_COSIGNER_TIMEOUT_MS env override).
     Dynamic read on each call so config picks up without a restart.

  5. Return CosignResult with signature converted from Uint8Array proto
     bytes to 0x-prefixed hex; externalId = Utila transaction.name.

Edge cases covered by 13 tests (mocked @luxfi/utila):
  - happy: SIGNED + signature → approved
  - happy: CONFIRMED (later state, sig already attested) → approved
  - happy: SIGNED-without-sig keeps polling, picks up next tick
  - rejected: DECLINED, CANCELED, EXPIRED, DROPPED — all map to rejected
  - failed: FAILED proto state
  - failed: initiateTransaction throws (auth: bad jwt)
  - failed: initiateTransaction returns no transaction.name
  - failed: timeout (AWAITING_* indefinitely)
  - failed: missing vault_id → fail BEFORE any RPC call
  - failed: env-var fallback unset → clear KMS-TODO error
  - wire-shape assertion: initiateTransaction is called with the right
    parent + evmPersonalSign + fromAddress + messageHex + note

Test infra:
  - vitest.config.ts gains a resolve.alias mapping @luxfi/utila to its
    workspace src/grpc-client.ts. The published 3.0.0 rollup-built
    dist/ is what npm consumers see, but the workspace package's
    local rollup config has a long-standing outDir-vs-file-path issue
    that prevents in-place builds. Vitest resolves the TS source
    directly and runtime production is unaffected.

Tests: 91 → 104 total (58 SDK + 46 server).
  Server breakdown: 16 validator + 10 Fireblocks + 7 persistence + 13 Utila.

After this commit there are no STUBs in the cosigner module. The
remaining work on #386 is the KMS round-trip in fetchCosignerSecret
(env-var fallback works for dev; production needs KMS path read).

User correction: 'utila was already 100% complete... please make sure
it's done' — addressed by replacing the runUtila STUB with the real
implementation that reuses the @luxfi/utila Connect-RPC machinery
already in the workspace.
2026-05-22 17:34:34 -07:00
2024-01-08 22:05:19 +01:00
2025-12-24 14:19:52 -08:00
2025-12-24 14:19:52 -08:00
2026-03-27 19:44:20 -07:00
2025-12-31 22:21:30 -08:00
2025-08-03 15:52:32 -05:00

Lux Bridge

Bridge monorepo for Lux Network — a decentralized cross-chain bridge using Multi-Party Computation (MPC) for secure asset transfers.

Embed the bridge SDK

To embed bridge.lux.network in another app, install @luxfi/bridge:

pnpm add @luxfi/bridge react react-dom
import { mountBridge } from '@luxfi/bridge'

mountBridge({
  config: {
    apiHost: 'https://api.bridge.lux.network',
    env: 'mainnet',
    brand: { name: 'Lux Bridge', primaryColor: '#0066ff' },
  },
})

@luxfi/bridge is the only package consumers import. All @luxbridge/* packages are internal workspace deps and are never published.

Package scopes

Scope Visibility Role
@luxfi/bridge public (npm) Canonical SDK — embed bridge UI into a host app.
@luxfi/core public (npm) Shared types (Asset, Network, Contract, ...).
@luxfi/threshold public (npm) T-Chain MPC threshold-signature SDK.
@luxfi/utila public (npm) Utila protobuf client.
@luxbridge/lux-tenant private (workspace) Slim Vite SPA for bridge.lux.network — consumes @luxfi/bridge.
@luxbridge/settings private (workspace) Network + asset registry.
@luxbridge/explorer private (workspace) On-chain explorer UI.
@luxbridge/server private (workspace) Bridge API daemon.
@luxbridge/ui-automation private (workspace) Selenium UI test harness.

One canonical SDK name. One mount function. One config shape.

Prerequisites

  • Node.js v20+
  • pnpm v9.15.0+
  • Go 1.22+ (for MPC tools)
  • Docker (for infrastructure services)

Quick Start

1. Install Dependencies

# Install pnpm if needed
npm install -g pnpm

# Install project dependencies
pnpm install

# Install MPC tools via Go
make install-mpc

2. Start Infrastructure

# Start infrastructure services (Lux ID, KMS, databases, etc.)
make up

This starts:

  • Lux ID (Casdoor) - Port 8000 - Unified authentication
  • KMS (Vault) - Port 8200 - Key management
  • NATS - Port 4223 - Messaging
  • Consul - Port 8501 - Service discovery
  • PostgreSQL - Port 5433 - Bridge database
  • Lux ID DB - Port 5434 - Auth database

3. Start MPC Network

# Start 3-node MPC network
make start-mpc-nodes

This runs MPC nodes on:

  • Node 0: HTTP 6000, gRPC 9090
  • Node 1: HTTP 6001, gRPC 9091
  • Node 2: HTTP 6002, gRPC 9092

4. Start Application Services

In separate terminals:

# Terminal 1: Bridge API Server
cd app/server && pnpm dev

# Terminal 2: Bridge UI
cd app/bridge && pnpm dev

5. Access Services

Architecture

Tenant app (Lux, Hanzo, Zoo, …)
  imports @luxfi/bridge  +  @<org>/brand
                │
                ▼
    @luxfi/bridge (pkg/bridge)
    Bridge UI inlined under src/app/
    Direct deps: @hanzo/gui, @luxfi/threshold, wagmi, viem
                │
        ┌───────┴────────┐
        ▼                ▼
   app/server      @luxfi/threshold
   Express+Prisma  MPC SDK (also consumed by app/server)
                          │
                          ▼
                  m-chain (public MPC, 2-of-3 threshold)
                  + optional private cluster (treasury fees)
                  + optional layered cosigners (Utila / Fireblocks)

See docs/LLM.md for the full topology, MPC protocol list (cggmp21, frost, bls, doerner, pulsar, corona, magnetar), layered-cosigner pattern, and explorer-migration plan.

Key Features

  • MPC Security: 2-of-3 threshold signatures (also PQ-safe lattice variants)
  • Layered cosigners (since SDK v1.0.3): optional Utila / Fireblocks cosign on top of native MPC for institutional flows
  • Multi-Chain: Support for EVM and non-EVM chains
  • Unified Auth: Lux ID (Casdoor) integration
  • Key Management: Secure key storage in KMS
  • Service Discovery: Consul for dynamic configuration

Development

# Run tests
pnpm test

# Check MPC status
lux-mpc-cli status --url http://localhost:6000

# View logs
tail -f logs/mpc-node-0.log

# Stop services
make stop-mpc-nodes  # Stop MPC
make down           # Stop infrastructure

Documentation

  • docs/LLM.md — canonical AI/onboarding doc: architecture, package layout, MPC topology, runtime config, publishing status, layered cosigners, explorer migration plan
  • docs/LUX-ID-INTEGRATION.md — Lux ID (Casdoor) auth integration
  • pkg/bridge/README.md — consumer-facing SDK README (mountBridge, BridgeConfig, all config blocks)

For the new bridge dev: issue #392 is the entry point — read order, repo map, open issues by priority.

Tips

Since "pnpm" is a finger twister, many people alias it to "pn". For example, with bash, put alias pn='pnpm' in .bashrc.

S
Description
▼ Bridge, powered by trustless MPC network and privacy focused Teleport protocol.
Readme BSD-3-Clause
260 MiB
Languages
TypeScript 52.2%
Go 40.9%
Solidity 2.8%
JavaScript 1.8%
Shell 1.4%
Other 0.8%