Skip to content

Split Bitcoin wallets into eCash (ECX) - #6294

Merged
peachbits merged 6 commits into
developfrom
matthew/ecx-gui
Oct 30, 2026
Merged

peachbits merged 6 commits into
developfrom
matthew/ecx-gui

Conversation

@peachbits

@peachbits peachbits commented Oct 27, 2026 •

Copy link
Copy Markdown
Contributor

Description

Asana task

Lets Bitcoin wallets split into eCash (ECX), Paul Sztorc's Bitcoin hard fork, and warns the user while the split wallet is still exposed to replay.

  • Registers the ecashbitcoin and ecashbitcoinbetanet plugins, off unless the plugin config enables them. Betanet also needs beta features, so production Bitcoin wallets never offer a split into it.
  • Adds both ECX wallet types to the end of the create-wallet order, the asset settings list, and the view-xpub and sign-message menus, with Segwit and no-Segwit create options like Bitcoin's.
  • Before splitting a Bitcoin wallet into ECX, a modal explains that the new wallet sends its ECX to itself in a replay protection transaction, that this costs an ECX network fee, and that Bitcoin sends can also move the ECX until it confirms. The split runs only after the user confirms.
  • The Bitcoin wallet scene shows a warning card while an ECX wallet with the same xpubs has a protection sweep that has not gone out yet or has not confirmed, as recorded in the EcxProtection.json edge-core-js keeps in the ECX wallet. The card says which, and finds the sweep by its recorded txid.
  • Fixes useRefresher running two refresh loops at once when it starts without a default value, which doubled the info server and Zcash migration refreshes. The eCash warning uses its new isFinal option to stop polling once every split wallet's sweep confirms.

CHANGELOG

Does this branch warrant an entry to the CHANGELOG?

  • Yes
  • No

Dependencies

Requirements

If you have made any visual changes to the GUI. Make sure you have:

  • Tested on iOS device
  • Tested on Android device
  • Tested on small-screen device (iPod Touch)
  • Tested on large-screen device (tablet)

@peachbits
peachbits marked this pull request as ready for review October 28, 2026 18:02

@claude claude Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Claude Code Review

Claude Code Review is paused for this repository because its GitHub organization is not linked to your Claude organization.

To turn it back on, one person needs both roles: owner of the GitHub organization (or the account owner, for a personal repository) and admin or owner of your Claude organization. If that is not you, pass this on to someone who is.

Open Organization settings, Git providers and click Link next to the GitHub organization's name. If it is not listed, click Enable GitHub integration or Connect your GitHub first, whichever the page shows.

New pull requests are then reviewed as before. To get a review on one that is already open, someone with write access can comment @claude review.

Tip: disable this comment in your organization's Code Review settings.

@cursor

cursor Bot commented Oct 28, 2026

Copy link
Copy Markdown

Bugbot is paused — on-demand spend limit reached

Bugbot uses usage-based billing for this team and has hit its on-demand spend limit.

A team admin can raise the spend limit in the Cursor dashboard, or wait for the next billing cycle to continue.

@peachbits

Copy link
Copy Markdown
Contributor Author
image image

