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.