Commit Graph
13 Commits
Author SHA1 Message Date
zeekay 75c8c1bb78 feat(bridge): humanize XRP engine_result errors for SDK + UI
broadcast_driver.go::humanizeBroadcastErr now recognizes the most
common XRP engine_result codes and renders them as one-line user-
facing messages, matching the EVM-side humanization pattern:

  tecINSUFFICIENT_FUNDS / tecUNFUNDED → "Insufficient funds in release
                                          address — fund the MPC address"
  tecNO_DST                           → "XRP destination account is
                                          not activated — recipient must
                                          hold ≥10 XRP reserve"
  tefPAST_SEQ                         → "XRP sequence already used —
                                          bridge will requery and retry"
  tem*                                → "XRP transaction was malformed
                                          — bridge will retry"
  telINSUF_FEE_P                      → "XRP network fee too low for
                                          current load — bridge will retry"

The SDK + UI surface these from Swap.LastError so users see the
actionable cause (fund the wallet, activate the destination, etc.)
instead of the raw rippled error string.

Test: TestHumanizeBroadcastErr_XRPEngineResults covers all five new
mappings plus regression for the existing EVM mappings.
2026-05-27 18:15:03 -07:00
zeekay df4055fc25 feat(bridge): XRP end-to-end — txassembler, mchain pubkey, signing driver, release pool
internal/txassembler/xrp.go:
- XRPProvider interface + per-network XRPNetwork config (LastLedgerWindow,
  fee override, canonical-sig flag).
- Assembler.PreSignXRP: pulls Sequence + suggested fee from rippled,
  embeds the release wallet's compressed-secp256k1 pubkey, returns
  the 32-byte SHA-512Half (via internal/xrpl) the MPC quorum signs.
- Assembler.FinalizeXRP: parses (r, s) from the MPC's 65-byte R||S||V
  hex (ignores recovery byte — XRPL doesn't need ecrecover), DER-encodes
  with low-s canonicalization, attaches as TxnSignature, returns the
  wire blob (hex) + canonical XRPL tx hash.
- XRPRequiredDrops: amount + fee + 10-XRP reserve, used by the gas
  pre-check to refuse signing when the wallet can't cover.
- xrpFloatToDrops with the same string-format trick floatToWei uses,
  avoiding IEEE noise at 6-decimal native XRP precision.

internal/mchain/client.go:
- Wallet now carries ECDSAPubKeyHex (compressed-secp256k1 hex). EVM
  paths ignore it; XRP signing needs it on the wire.
- pickAddress(XRP) derives the canonical r-address from the ECDSAPubKey
  via internal/xrpl.AddressFromPubKey instead of falling back to the
  ETH slot. Fallback retained for legacy mocks without the pubkey field.

cmd/bridge/xrp_provider.go:
- HTTP wrapper over rippled JSON-RPC (account_info, fee). Returns
  (Sequence, validated_ledger_index) and the open_ledger_fee/median
  fee / base_fee chain. Treats actNotFound on balance lookup as
  "wallet not activated → balance 0" so the gas pre-check correctly
  reports an unactivated release wallet.

cmd/bridge/release_pool.go:
- ReleasePoolEntry gains Family + PubKeyHex fields (round-trips through
  JSON; pre-XRP entries leave them empty).
- AcquireForFamily filters entries by family — the signing driver now
  picks an XRP wallet for XRP destinations and an EVM wallet for EVM.
  Falls back to family-agnostic Acquire when no per-family match.
- SizeByFamily + Reload helpers so a parallel XRP-pool bootstrap can
  share the same store as the EVM pool without re-indexing.

cmd/bridge/signing_driver.go:
- signOne dispatches on mchain.AddressTypeFor(DestinationNetwork). XRP
  routes to signOneXRP, which mirrors the EVM ceremony but: builds a
  Payment via Assembler.PreSignXRP, runs an XRP gas pre-check (balance
  vs amount+fee+reserve) instead of eth_getBalance, hands the
  SHA-512Half digest to the MPC, then Assembler.FinalizeXRP emits the
  signed wire blob.
- xrpGasPrecheck short-circuits to SwapStatusFailedInsufficientReleaseGas
  with a humanized "balance=X drops, required=Y drops" reason. Probe
  failures DON'T block the swap (best-effort, broadcast layer still
  retries on transient rippled flakes).

cmd/bridge/main.go:
- New flags: --xrp-release-pool-size (default 5),
  --xrp-release-pool-mint-network (default XRP_TESTNET),
  --xrp-release-balance-threshold-drops (default 10 XRP).
- XRPProviderClient instantiated once, shared between the assembler
  (for PreSignXRP) and the gas pre-check (via assembler.XRPProviderFor).
- XRP pool minted on top of the existing EVM pool (via a helper-pool
  bound to the same store + Reload), so a single ReleasePool object
  serves both families.
- /health surfaces pool sizes broken out by family.

Tests:
- xrpl: address derivation round-trip, bad-pubkey rejection, DER
  encoding + low-s flip, PreSign digest matches SHA512Half(STX||body),
  full PreSign→Finalize round-trip through the rubblelabs parser.
- broadcast: tesSUCCESS happy path, terQUEUED retryable, tecINSUFFICIENT_FUNDS
  + tecNO_DST fatal, temBAD_AMOUNT malformed, telINSUF_FEE_P retryable,
  rippled high-level errors, classifyEngineResult table.
- txassembler: PreSignXRP populates Sequence / Fee / LastLedger,
  fee floor + pin, bad-input rejection, FinalizeXRP round-trip,
  XRPRequiredDrops math, xrpFloatToDrops table.
- mchain: XRP r-address derivation from ECDSAPubKey, legacy fallback
  to ETH slot, bad-hex rejection.
- release pool: XRP-family Bootstrap + entry shape + PubKey roundtrip,
  AcquireForFamily filtering, no-match returns ErrEmptyPool.
- signing driver: XRP happy path advances to broadcasting with
  populated DestRawTx, insufficient balance short-circuits, missing
  PubKeyHex rolls back with readable LastError.
- xrp_provider: account_info / fee / balance stubs, actNotFound → 0,
  rippled error surfacing.
2026-05-27 18:13:43 -07:00
zeekay b49e3e6552 feat(bridge): signing driver — release-pool rotation + gas pre-check
Wires the static release pool into the MPC signing driver and adds an
eth_getBalance pre-check that short-circuits swaps before the (slow)
ceremony when the release wallet can't cover destination-chain gas.

signing_driver.go:

- SetReleasePool / SetGasProbe inject the pool + balance probe at
  startup. Without a pool, the driver falls back to extracting the
  wallet ID from Swap.DepositAddress (v1 behaviour).
- signOne() now picks the release wallet from the pool when one is
  configured AND has entries, persisting ReleaseWalletID +
  ReleaseAddress on the Swap so the broadcast + refund drivers can
  read them later. The chosen address goes into the assembler's
  PreSign as SenderAddress so the unsigned tx targets the correct
  nonce + signs against the pool wallet.