Comment thread src/util/ecashSplit.ts Outdated
const result = await stream.next()
const sweep: EdgeTransaction | undefined =
result.done === true ? undefined : result.value[0]
return sweep?.confirmations === 'confirmed' ? 'settled' : 'pending'

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Warning: anything without a confirmed "Replay Protection Tx" reads as pending forever. That includes a sweep core never sent (app closed before the ECX wallet synced; see the EdgeApp/edge-core-js#749 review), a dropped or failed sweep, and an ECX wallet imported from the same key (isImportKeySupported: true), which never had a sweep. In each case the BTC wallet tells the user to wait for a confirmation that will never come, and polling never stops. This needs a recovery path (or a core-provided status) rather than an indefinite wait.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The card now reads core's EcxProtection.json (EdgeApp/edge-core-js#749). A pending record shows a new message saying the transaction has not gone out yet and goes out once the eCash wallet syncs. A sent record keeps the wait-to-confirm message until that txid confirms. An ECX wallet without a record, such as one imported from the same key, shows no card; protecting imported wallets is a follow-up.

Comment thread src/util/ecashSplit.ts Outdated
wallet: EdgeCurrencyWallet
): Promise<EcashSplitStatus> {
// An empty wallet has nothing to separate, though it may still be syncing:
if (wallet.balanceMap.get(null) === '0') return 'clear'

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Warning: right after a split the ECX wallet has not synced. Its balance reads '0' and this returns clear. The card stays hidden during the window when a BTC send is most likely to replay, and it appears only after the balance syncs. Checking the ECX wallet's syncStatus.totalRatio before trusting a zero balance would cover it.

The same early return also means an emptied ECX wallet never reaches settled. The 15s poll then runs for as long as the BTC transaction list is open.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The balance check is gone. The record says pending from the moment of the split, so the card shows while the ECX wallet syncs. An emptied wallet settles once its sweep confirms, or right away when its record says empty, which ends the poll.

Comment thread src/util/ecashSplit.ts Outdated
searchString: PROTECTION_TX_NAME,
firstBatchSize: 1
})
const result = await stream.next()

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggestion: the stream is read once and never closed. With the 15s poll, each tick leaves another open streamTransactions iterator per matching ECX wallet over the core bridge. A stream.return?.() in a finally would release it.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The lookup reads the stream with for await, which calls return() when it stops early. A test checks that the stream closes.

Comment thread src/util/ecashSplit.ts Outdated
// Transactions stream newest first, so this finds the latest sweep:
const stream = wallet.streamTransactions({
tokenId: null,
searchString: PROTECTION_TX_NAME,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggestion: matching on the free-text name couples the GUI to an English string core happens to write. searchString also matches notes. A user who renames the sweep makes the card stick on pending, and a user tx with that text in its notes is mistaken for the sweep. A structured marker from core (e.g. savedAction or a wallet-level flag) would be sturdier.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The sweep is now found by the txid core records, not by its name.

@cursor

cursor Bot commented Oct 29, 2026

Copy link
Copy Markdown

Bugbot is paused — on-demand spend limit reached

Bugbot uses usage-based billing for this team and has hit its on-demand spend limit.

A team admin can raise the spend limit in the Cursor dashboard, or wait for the next billing cycle to continue.

Without a default value, the hook ran the first refresh right away and
also scheduled one after the delay, so two refresh loops ran side by
side and every caller refreshed twice per delay. Unmounting cleared only
one of the two timers, so the other still fired once. The info server
refresh in Services and the Zcash migration status both start without a
default value.
Render the menu option icons through VectorIcon instead of a direct
AntDesign import.
Registers the ecashbitcoin and ecashbitcoinbetanet plugins, off unless the
plugin config enables them, with UTXO currency info matching Bitcoin's.
Betanet is a test network, so it also needs beta features, and production
Bitcoin wallets never offer a split into it.
Splitting into eCash (ECX) sweeps the new wallet to itself, and until that
sweep confirms a Bitcoin send can be replayed on ECX. edge-core-js records
the sweep in the ECX wallet's EcxProtection.json. The Bitcoin wallet scene
shows a warning while an ECX wallet with the same xpubs has a sweep that
has not gone out yet or has not confirmed, and finds the sweep by the txid
in that record.
Splitting a Bitcoin wallet into eCash (ECX) makes the new wallet sweep
its funds to itself, which costs an ECX network fee, and until that
sweep confirms a Bitcoin send can also move the ECX. The split button
now explains this in a modal and only splits once the user confirms.
@peachbits
peachbits enabled auto-merge October 30, 2026 00:34
@peachbits
peachbits merged commit 2d0c75c into develop Oct 30, 2026
5 checks passed
@peachbits
peachbits deleted the matthew/ecx-gui branch October 30, 2026 00:59
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants