Skip to content

Unify the Epic Cash and MWC wallets and slatepack UI - #1507

Open
sneurlax wants to merge 20 commits into
stagingfrom
feat/slatepack-interface
Open

sneurlax wants to merge 20 commits into
stagingfrom
feat/slatepack-interface

Conversation

@sneurlax

@sneurlax sneurlax commented Nov 11, 2026 •

Copy link
Copy Markdown
Member

Epic Cash and MWC now share one wallet base and one slatepack UI.

Refactor:

  • MimblewimbleWallet is a shared base for EpicCashWallet and MimblewimblecoinWallet, with a shared native transaction model (MimblewimbleTransaction) and shared slatepack and relay server models.
  • One slatepack UI serves both coins. Screens go through a new SlatepackInterface, so a coin declares its methods and capabilities instead of the screens checking the coin type.
  • Stored per-coin wallet info keeps its keys and types, and the relay Hive adapters keep their type IDs, so no data migration is needed.

Fixes:

  • MWC estimates a creation height from its own genesis time, instead of Epic's.
  • A slatepack too long for a QR code shows a message with copy, share and save, instead of a broken QR.
  • Local notes are kept when creating a slatepack.
  • Slatepack input that arrives after the screen closes is ignored.
  • An Epicbox send to an unreachable relay shows "Unable to reach epicbox server". Before, the send dialog stayed up forever, because the pre-send check awaited a websocket sink.close() that never completes after a failed connection.
  • The on-chain note of sent Epic and MWC transactions shows the sender's message. Before, it showed "null".
  • The Epicbox insecure flag is saved under the key the plugin reads (epicbox_protocol_unsecure), and both older spellings still load.
  • The help text on the desktop Slatepack screen is readable.

Testing:

  • Unit and widget tests: flutter test test/wallets test/models test/pages/slatepack test/pages/send_view, 406 passed. New tests cover slatepack routing, slate inspection, the QR guard, note persistence, the Epicbox probe, on-chain notes and the Epicbox config model.
  • Checked against private Epic Cash and MWC test chains, with a real node, a relay and CLI peer wallets:
    • Epicbox send and receive.
    • A slate send, receive and finalize.
    • MWC slatepack send and receive.
    • Cancel.
  • Migration: wallets created, funded and given notes on staging keep their balances, full history (including a cancelled tx) and local notes, and sync on this branch.
  • Not covered: MWCMQS (no local relay), mobile devices, and mainnet.

Known issue, fixed separately:

The slatepack result types were identical apart from an Epic prefix, so
there is now one set. The epicbox/mwcmqs server and config models keep
their own classes, Hive type ids and JSON keys but inherit the common
fields from RelayServerModel and RelayConfigModel.
EpicTransaction/MwcTransaction and their message types had the same
fields. Both native interfaces now return MimblewimbleTransaction; the
templates in tool/wl_templates construct it, so regenerate lib/wl_gen
after pulling this.
Both wallets carried the same sync percent, scan loop, refresh
sequence, balance parsing, slate analysis and wallet info bookkeeping,
differing only in how they talk to their native library. The shared
parts now live in an abstract MimblewimbleWallet with hooks for the
native calls, and the slatepack/relay API (createSlatepack,
receiveSlatepack, finalizeSlatepack, getRelayConfig, ...) is declared
there so UI can be written against one type.

ExtraEpiccashWalletInfo and ExtraMimblewimblecoinWalletInfo had the
same JSON shape and are now one class; each wallet still stores it
under its own WalletInfoKeys entry.
The finalize view, entry/import/response dialogs, send-slatepack dialog
and method toggle existed twice, differing only in the wallet type they
cast to and in whether the copy said "slate" or "slatepack". They now
take a MimblewimbleWallet and pick their wording from SlatepackTerms.
The forked _createSlatepack/_importSlatepack/receive handlers in the
send, receive and confirm views collapse the same way.

EpicFinalizeView stays as a thin wrapper so its route name keeps
working. The transaction method enums and their providers become one
enum keyed by coin, keeping Epic's default of relay (epicbox) and MWC's
default of slatepack.
The MWC wallet copied Epic Cash's first-block timestamp (1565370278)
under the name mimblewimblecoinFirstBlock. MWC mainnet started on
2019-11-11 (1573462801, the value restore_wallet_view already uses), so
the estimate for a freshly created wallet landed about 130k blocks above
the actual tip: the initial scan and any later rescan from that height
never ran. Wallets restored from a seed were unaffected.

Staging already fixed this in 0e56467 with 1573462800. Rebased onto
it, this only aligns the value with restore_wallet_view.
qr 3 only notices oversized data while painting, after qr_flutter's own
validation has passed, so a slatepack too long for a level L code threw
during build. QR now checks the data first and shows an errorStateBuilder,
defaulting to a short message.
SlatepackInterface is the coin-neutral contract the slatepack screens
use: supported methods, on-chain note and invoice capabilities, terms,
restore progress, inspectSlatepack, localTxForSlate, create/receive/
finalize, cancel, and unsupported defaults for pending slatepacks,
repost, invoices and payment proofs. MimblewimbleWallet mixes it in;
inspectSlatepack replaces fullDecodeSlatepack and analyzeSlatepack.
SlatepackResult gains state, slateId, amount and fee, FinalizeResult
gains posted and kernelExcess, and receive returns a SlatepackResult.

UI changes:
- Slatepack sends go through prepareSend and the confirm screen on
  mobile and desktop, so the fee is shown. Epic and MWC prepareSend tag
  otherData with the method; the confirm screen creates the slatepack.
- SlatepackImportFlow inspects any pasted slatepack and dispatches on
  its state: receive S1, finalize S2, placeholders for invoices, messages
  for finalized and unknown slates. Finalize summaries use the local tx.
- One SlatepackDialog for S1/S2/I1/I2 with copy, share, save to
  <slateId>.<state>.slatepack, the encrypted flag and a QR code only up
  to 1000 characters, tap to enlarge.
- SlatepackInputField: paste, scan (QrCodeScannerDialog on desktop) and
  open file with a 256 KiB cap, used wherever a slatepack is entered.
- The Finalize nav item and desktop tab become "Slatepack" and open the
  import flow. Desktop receive always shows the address and QR.
- The method toggle is per wallet instead of per coin.
- Casts to MimblewimbleWallet and coin gates in slatepack UI use the
  interface. Relay settings stay coin specific.

Dead code removed: Epic's isSlatepack, MWC's slatepack type detection
helpers and its slatepack-as-address branch in prepareSend.
The pre-send relay check awaited sink.close() in a finally block. After a
failed connection that future never completes, so a send to an
unreachable or misconfigured relay left the sending dialog up forever
instead of reporting that the relay could not be reached. Move the check
into probeEpicboxServer, close the channel without waiting, and bound the
connect with a timeout.
Both plugins turn a missing slate message into the string "null", and on
a send the first participant entry belongs to the recipient. Taking
messages.first therefore showed "null" as the on-chain note of every sent
transaction. Use the first participant message that has text.
EpicboxConfig in the plugin reads epicbox_protocol_unsecure, but the
model wrote epicbox_protocol_insecure, so the flag never reached it.
Write the plugin's key and keep reading the two older spellings. Also
make copyWith on the Epicbox and MWCMQS config models apply their
arguments.
desktopTextExtraSmall uses the disabled button text color, which is
nearly white on the desktop card, so the explanation above the input was
almost invisible. Use the subtitle color like the other slatepack hints.
@sneurlax

Copy link
Copy Markdown
Member Author

See staging...refactor/epic-mwc-unify-rebased for an easier-to-review core of this functionality

@sneurlax sneurlax changed the title Unified Slatepack interface for MimbleWimble coins Unify the Epic Cash and MWC wallets and slatepack UI Nov 11, 2026
@sneurlax
sneurlax marked this pull request as ready for review November 11, 2026 02:29
@sneurlax

Copy link
Copy Markdown
Member Author

See that last note:

Known issue, fixed separately:

We will want to update this after those merge

The send field parses its text as a payment URI as soon as it contains a
colon. "http:" parsed as a URI with an empty address, so typing an Epic
Cash HTTP listener address cleared the field at the colon and left
"//host:port", which failed validation. Only pasting the whole URL
worked. Treat http and https URLs as addresses, not payment URIs.
The send views added a listener to the address controller inside build()
for Epic Cash (and, on desktop, MWC) that reassigned the controller text
on every change. Listeners piled up with each rebuild, and resetting the
text dropped the selection, so on Android only the first character of a
typed address survived; pasting still worked. Strip the relay address
prefixes in the field's onChanged instead, and only rewrite the field
when that changes the text.
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.

1 participant