Skip to content

Protect eCash (ECX) wallets split from Bitcoin - #749

Merged
peachbits merged 1 commit into
masterfrom
matthew/ecx-split-protection
Oct 29, 2026
Merged

peachbits merged 1 commit into
masterfrom
matthew/ecx-split-protection

Conversation

@peachbits

@peachbits peachbits commented Oct 27, 2026 •

Copy link
Copy Markdown
Contributor

CHANGELOG

Does this branch warrant an entry to the CHANGELOG?

  • Yes
  • No

Dependencies

none. The sweep only protects anything once the ECX plugins from EdgeApp/edge-currency-plugins#466 exist, since their magic nLockTime is what keeps it off Bitcoin.

Description

Asana task

eCash (ECX) is a Bitcoin hard fork with no automatic replay protection. After a Bitcoin wallet splits into ECX, the new ECX wallet sends its whole balance to itself once it syncs. The ECX plugin's magic nLockTime makes this sweep invalid on Bitcoin, so afterwards the ECX sits on UTXOs that exist only on ECX, and later Bitcoin spends cannot replay onto them.

  • The sweep runs in the background, so split returns without waiting for the sync.
  • A wallet with nothing spendable skips the sweep, so empty wallets still split.
  • The ECX wallet's synced storage records the protection in EcxProtection.json: pending from the split, then sent with the sweep's txid, or empty. The GUI reads this file.
  • Each load of a pending wallet resumes the sweep, so logging out or closing the app before the wallet syncs leaves the sweep to the next login, on any device. Only one sweep goes out even when several loads wait at once.
  • A wallet that goes away while waiting (logout, archiving, deletion) cancels quietly instead of reporting an error.

Testing: unit tests for the sweep, the empty-wallet split, and a sweep resumed on the next login after logging out before the wallet synced. In the simulator, a betanet split signed the sweep, which was logged instead of broadcast and checked by hand.


Comment thread src/core/login/splitting.ts Outdated

// The protection waits for the wallet to sync, so it runs in the background:
if (protectEcx) {
protectEcxWallet(ai, wallet).catch((error: unknown) =>

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.

Critical: the sweep exists only as this in-memory promise. split() resolves before the ECX wallet syncs, and nothing persists or retries the protection. If the app is killed, backgrounded until the OS reaps it, or logged out before totalRatio reaches 1, the sweep never runs in any later session. The ECX coins keep sharing UTXOs with BTC. The next BTC spend then replays on ECX and moves them. The GUI card in EdgeApp/edge-react-gui#6294 then says "wait for it to confirm" for a transaction that will never be sent. The only recovery is deleting and re-splitting the ECX wallet.

Suggest a durable marker (wallet otherData / a disklet flag on the ECX wallet) set at split time and cleared after saveTx, with the sweep resumed on login whenever the marker is present.

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.

Fixed. The ECX wallet's synced storage now holds EcxProtection.json: pending from the split, then sent with the sweep's txid, or empty. Each load of a pending wallet resumes the sweep, so logging out or closing the app before the wallet syncs leaves it to the next login, on any device. Calls re-read the status under a per-wallet lock before sweeping, so only one sweep goes out. The GUI reads the same file.

Comment thread src/core/login/splitting.ts Outdated
wallet: EdgeCurrencyWallet
): Promise<void> {
// Wait until the wallet has found every coin it shares with Bitcoin:
await ai.waitFor((props: RootProps): true | undefined => {

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: this wait has no exit besides totalRatio >= 1 or a throw from checkCurrencyWallet. An archived/paused ECX wallet, or an engine reset (CURRENCY_ENGINE_CLEARED sets the ratio back to 0), leaves it pending for the rest of the session and silently skips protection. On logout or wallet deletion it throws Wallet id ... does not exist, which reaches onError as an error the user did nothing to cause. Consider treating a missing wallet as a quiet cancel, and logging when protection is abandoned.

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.

A wallet that goes away (logout, archive, delete) now ends the wait quietly with a log line, and its pending status resumes the sweep at its next load. Engine failures still reach onError. An engine reset does not skip the protection: the wait continues until the resync reaches 1. A paused wallet waits until it is unpaused.

ECX has no automatic replay protection. After a Bitcoin wallet splits
into ECX, the new wallet sweeps itself once it syncs. The ECX plugin's
magic nLockTime keeps the sweep off Bitcoin, so the swept coins exist
only on ECX and later Bitcoin spends cannot replay there. The sweep runs
in the background so the split does not wait for the sync, and a wallet
with nothing spendable skips it, so empty wallets still split.

The wallet's synced storage records the protection in EcxProtection.json:
pending from the split, then sent with the sweep's txid, or empty. Each
load of a pending wallet resumes the sweep, so a session that ends
before the wallet syncs (logout, app exit, archiving) leaves the sweep
to the next load instead of dropping it, and the GUI reads the status
instead of searching transaction names. A wallet that goes away while
waiting cancels quietly.
@peachbits
peachbits force-pushed the matthew/ecx-split-protection branch from 795c5f2 to e102af5 Compare October 29, 2026 21:23
@peachbits
peachbits marked this pull request as ready for review October 29, 2026 22:39
@peachbits
peachbits merged commit 4fdac49 into master Oct 29, 2026
4 checks passed
@peachbits
peachbits deleted the matthew/ecx-split-protection branch October 29, 2026 23:37
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