- AFTER PreSign (which gave us the exact GasPrice * GasLimit + Value),
  the gas pre-check queries the release wallet's destination-chain
  balance. If balance < required cost, the swap moves to
  SwapStatusFailedInsufficientReleaseGas with a clear LastError
  ("Release wallet 0x… has insufficient balance: balance=… required=…
   gasCost=… value=… short=…"). The MPC ceremony is skipped.
  Probe failures are treated as best-effort: log + continue rather
  than refuse to sign a swap we couldn't verify.
- New ShortCircuited counter distinct from Failures (operator
  dashboards differentiate "operator needs to fund a pool wallet"
  from "transient MPC / RPC flake").

main.go:

- --release-pool-size (default 10), --release-pool-mint-network
  (default LUX_TESTNET), --release-balance-threshold-wei
  (default 1e17 wei = 0.1 LUX), --disable-gas-precheck flag, plus
  --mpc-dashboard-url / --mpc-dashboard-token / --mpc-dashboard-api-key
  for the dashboard signing wiring landed in commit 1.
- Bootstrap path: instantiates the balance probe, then the
  ReleasePool, then calls Bootstrap (which lazily mints the
  remaining wallets up to pool size, idempotent across restarts).
  Wires both probe and pool into the signing driver.
- /health endpoint exposes the pool size + threshold for ops triage.

gas_precheck_test.go covers the three core behaviours:
- insufficient balance → swap transitions to
  failed_insufficient_release_gas with no signer call
- sufficient balance → swap proceeds normally to broadcasting
- probe error → swap proceeds (best-effort, broadcast retry still works)

Together with the earlier dashboard-sign + pool foundation commits,
this closes the testnet refund-loop issue: the bridge now (a) speaks
the real /v1/mpc/sign endpoint, (b) rotates through pre-funded
release wallets, and (c) refuses to spend the MPC ceremony on swaps
that would fail on broadcast.
2026-05-27 17:13:57 -07:00
zeekay fbdfc9a60e feat(bridge): release-wallet pool + balance probe foundation
Introduces a static pool of pre-funded MPC release wallets so a single
under-funded wallet doesn't block all subsequent swaps. Previous
behaviour reused the deposit wallet for the release tx — a fresh
keygen with zero destination-chain balance.

This commit adds the foundation:

- release_pool.go: ReleasePool with round-robin Acquire(), lazy
  Bootstrap() that mints wallets up to a configured size via the
  Keygener interface (*mchain.Client satisfies). Pool entries persist
  across restarts via the new ReleasePoolStore interface.
- ReleasePoolStore implementations on both InMemoryStore (test/dev)
  and ZapStore (prod, persisted under releasepool:NNNNNN keys, sorted
  by zero-padded index for canonical iteration order).
- balance_probe.go: BalanceProbeClient — small JSON-RPC wrapper around
  eth_getBalance that reuses the broadcast + depositcheck RPC URL
  tables (with overrides). Used by both the pool's low-balance
  alerter and by the gas pre-check landing in a follow-up commit.
  Non-EVM networks return ErrFamilyNotSupportedForBalance.
- Swap.ReleaseWalletID + Swap.ReleaseAddress fields so the broadcast
  + refund drivers know which pool wallet signed each swap.
- SwapStatusFailedInsufficientReleaseGas — new terminal state for
  swaps the gas pre-check refuses to enter the MPC ceremony for.
- release_pool_test.go covers Bootstrap idempotency (reload doesn't
  re-mint), pool growth, keygen failure surfacing, round-robin
  fairness, balance-probe call gating on the threshold being set, and
  Entries() copy isolation.

No wiring into the signing driver yet — that's the next commit so
the signing-driver change is small and reviewable in isolation.
2026-05-27 17:13:11 -07:00
venuswhispers ab4adfde5a feat: refunding has been working 2026-05-28 00:10:03 +02:00
venuswhispers ca39b78776 feat: simple base swap is working now 2026-05-27 22:04:22 +02:00
venuswhispers 42f8a95191 feat: cmd/bridge api go has been updated 2026-05-27 17:41:29 +02:00
venuswhispers b76c3ecf2a feat: added mpc sign option 2026-05-27 14:49:55 +02:00
venuswhispers fc6a430f02 feat: base bridge functions like swap function has been set almost 2026-05-25 23:08:12 +02:00
zeekay 52c437b00e phase2-r2: scrub bridge3 / @luxbridge/app / lux-bridge references
Sweep through configs, docs, and ignore files after the structural
collapse in the previous commit. Updates fall into three buckets:

1) Restore the Dockerfile for app/bridge/
   - app/bridge/Dockerfile: new file, Vite SPA → ghcr.io/hanzoai/spa,
     same shape as the pre-collapse legacy Dockerfile, build context
     is repo root so @luxfi/bridge (workspace:*) resolves.
   - cmd/bridge/Dockerfile: switch UI-build stage to the workspace
     build context so the new app/bridge (which depends on the
     sibling pkg/bridge package) actually installs.
   - Makefile build-ui target: drop the per-app build context for
     the same reason — context must be repo root.

