Skip to content

chore(utxo): finish migration off coinselect for BCH/LTC/DOGE/DASH #1746

Description

@Thorian1te

Context

UTXO selection currently exists in two stacks:

Stack Where Used for
@xchainjs/xchain-utxo strategies UtxoSelector (BnB, SingleRandomDraw, Accumulative, LargestFirst, SmallFirst) Keystore transfer via prepareTxEnhanced on BTC/BCH/LTC/DOGE/DASH; Bitcoin Ledger
npm coinselect/accumulative Per-chain buildTx / Utils.buildTx Legacy prepareTx / buildTx; BCH/LTC/DOGE/DASH Ledger still call prepareTx

Bitcoin was cleaned up first (prepareTxEnhanced end-to-end). Other UTXO chains got enhanced bolted on but kept the old coinselect path for compatibility. Dead leftover on bitcoin: coinselect in package.json + src/modules.d.ts (no imports).

Related: monorepo pin bump #1745 (3.1.123.1.13) is hygiene only — 3.1.13 only changes split.js; we never call split, only accumulative (identical across those versions).

coinselect vs xchain-utxo (short)

  • coinselect: output-list API, P2PKH-ish size estimates, default entry is blackjack→accumulative; we only import accumulative.js.
  • xchain-utxo: amount + extraOutputs + preferences; multi-strategy tournament; fixed P2WPKH-ish vbyte constants (68/31/10); typed UtxoError.
  • Fee/input counts can differ at the margin for the same UTXO set because size models differ.
  • Full write-up lived in internal review notes; this issue is the actionable follow-up.

Goal

One selection path for all UTXO chains: selectUtxosForTransaction / prepareTxEnhanced only. Remove npm coinselect from the monorepo when nothing imports it.


Current call graph (as of 2026-08)

Chain Keystore transfer Ledger transfer Still imports coinselect?
Bitcoin prepareTxEnhanced prepareTxEnhanced Dep only (unused)
BCH prepareTxEnhanced prepareTx → coinselect Yes
LTC prepareTxEnhanced prepareTx → coinselect Yes
DOGE prepareTxEnhanced prepareTx → coinselect Yes
DASH prepareTxEnhanced prepareTx → Utils.buildTx → coinselect Yes
ZEC own / utxo base (no coinselect) No

Implementation plan

Work one chain at a time (Ledger is the risky surface). Prefer PR order: LTC → DOGE → BCH → DASH → bitcoin cleanup → monorepo delete.

Phase 0 — Housekeeping (optional small PR)

  • Bitcoin: remove unused "coinselect" dependency and packages/xchain-bitcoin/src/modules.d.ts (coinselect shim only). Patch changeset.
  • Document in each remaining package that prepareTx / buildTx are deprecated in favor of prepareTxEnhanced (already true in several places — keep consistent).

Phase 1 — Point Ledger at enhanced (per chain)

For each of LTC, DOGE, BCH, DASH:

  1. Change ClientLedger.transfer (and transferMax if present) to call prepareTxEnhanced (or the same path keystore uses), not prepareTx.
  2. Confirm Ledger signing still receives the right shape:
    • PSBT / raw tx encoding
    • input list / txHex / witnessUtxo as required by @ledgerhq/hw-app-btc (or chain-specific app)
    • change address / memo / fee rate behavior unchanged from product POV
  3. Manual or e2e smoke with a test device/phrase on testnet if available.
  4. Keep prepareTx temporarily as a thin deprecated wrapper or route it to enhanced for external callers (prefer one implementation).

Phase 2 — Delete legacy coinselect builders (per chain)

After Ledger + keystore both use enhanced:

  1. Remove import accumulative from 'coinselect/accumulative.js'.
  2. Remove old buildTx body that only existed for coinselect (or reduce to call enhanced internals).
  3. Delete modules.d.ts coinselect module declaration.
  4. Drop "coinselect" from that package’s package.json.
  5. Patch changeset; run package unit tests + any existing e2e.

Memo gotcha: LTC/DOGE historically passed { script, value: 0 } into coinselect so fee sizing included OP_RETURN. Enhanced path uses extraOutputs / post-selection memo — verify fee and dust behavior with memo still match expectations.

Size model gotcha: enhanced uses shared P2WPKH-ish constants from xchain-utxo. Expect small fee differences vs coinselect’s P2PKH-ish estimates; document if product needs chain-specific constants later (esp. BCH/DASH non-segwit scripts).

Phase 3 — Monorepo cleanup

  • yarn / lockfile: no remaining coinselect resolutions.
  • Grep clean: no coinselect under packages/**/src.
  • Close or supersede any Dependabot coinselect PRs as obsolete.

Phase 4 — Optional hardening (separate issues OK)

  • Chain-specific input/output vbyte tables (P2PKH / P2SH / P2WPKH / P2TR) instead of flat 68/31.
  • Align dust thresholds per chain where consensus dust ≠ 546.
  • Collapse “LargestFirst ≈ sorted Accumulative” if we want fewer redundant strategies.
  • Shared Ledger prepare helper in xchain-utxo to avoid drift across chains.

Acceptance criteria

  • No package depends on coinselect.
  • Keystore and Ledger transfers on BTC/BCH/LTC/DOGE/DASH use xchain-utxo selection only.
  • Deprecated prepareTx either removed or delegated to enhanced (no second algorithm).
  • Tests green; memo transfers still build/sign; no regression in “insufficient balance” / dust handling beyond expected fee-model delta.

Out of scope

  • Changing BranchAndBound / strategy scoring design.
  • Replacing coin selection with Bitcoin Core wallet algorithms.
  • ZEC (already outside coinselect).

References

  • packages/xchain-utxo/src/utxo-selector.ts, packages/xchain-utxo/src/strategies/*
  • Legacy: coinselect/accumulative.js imports in BCH/LTC/DOGE/DASH
  • Bitcoin enhanced path: packages/xchain-bitcoin/src/client.ts (buildTxEnhanced / prepareTxEnhanced)
  • Coinselect pin PR: chore(utxo): bump coinselect 3.1.12 → 3.1.13 #1745

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions