mirror of
https://github.com/luxfi/bridge.git
synced 2026-09-24 00:26:46 +00:00
fix(xrp): gate withdrawal on destination-tag plumbing; reserve minimum; honest trust docs
Red review of 2dd8da86 found the real blocker: XRP outbound has no DestinationTag
(internal/txassembler/xrp.go builds Payment without one; Swap/API have no tag field),
so a payout to an exchange's pooled deposit address is credited to nobody and is
irreversible. Set XRP isWithdrawalEnabled:false on both nets until the tag is plumbed
end-to-end + X-addresses are rejected — deposit-only, matching the BTC precedent.
Also from the review:
- per-XRP minUSD:30 floor — a fresh per-swap deposit address must hold the >=10-XRP
AccountRoot base reserve or it never confirms (token-denominated min is the robust
follow-up; USD floor is the interim proxy).
- Correct the depositcheck + deposit_watcher trust-model docstrings: depositcheck.Check
is the SOLE runtime gate that advances a swap to a release-pool payout — the single
upstream RPC is load-bearing/trusted, not 'secondary verification'. Documents the
systemic multi-RPC-quorum hardening follow-up so operators point only at trusted RPCs.
Inbound checkXRP is unchanged (red verified it solid). cmd/bridge + depositcheck green.
This commit is contained in:
@@ -31,9 +31,15 @@ import (
|
||||
// The MPC ceremony driver (Phase 4.6) is what picks up
|
||||
// bridge_transfer_pending swaps from here.
|
||||
//
|
||||
// Trust model: depositcheck queries public source-chain RPCs. The
|
||||
// result is best-effort. BridgeVM's own auditor (when deployed) is
|
||||
// the authority for slashing; this layer is for UI / SDK polling.
|
||||
// Trust model (IMPORTANT — load-bearing): this watcher is the sole
|
||||
// automated forward producer of bridge_transfer_pending for deposit-address
|
||||
// swaps, and that state directly drives a release-pool payout. So the single
|
||||
// upstream RPC that depositcheck.Check consults IS trusted at runtime: an RPC
|
||||
// that lies about a balance triggers a payout for a deposit that never landed.
|
||||
// There is no runtime BridgeVM gate today (--resync-swaps is a one-shot boot
|
||||
// reconciliation). Hardening this to a multi-RPC quorum / on-chain proof is a
|
||||
// platform-wide follow-up; until then, operators MUST point depositcheck at
|
||||
// trusted endpoints only.
|
||||
|
||||
// DepositChecker is the 1-method interface the watcher needs. Pulls
|
||||
// the dependency to an interface so tests can swap in a fake without
|
||||
|
||||
@@ -270,16 +270,20 @@ tokens:
|
||||
isWithdrawalEnabled: false
|
||||
|
||||
# XRP settles on secp256k1 (same curve as the live ETH/BTC pipeline) — no
|
||||
# mpcd FROST/Ed25519 gap. Inbound detection via rippled account_info
|
||||
# (internal/depositcheck); outbound via the XRP release pool + assembler +
|
||||
# rippled submit. Operator note: pre-fund each XRP release wallet with
|
||||
# >= 10 XRP (AccountRoot base reserve) + per-payment fee before payouts.
|
||||
# mpcd FROST/Ed25519 gap. Inbound detection via rippled account_info, last
|
||||
# validated ledger (internal/depositcheck). Deposit-only for now, matching
|
||||
# the BTC precedent: WITHDRAWAL IS GATED until the destination tag is plumbed
|
||||
# end-to-end (API -> Swap -> XRPSpec -> Payment) and X-addresses are rejected
|
||||
# at create time — a tag-less payout to an exchange's pooled deposit address
|
||||
# is credited to nobody and is irreversible (red finding, c603f579 review).
|
||||
# A fresh per-swap deposit address must also hold the >= 10-XRP AccountRoot
|
||||
# base reserve, so the per-XRP minimum below stays above it.
|
||||
- asset: XRP
|
||||
name: XRP
|
||||
decimals: 6
|
||||
network: XRP_MAINNET
|
||||
isDepositEnabled: true
|
||||
isWithdrawalEnabled: true
|
||||
isWithdrawalEnabled: false
|
||||
|
||||
limits:
|
||||
minUSD: 10
|
||||
@@ -288,3 +292,8 @@ limits:
|
||||
USDT: {minUSD: 10, maxUSD: 100000}
|
||||
USDC: {minUSD: 10, maxUSD: 100000}
|
||||
DAI: {minUSD: 10, maxUSD: 100000}
|
||||
# XRP per-swap deposit addresses must hold the >= 10-XRP AccountRoot base
|
||||
# reserve to exist on-ledger; a sub-reserve deposit never confirms. Keep the
|
||||
# minimum above the reserve (token-denominated minimum is the robust
|
||||
# follow-up; this USD floor is the interim proxy).
|
||||
XRP: {minUSD: 30, maxUSD: 100000}
|
||||
|
||||
@@ -280,18 +280,28 @@ tokens:
|
||||
|
||||
# XRP settles on secp256k1 — the SAME curve as the live ETH/BTC pipeline, so
|
||||
# it has no mpcd FROST/Ed25519 gap (unlike SOL/TON). Inbound detection
|
||||
# (rippled account_info) is implemented in internal/depositcheck; the
|
||||
# r-address is derived locally from the ecdsa_pub_key mpcd already returns.
|
||||
# Outbound is wired (XRP release pool + assembler + rippled submit).
|
||||
# Operator note: pre-fund each XRP release wallet with >= 10 XRP (the
|
||||
# AccountRoot base reserve) + per-payment fee before enabling payouts.
|
||||
# (rippled account_info, last validated ledger) is implemented in
|
||||
# internal/depositcheck; the r-address derives locally from the ecdsa_pub_key
|
||||
# mpcd already returns. Deposit-only for now, matching the BTC precedent:
|
||||
# WITHDRAWAL IS GATED until the destination tag is plumbed end-to-end
|
||||
# (API -> Swap -> XRPSpec -> Payment) and X-addresses are rejected at create
|
||||
# time — a tag-less payout to an exchange's pooled deposit address is
|
||||
# credited to nobody and is irreversible (red finding, c603f579 review).
|
||||
# A fresh per-swap deposit address must also hold the >= 10-XRP AccountRoot
|
||||
# base reserve, so the per-XRP minimum below stays above it.
|
||||
- asset: XRP
|
||||
name: XRP
|
||||
decimals: 6
|
||||
network: XRP_TESTNET
|
||||
isDepositEnabled: true
|
||||
isWithdrawalEnabled: true
|
||||
isWithdrawalEnabled: false
|
||||
|
||||
limits:
|
||||
minUSD: 1
|
||||
maxUSD: 100000
|
||||
per:
|
||||
# XRP per-swap deposit addresses must hold the >= 10-XRP AccountRoot base
|
||||
# reserve to exist on-ledger; a sub-reserve deposit is rejected network-side
|
||||
# and never confirms. Keep the minimum above the reserve (a token-denominated
|
||||
# minimum is the robust follow-up; this USD floor is the interim proxy).
|
||||
XRP: {minUSD: 30, maxUSD: 100000}
|
||||
|
||||
@@ -7,12 +7,16 @@
|
||||
// and returns whether the balance at the address meets or exceeds the
|
||||
// required amount.
|
||||
//
|
||||
// Trust model: each upstream RPC / REST endpoint is treated as
|
||||
// best-effort transport. The authoritative "swap can advance to
|
||||
// signing" decision is made by b-chain (BridgeVM); this package is
|
||||
// only useful for ops diagnostics, latency monitoring, or as a
|
||||
// secondary verification layer. Production swap-state advancement
|
||||
// belongs to BridgeVM's own watcher, not to anything in here.
|
||||
// Trust model (IMPORTANT — load-bearing): the deposit watcher
|
||||
// (cmd/bridge/deposit_watcher.go) uses Check to advance a swap from
|
||||
// user_deposit_pending to bridge_transfer_pending, which drives a
|
||||
// release-pool payout. So the single upstream RPC per network IS
|
||||
// trusted at runtime — an RPC that lies about a balance triggers a
|
||||
// payout for a deposit that never landed. This is identical across
|
||||
// every chain family here (one hardcoded endpoint, no quorum, no
|
||||
// confirmation count). Hardening to a multi-RPC quorum / on-chain
|
||||
// proof is a platform-wide follow-up; until then, only point this at
|
||||
// trusted endpoints (RPCURLOverrides) in production.
|
||||
//
|
||||
// Returns from `Check`:
|
||||
// - (true, nil) — confirmed deposit
|
||||
|
||||
Reference in New Issue
Block a user