morphit/docs/adr/0025-litecoin-trade-only-addition.md

10 KiB

ADR-0025 — Litecoin (LTC) trade-only addition

Status: Accepted (Part 122 cp24) Date: 2026-05-17 Deciders: project maintainer Supersedes: none Superseded by: none Related: ADR-0023 (USDT multi-network — established the Category A/B trade-only pattern), ADR-0024 (Bitcoin Cash — first single-network Category-B addition), ADR-0011 (fee model + fee_method enum-freeze), Part 122 cp22 (interactive disable-trade-only-asset wizard step).

Context

Following BCH's successful single-network Category-B integration in Part 122 cp21 (ADR-0024), the project maintainer requested Litecoin (LTC) as the third Category-B trade-only asset. LTC shares BCH's architectural shape — single-network transparent chain, no central issuer, BTC-fork heritage — making the cp21 template directly applicable.

The cp21 BCH addition surfaced a CLASS of bugs cp23's fresh deep-deep found: canonical-source extensions (asset registry, chat payload, primary UI dispatches) but missed downstream typed-consumer maps (price store, Coingecko ID map, fallback prices, payment-method registry, cheat-sheet, crawler-facing static files). Cp24 closes those proactively for LTC rather than waiting for a post-addition DD to surface them.

Decision

LTC ships as the third Category-B trade-only single-network asset following ADR-0024's template, with eight design choices:

1. Trade-only (Category B), canPayListingFee: false

Listing-fee payment methods remain frozen at {blurt, btc, xmr, waived_first_buy} per Memory #23 / ADR-0011. Adding LTC as a new fee_method enum value would break the wire-format invariant that smokes (fee-method-enum-frozen-smoke) explicitly pin.

LTC users trade LTC; they pay listing fees in BLURT/BTC/XMR like every other Category-B asset.

2. Single-network mainnet

LTC has no production sidechains, L2s, or wrapped-asset variants on Morphit's radar. No per-network picker; defaults to mainnet and stays there. This is the BCH posture, NOT the USDT posture.

3. No privacy-warning chip (privacyWarningKey: null)

LTC is transparent (like BTC and BCH) but the chain is fully decentralized and LTC addresses cannot be frozen by an issuer. Same posture as BTC/BCH: no warning chip needed.

Note: LTC has opt-in privacy via MWEB (MimbleWimble Extension Blocks). MWEB is wallet-side and per-transaction, NOT a chain property. Users seeking Morphit's strongest privacy posture should use XMR. MWEB-enabled trades are not specially flagged in Morphit's UI; the operator-counterparty knows the privacy posture of the addresses they share.

4. Address validator accepts ALL four LTC formats

LTC has three address-shape eras:

  1. Legacy P2PKH starting with L — base58, 26-35 chars. Unambiguous with BTC since BTC P2PKH starts with 1.
  2. Modern P2SH starting with M — base58, 26-35 chars. Introduced 2017 to disambiguate from BTC P2SH.
  3. Deprecated P2SH starting with 3 — base58, 26-35 chars. BTC-shape ambiguous. Still valid on the LTC chain.
  4. Bech32 / Bech32m with ltc1 prefix — 6-87 char body. Covers both segwit-v0 (ltc1q...) and taproot (ltc1p...).

Decision: accept all four including the deprecated 3-prefix P2SH form. Recipient wallet does chain-binding on receive — this matches ADR-0024 §4's stance for BCH legacy addresses.

Refusing the 3-prefix form would be paternalistic toward LTC users whose wallets still emit it. The cost is the same as BCH: a user pasting an LTC 3-address while having selected BTC as the asset wouldn't trigger a shape-level rejection. Same mitigation: clear UI labels at the picker stage.

5. Decimals = 8 (satoshi-denominated)

LTC inherited BTC's 8-decimal smallest-unit semantics; the LTC ecosystem calls it "litoshi" but the encoding is identical.

6. Bundled explorer default: litecoinspace.org

From Ken's seven-explorer candidate list:

  • blockchair.com/litecoin — multi-chain (same vendor as BCH default)
  • oklink.com/litecoin — OKX exchange-affiliated (centralization concern)
  • bitinfocharts.com/litecoin/explorer/ — analytics + tracking
  • chain.so/LTC — older infrastructure, less actively maintained
  • litecoinspace.org — community-led, mempool.space-style, no JS tracking, open-source
  • blockexplorer.one/litecoin/mainnet — generic multi-coin
  • ltc.tokenview.io — Chinese-operated multi-chain

Chosen: litecoinspace.org/tx/{txid}. Rationale: it's the LTC-equivalent of mempool.space (which we already use for BTC). Privacy-aligned with Morphit's priority #1 (no JS tracking, open-source). Operators wanting a different default override via MORPHIT_FRONTEND_LTC_CHAT_LINK_URL; all seven candidates are enumerated in docs/OPERATIONS.md for operator reference.

7. Default-ON instance-wide; operator opt-out via env var

Per Memory #25: every new asset ships ENABLED by default on a fresh instance. Operators wishing to refuse LTC set MORPHIT_INDEXER_DISABLED_ASSETS="LTC" (or include LTC in a comma-separated list).

The morphit-ops init wizard step 13 "Trade-only asset policy" (Part 122 cp22) walks new operators through this decision at install time; LTC surfaces in the wizard automatically because the wizard iterates ASSETS.filter(a => a.canBeTraded && !a.canPayListingFee) from the canonical registry.

8. Operator-approved logo at apps/web/static/icons/icon-ltc.svg (updated Part 122 cp27-DD2)

Silver-gray disc with stylized "Ł" (the Polish-style L with diagonal slash that distinguishes Litecoin's mark from a generic L). No <text> elements (font-fallback rules from ADDING-A-COIN.md). Originally shipped at cp24 as a placeholder pending community-blessed artwork from the Litecoin Foundation; operator-approved as-is at cp27-DD2 (Ken: "the current ltc icon looks great, i do not think u need to change that"). Minified via svgo to 0.4 KB while preserving viewBox. Drop-in SVG swap remains supported with no other code changes if the Litecoin Foundation later publishes a different mark.

Files changed (cp24)

Code:

  • packages/asset-registry/src/index.tsASSET_TICKERS extended; LTC AssetEntry
  • apps/web/src/lib/chat/payload.ts — 4 LTC regex constants, isValidLtcAddress + isValidLtcTxid, ChatAssetTicker union widened, dispatcher widening, 4 dispatch gates widened, buildPaymentUri LTC branch (litecoin: URI scheme)
  • apps/web/src/lib/assets/registry.tsvalidateLtc helper, full LTC entry with accentClass: 'text-slate-400'
  • apps/web/src/lib/explorer/urlsCore.tsLTC_TXID_RE, BUNDLED_LTC_CHAT_LINK_URL
  • apps/web/src/lib/explorer/urls.tsLTC added to ExternalAsset, EXPLORER_REGISTRY.LTC
  • apps/web/src/lib/stores/instance.tschat_link_urls.ltc field
  • apps/indexer/src/config/index.tsfrontendLtcChatLinkUrl Config field + Zod schema + env-var mapping
  • apps/indexer/src/api/instance.tsltc: string | null in InstanceResponse
  • apps/ops-cli/src/init/steps.tsDEFAULT_LTC_CHAT_LINK_URL, ChatLinkExplorersResult.ltc, LTC prompt block, CATEGORY_B_DESCRIPTIONS LTC entry
  • apps/ops-cli/src/init/render.tsMORPHIT_FRONTEND_LTC_CHAT_LINK_URL emission
  • apps/ops-cli/src/commands/init.ts — LTC printReview line
  • apps/web/src/lib/components/AddressShareModal.svelte — LTC tab + dispatches
  • apps/web/src/lib/components/FundsSentModal.svelte — LTC tab
  • apps/web/src/lib/components/ChatMessage.svelte — explorer dispatch, type unions widened, pill dispatches
  • apps/web/src/lib/components/ConversationView.svelte — type unions widened
  • apps/web/src/routes/[lang]/post/+page.svelte — LTC tooltip block
  • apps/matrix-bot/scripts/api-response-shape-smoke.tsltc in ChatLinkUrlsSchema

Smokes:

  • packages/asset-registry/scripts/ltc-trade-only-smoke.ts — NEW (13 scenarios)
  • apps/ops-cli/scripts/disabled-assets-wizard-smoke.ts — Category-B count 2→3
  • scripts/run-smokes.shltc-trade-only-smoke registered

i18n:

  • apps/web/src/lib/i18n/locales/{en,es,fr,de,it,pl,ru,fa,zh-CN,zh-HK}.json — 8 LTC keys per locale

Logo:

  • apps/web/static/icons/icon-ltc.svg — operator-approved stylized "Ł" on silver disc, originally shipped cp24 as placeholder; operator approval Part 122 cp27-DD2; minified via svgo at cp27-DD.

cp23-DD-class downstream consumers (the BCH-class bugs cp23 found, closed proactively for LTC):

  • apps/web/src/lib/prices/index.tsLTC: null in internalStore + reset()
  • apps/web/src/lib/prices/providers/coingecko.tsLTC: 'litecoin'
  • apps/web/src/lib/prices/providers/fallback.tsLTC: 100
  • apps/web/src/routes/[lang]/cheat-sheet/+page.svelte — LTC row
  • apps/web/src/lib/payments/registry.tspay_ltc entry
  • apps/indexer/src/indexer/handlers/operatorPaymentMethod.tspay_ltc in RESERVED_CANONICAL_KEYS
  • apps/indexer/src/db/schema.sql — comments updated
  • docs/API.md — asset filter + example
  • docs/GRANDMA-FRIENDLY-INVESTIGATION.md — status notes
  • apps/web/static/llms.txt + llms-full.txt — references updated

Consequences

Positive

  • Third Category-B asset shipped — pattern fully matured. Future trade-only additions (Dash, DOGE) will follow the same template with even less ceremony.
  • cp23-DD class closed proactively — for the first time in the asset-addition lifecycle, the downstream typed-consumer maps are touched in the SAME checkpoint as the canonical registry, rather than days later in a follow-up DD.
  • Operator stance UX maturesmorphit-ops init step 13 now walks through 3 Category-B assets (USDT, BCH, LTC) in alphabetical order with per-ticker Y/n prompts.

Trade-offs accepted

  • Legacy 3-prefix P2SH is BTC-shape ambiguous (§4 above). Wallet does chain-binding on receive. Same posture as BCH.
  • One more chat-link env varMORPHIT_FRONTEND_LTC_CHAT_LINK_URL. Operators editing the env file have one more line; the wizard hides this complexity behind a single prompt.

Future revisits

  • Possible MWEB awareness chip if/when MWEB-only trades become a UX concern (not at launch — wallet-side opt-in, no Morphit surface area).
  • Same pattern extends to Dash, DOGE, etc. Each new Category-B asset surfaces in the wizard automatically without per-asset wizard code.