morphit/docs/adr/0024-bitcoin-cash-trade-only-addition.md

265 lines
11 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# ADR-0024 — Bitcoin Cash (BCH) trade-only addition
**Status:** Accepted (Part 122 cp21)
**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 that BCH follows), ADR-0011
(fee model + fee_method enum-freeze).
---
**2026-05-17 (Part 122 cp22) forward-note — operator-stance UX
closure.** Same closure as ADR-0023's cp22 forward-note: the
`MORPHIT_INDEXER_DISABLED_ASSETS` env var path established here
remains the canonical contract; cp22 adds an interactive wizard
step that walks operators through enabling/disabling each
trade-only asset (USDT and BCH today, plus any future
Category-B addition) and emits the right env-file line without
manual editing. The wizard iterates the canonical registry
filtered to `canBeTraded && !canPayListingFee`, so future
trade-only assets surface in the wizard automatically without
per-asset wizard code changes. Discoverability win for
operators; design contract from this ADR unchanged.
---
## Context
After USDT shipped as Morphit's first Category-B trade-only
asset (ADR-0023, Part 121), Ken asked whether adding a few more
trade-only coins — Bitcoin Cash, Dash, and similar — would be
similarly contained, and committed to add BCH as the fifth
tradable asset.
The Category-B pattern from ADR-0023 was designed to make this
exact case routine: single entry in
`packages/asset-registry/src/index.ts` with
`canPayListingFee: false` + `canBeTraded: true`, a mirror in the
frontend registry, no fee-verifier (it can't pay fees), the
ADDING-A-COIN.md inputs (logo, address regex, txid regex,
explorer URL). This ADR records the BCH-specific choices.
## Decision
### 1. BCH is **trade-only** (Category B)
Same posture as USDT. The `fee_method` wire-format enum stays
exactly `'blurt' | 'waived_first_buy' | 'btc' | 'xmr'`
`fee-method-enum-frozen-smoke.ts` continues to pin this. A
new `bch-trade-only-smoke.ts` mirrors `usdt-trade-only-smoke.ts`
as a BCH-specific sentinel that fails loudly if a future
contributor toggles BCH's `canPayListingFee` to true.
### 2. **Single-network** — mainnet only
BCH is single-network (unlike USDT). `supportedNetworks:
['mainnet']`, `defaultNetwork: 'mainnet'`. No network picker
shown in the post-order form or address-share modal. The
`asset_network` field on order/chat payloads stays undefined for
BCH (it's required only for `asset === 'USDT'`).
### 3. **No privacy warning chip**
`privacyWarningKey: null`. BCH is transparent (like BTC) but
decentralized — no issuer can freeze addresses, no central
authority can blacklist holders. Same posture as BTC: users
opting into Bitcoin Cash know its traceability properties; we
don't surface a warning where none is needed. This is a
deliberate philosophical alignment, not a copy-paste oversight:
the warning chip exists for assets that compromise either of
the top-2 priorities (privacy or decentralization), and BCH
compromises neither.
### 4. Address validator accepts both **CashAddr and legacy**
BCH wallets in the field emit either format. CashAddr is the
modern BCH standard (post-2018), with or without the
`bitcoincash:` prefix. Legacy P2PKH (1...) and P2SH (3...)
addresses share their format with pre-fork Bitcoin and are
still accepted by most BCH wallets. Our shape-check regex
accepts all four cases:
```ts
addressShape:
/^(bitcoincash:[qp][a-z0-9]{41}|[qp][a-z0-9]{41}|[13][1-9A-HJ-NP-Za-km-z]{25,34})$/
```
This is a permissive shape check, not a checksum — the
recipient's wallet does the real verification. Tradeoff
discussed and accepted: a `1...` or `3...` address typed in
the BCH tab could be a BTC address that the user picked the
wrong tab for. We can't disambiguate from the shape alone; the
buyer's wallet will refuse if it's wrong-chain. The friction
is acceptable to avoid rejecting legitimate legacy BCH
addresses.
### 5. Decimals = 8 (same as BTC)
BCH preserved BTC's satoshi-denominated smallest unit across
the 2017 fork. The amount-formatter and form-input precision
match BTC's behavior exactly.
### 6. Bundled chat-link explorer: `blockchair.com/bitcoin-cash`
Operator surveyed eight BCH explorers at cp21 addition time:
1. https://blockchair.com/bitcoin-cash
2. https://www.blockchain.com/explorer
3. https://bitinfocharts.com/bitcoin%20cash/explorer/
4. https://bchexplorer.info/
5. https://www.oklink.com/bch
6. https://bch.tokenview.io/
7. https://blockexplorer.one/bitcoin-cash/mainnet
8. https://explorer.cloverpool.com/bch
Chose **blockchair.com/bitcoin-cash** as the bundled default
based on:
- Well-established multi-chain explorer (operating since 2017)
- Predictable URL format: `/transaction/{txid}`
- Good uptime track record
- No mandatory JavaScript for the basic tx-view page
- Doesn't aggressively fingerprint visitors
Operators wanting a different default override per-instance via
`MORPHIT_FRONTEND_BCH_CHAT_LINK_URL`; the env var follows the
same shape contract as BTC/XMR (`https://…/{txid}`, must contain
the `{txid}` placeholder, must parse as a valid URL). The
ops-cli wizard step 12 now asks for the BCH URL after BTC and
XMR with the same probe-reachability check.
### 7. Default-ON instance-wide, operator opt-out via env var
Per Memory #25 (every new asset ships default-ON instance-wide,
operator override). An operator who wants to refuse BCH orders
on their instance sets `MORPHIT_INDEXER_DISABLED_ASSETS="BCH"`
(or includes BCH in a comma-separated list). Federation-wise:
disabled-on-A doesn't break visibility of BCH orders posted on
peer instance B — they still appear in A's read-only orderbook
view. A only refuses to ACCEPT new BCH orders posted FROM its
own users.
### 8. Community-canonical logo (updated Part 122 cp27-DD)
`apps/web/static/icons/icon-bch.svg` ships the canonical
Bitcoin Cash mark on a BCH-brand-green disc (#0AC18E): the
official "Ƀ" glyph with two vertical strokes piercing the
rounded body. Operator-supplied from `bitcoincash.org`-style
canonical artwork; minified via svgo to 0.8 KB while preserving
viewBox. Originally shipped at cp21 as a path-based placeholder
"B" stylization; replaced with the community-canonical SVG at
Part 122 cp27-DD when Ken provided the authoritative artwork.
## Files changed
Canonical asset registry:
- `packages/asset-registry/src/index.ts``ASSET_TICKERS`
extended `['BTC','XMR','BLURT','USDT']``['BTC','XMR','BLURT','USDT','BCH']`;
full `BCH` `AssetEntry` appended to `ASSETS`.
Frontend asset registry:
- `apps/web/src/lib/assets/registry.ts``validateBch`
function; frontend BCH entry (logoSvgPath, accentClass
`text-lime-500`, decimals 8, supportsMemo false).
Chat payload:
- `apps/web/src/lib/chat/payload.ts` — 5 BCH regex constants
(CashAddr prefixed/bare, legacy P2PKH/P2SH, txid),
`'bch'` added to `ChatAssetTicker` union, `isValidBchAddress`
+ `isValidBchTxid` functions, `isValidAddress` + `isValidTxid`
dispatchers extended.
Explorer URL plumbing:
- `apps/web/src/lib/explorer/urlsCore.ts``BCH_TXID_RE`,
`BUNDLED_BCH_CHAT_LINK_URL`.
- `apps/web/src/lib/explorer/urls.ts``'BCH'` in
`ExternalAsset` type, `EXPLORER_REGISTRY.BCH` entry,
re-export.
Indexer + clients:
- `apps/indexer/src/api/instance.ts``chat_link_urls.bch`
field, emitted in body.
- `apps/indexer/src/config/index.ts``frontendBchChatLinkUrl`
field, `MORPHIT_FRONTEND_BCH_CHAT_LINK_URL` Zod schema,
mapped in Config builder.
- `packages/indexer-client/src/index.ts``bch?` in
`chat_link_urls` schema (optional for back-compat).
- `apps/matrix-bot/scripts/api-response-shape-smoke.ts``bch`
in `ChatLinkUrlsSchema`.
Instance store:
- `apps/web/src/lib/stores/instance.ts``bch: string | null`
in store interface, FALLBACK, fetch handler defensive
fallback.
ops-cli:
- `apps/ops-cli/src/init/steps.ts``DEFAULT_BCH_CHAT_LINK_URL`,
`ChatLinkExplorersResult.bch`, step 12 explain text +
BCH prompt + return value.
- `apps/ops-cli/src/init/render.ts` — emits
`MORPHIT_FRONTEND_BCH_CHAT_LINK_URL` in rendered env file.
UI dispatches:
- `apps/web/src/lib/components/AddressShareModal.svelte` — BCH
tab, placeholder dispatch, invalid-address message.
- `apps/web/src/lib/components/FundsSentModal.svelte` — BCH tab.
- `apps/web/src/lib/components/ChatMessage.svelte` — BCH
branches in explorer URL dispatch, address-pill label,
funds-sent pill title; `canMarkSent` guard extended;
`onMarkSent` callback type widened.
- `apps/web/src/lib/components/ConversationView.svelte`
`markSentArgs` + `handleMarkSentClick` types widened.
- `apps/web/src/routes/[lang]/post/+page.svelte` — BCH tooltip
block in asset picker.
i18n × 10 locales:
- `apps/web/src/lib/i18n/locales/{en,es,fr,de,it,pl,ru,fa,zh-CN,zh-HK}.json`
`assets.bch.{displayName, oneLineDescription, disabled_on_instance}`,
`chat.address.{method_bch, address_placeholder_bch, address_invalid_bch, pill_method_bch}`,
`chat.funds_sent.pill_title_bch`, `home.asset_subtitles.bch`,
`post_order.form.asset_explainer.bch`,
`payment_method.pay_bch.description`,
`cheat_sheet.section_assets.bch`.
Logo:
- `apps/web/static/icons/icon-bch.svg` — community-canonical
Bitcoin Cash mark on green disc, swapped in from cp21
placeholder at cp27-DD when Ken provided authoritative
artwork; minified via svgo to 0.8 KB.
Smoke:
- `packages/asset-registry/scripts/bch-trade-only-smoke.ts`
13 scenarios, mirrors `usdt-trade-only-smoke.ts`.
- `scripts/run-smokes.sh` — registers
`packages/asset-registry:bch-trade-only-smoke`.
## Consequences
**Positive:**
- Two trade-only assets now shipped (USDT, BCH); the Category-B
pattern is exercised twice and the indexer/frontend split
holds. Future trade-only additions (Dash, Litecoin, etc.)
follow the same template with even less new code.
- BCH community visibility — a long-running coin with
established users now has a no-KYC P2P trading rail.
- Asset count: BTC, XMR, BLURT, USDT, BCH = 5.
**Trade-offs accepted:**
- Legacy BCH addresses (1.../3...) are indistinguishable from
BTC addresses at the shape-check layer. Mitigation:
recipient wallet rejects wrong-chain sends. Friction
considered acceptable to avoid rejecting legitimate
legacy-format BCH addresses.
- One more chat-link override env var (`MORPHIT_FRONTEND_BCH_CHAT_LINK_URL`)
for operators to remember — but the ops-cli wizard step 12
walks them through it.
**Future revisits:**
- If the BCH community pushes for CashAddr-only behavior,
consider tightening the validator to reject legacy P2PKH/P2SH
(currently accepted).
- If/when Dash, Litecoin, or similar request inclusion, mirror
the same Category-B template — the pattern is now proven for
single-network trade-only coins.