2) Update workspace docs to the new layout
   - README.md: rename @luxbridge/app row → @luxbridge/lux-tenant,
     port 3000 → 3001 (the slim tenant's vite config uses 3001).
   - docs/LLM.md: drop the bridge3 row, rename app/bridge row to
     @luxbridge/lux-tenant, update dev port.
   - docs/{BRIDGE-STATUS,LOCAL-SETUP,CI-CD-DOCKER-IMAGES}.md:
     `docker build … ./app/bridge` → `… -f app/bridge/Dockerfile .`
     so docs reflect the workspace-rooted build context.
   - .github/workflows/publish.yml: drop @luxbridge/app and
     @luxbridge/app-v3 from the private-packages comment.

3) Delete AI-slop status reports + test harness
   - CI_STATUS_REPORT.md, DEPLOYMENT_STATUS.md, WORKFLOW_AUDIT.md,
     WORKFLOWS_SUMMARY.md — stale and referenced bridge3.
   - test-bridge-ui.js — top-level Puppeteer/Playwright probe of the
     deleted legacy UI; not a workspace test.
   - .dockerignore: drop the test-bridge-ui.js entry now that the
     file no longer exists.

Out of scope (untouched): pkg/bridge/src/{Bridge,config,types,mount}
and pkg/bridge/src/app/ — Phase 3 R2 is working there. The
`@luxbridge/app` mentions in those files are historical comments
explaining the prior workspace cycle, not live references.
2026-05-21 15:15:37 -07:00
zeekayandGitHub c09c6eac62 chore: bump Go toolchain to 1.26.3 (#383)
Pin Go version to 1.26.3 across go.mod, CI workflows, and Dockerfiles
for canonical alignment with the rest of the luxfi/* stack.
2026-05-16 16:42:28 -07:00
zeekay 96215e2f7d bridge: enforce LUX_STRICT_PQ_BRIDGE profile
Adds BridgeProfile + LuxStrictPQBridgeProfile (Pulsar-M-65 on both sides,
STARK_FRI_SHA3_PQ proofs, ML-DSA-87 admin, ML-DSA-65 multisig pause).
Forbids ECDSA admin, BLS aggregate, KZG commitments, Groth16 wrappers,
and pairing precompiles in strict mode.

post_quantum_end_to_end label exposed via RPC + block-explorer metadata.

Classical-compat bridges remain available under BridgeClassicalCompat
profile but are explicitly labeled non-E2E-PQ.

Integration sites:
- bridge/profile.go is the bridge-package audit gate (mirrors consensus
  type, owns the bridge_classical_compat_total counter).
- opnet-watcher/signer.go: SignRaw, SignTransaction, signEthMessage now
  consult profile.RefuseClassicalAdmin before any ecrecover / EIP-191 /
  secp256k1 sign — strict-PQ refuses, classical-compat increments
  the metric.
- cmd/bridge/api.go: exposes bridge_getProfile JSON-RPC, GET
  /v1/bridge/profile, and /metrics (Prometheus, including
  bridge_classical_compat_total{primitive=...}).
- --profile flag (BRIDGE_PROFILE env) selects strict-pq | classical-compat
  on both cmd/bridge and opnet-watcher.

Patch-bump.
2026-05-10 20:07:49 -07:00
zeekay a47f58bc54 feat(bridge): unified Go binary — embedded SPA + native networks/tokens/limits, proxied swap/quote/rate
Mirrors the luxfi/explorer pattern: cmd/bridge/main.go embeds the SPA
build via go:embed and exposes /v1/bridge/* on a single port. Read paths
(networks, tokens, exchanges, limits) are answered natively from a YAML
config — no Postgres. MPC-heavy paths (quote, rate, swaps, explorer)
reverse-proxy to the legacy Node backend until the native port lands.

Image: cmd/bridge/Dockerfile produces ~25 MB distroless.
Config: cmd/bridge/networks.example.yaml.
Migration target: Hanzo Base (SQLite) + luxfi/mpc direct calls.
2026-05-05 21:24:03 -07:00