Repository navigation
Releases
Moth Wallet Extension v0.14.1
What's Changed
Bug fixes
- fix(sync): stop the dedup filter punching holes in a replay by @bobblessinghartley in #125
Other changes
- fix(extension): stop a superseded wallet publishing its balance after a switch by @bobblessinghartley in #147
- chore: version packages by @github-actions[bot] in #165
Full Changelog: https://github.com/shieldedtech/moth-wallet/compare/@shieldedtech/moth-cli@0.14.0...moth-extension-v0.14.1
@shieldedtech/moth-wallet@0.14.1
Patch Changes
-
0f0afa3: Stop the dedup filter from punching holes in a replay, and say what a non-linear
insert actually means.partitionByAppliedIndexfiltered the whole batch by predicate, dropping every
event withid <= appliedIndexwherever it sat. For an ascending batch that is
the intended boundary-duplicate filter. For an out-of-order one it removes an
already-applied event from the middle and hands the SDK a replay with a gap —
and the ledger tree then rejects the insert:values inserted non-linearly into dust generation tree; expected to insert index 337423, but received 337429.Note the direction: the SDK bug this wrapper exists to work around re-sends a
boundary event, so the tree receives an index lower than it expects. This is
the mirror image — an index higher than expected, because events the tree
needed never arrived. The workaround for one could produce the other, and only
for the part whose cursor and tree had drifted apart, which is why a single
sub-wallet would stall while the others finished.The filter now drops only a leading contiguous run of already-applied events.
When an already-applied id appears after a fresh one the batch is not the
ascending stream the filter assumes, so nothing is dropped and the SDK's own
(more conservative) handling decides. The existing test named "drops only the
already-applied prefix" described this behaviour already; the implementation now
matches it.Non-linear insert failures are also no longer bare WASM strings surfacing four
layers down with no mention of sync state. They now carry the cursor and batch
that produced them and say that retrying cannot help — the same batch replays
from the same cursor forever — and that the fix is to clear that part's cache:appliedIndex=1291234, replayed 2 event(s) 1291235..1291240, 0 dropped as already applied. Clear this wallet part's sync cache and resync.The original error is preserved as
cause. Unrelated errors pass through
untouched.
@shieldedtech/moth-tui@0.14.1
Patch Changes
- Updated dependencies [0f0afa3]
- @shieldedtech/moth-wallet@0.14.1
@shieldedtech/moth-cli@0.14.1
Patch Changes
- Updated dependencies [0f0afa3]
- @shieldedtech/moth-wallet@0.14.1
- @shieldedtech/moth-tui@0.14.1
Moth Wallet Extension v0.14.0
What's Changed
Features
- perf(core): pre-seed DUST for restored wallets when the indexer proves no prior history by @chrispalaskas in #154
Other changes
- fix(release): normalize generated changelogs by @chrisferry in #138
- fix(dust): pay fees from the largest DUST coin first by @chrispalaskas in #141
- fix(dust): replace the SDK fee-balancing loop with one that terminates by @chrispalaskas in #142
- build(extension): make the build output directory for the extension configurable by @iangregsondev in #116
- fix(ci): clear the four critical scan findings blocking PR #105 by @bobblessinghartley in #112
- fix(extension): make Paste work outside Chrome and stop dropping address underscores by @chrispalaskas in #149
- fix(core): sign contract transactions through the SDK, and stop the DUST wait that stalled every daemon call by @GiuseppeSalvatoreShielded in #152
- ci: stop persisting the checkout token in read-only workflow jobs by @GiuseppeSalvatoreShielded in #161
- test(extension): Playwright smoke tests that load the built extension in Chromium by @GiuseppeSalvatoreShielded in #158
- feat(daemon): proveTransaction verb, and MOTH_HOME for the wallet root by @bobblessinghartley in #150
- chore: version packages by @github-actions[bot] in #143
New Contributors
- @iangregsondev made their first contribution in #116
Full Changelog: https://github.com/shieldedtech/moth-wallet/compare/@shieldedtech/moth-cli@0.13.0...moth-extension-v0.14.0
@shieldedtech/moth-wallet@0.14.0
Minor Changes
-
55a878c: Add a
proveTransactiondaemon verb, and honourMOTH_HOME.proveTransactionbuilds, balances, proves and signs a transfer, then returns
the finalized transaction as hex instead of submitting it. Programmatic clients
that need to hold a proof and submit it later had no verb for the prove half;
submitTransactionalready covered the other. The returned hex binds fee-side
DUST UTXOs by nullifier, so the wallet must stay quiet until submission — the
L3 modal says so, and--max-spendapplies as it does totransferTokens.MOTH_HOMEnow overrides the~/.mothroot for both the filesystem storage
adapter and the daemon socket path, so several isolated instances can run from
one account. A relative value is rejected rather than resolved against the
working directory, which would otherwise put a wallet and its socket under
different roots.
Patch Changes
-
3b39fb6: Report the real error when a contract artifact fails to load.
loadContractArtifactwrapped its wholemanaged/contract/branch in a
try/catchthat discarded the error, so a module that existed but failed to
load was reported asNo contract module found in <path>. The common cause is a
compact runtime version mismatch, whose own message names both versions and
points straight at the fix. Only a missingcontract/directory now falls
through to the remaining strategies. -
3b39fb6: Stop waiting for two equal DUST balances before building a contract transaction.
Before building a contract transaction the contract paths waited for the wallet
to be strictly synced, and then for two consecutive emissions carrying an
identical DUST balance.facade.state()emits roughly every 30 seconds, and a
resident daemon has just spent DUST on the previous call, so the pair never
matched on the first try and every call after the first paid a full emission
cycle.Measured with the wait instrumented, this was 39 s of a 60 s daemon call on a
local stack and 16-30 s of a ~36 s call on preprod, against 1.7 s of real work.
Comparing both snapshots at a single instant does not help, because the balance
genuinely differs after a spend.The pair check is removed. The strict-completion filter stays: that is what
guards against a stale dust tree root (InvalidDustSpendProof, error 170), and it
is all the transfer path has ever done. Per call this takes preprod from ~36 s
to ~27 s and a local stack from 60 s to ~29 s. -
3b39fb6: Sign contract transactions through
facade.signRecipe, so unshielded inputs reach the node signed.The contract paths signed by hand, ending each intent with
tx.intents.set(segment, signedIntent)and discarding the result.
Transaction.intentsis a WASM getter that builds a fresh JS Map on every read,
so this mutated a throwaway copy and the signature never reached WASM. Any
circuit producing unshielded UTxO inputs — anything callingreceiveUnshielded
— was submitted with those inputs and zero signatures, and the node rejected it
with1010 Invalid Transaction: Custom error: 192
(InputsSignaturesLengthMismatch).All four sites (call, deploy, and both maintenance paths) now call
facade.signRecipe, which returns a new recipe rather than mutating in place —
the same call the transfer path has always made. It also selects the right
signer per transaction type and stamps DUST registration signatures, neither of
which the hand-rolled helper did. The three copies of that helper are deleted.deploywas never affected, because it spends only DUST on fees, which is why
the failure looked specific tocall. -
77edf22: Replace the wallet SDK's DUST fee-balancing loop with one that terminates.
The SDK's
computeBalancingRecipe(wallet-sdk-dust-wallet4.2.0) re-selects
dust coins until they cover the fee of the transaction they produce, with no
iteration cap and no progress check. It also seeds its first pass with a
negative dust imbalance and every later pass with a positive fee; the balancer
reads the positive seed as a surplus, adds an output and selects nothing, so
only the first pass can ever converge. A wallet holding several part-drained
dust coins under-covers on that first pass and the loop then spins forever,
building and proof-erasing a WASM transaction on the calling thread each time
until the process runs out of memory.Moth now supplies its own transacting capability through the SDK's documented
V1Builder.withTransactingseam (sync/dust-transacting.ts). It keeps the
SDK's fee arithmetic —dryRunFeeandcalculateFee, the WASM parts — and
replaces only the control flow: each pass covers the outstanding deficit from
coins not yet selected, then re-prices the transaction with everything selected
so far. A pass that does not converge has strictly grown the input set, so the
loop is bounded by the number of coins; running out surfaces as the SDK's own
InsufficientFundsError. BothestimateFeeandbalanceTransactionsroute
through the replaced method, so the fee preview and the real spend take the
same path. Coin selection order is unchanged.Trade-off: this couples Moth to the SDK's exported implementation class and
three of its methods. The test suite pins that surface so an SDK upgrade that
changes it fails in CI rather than silently reverting to the non-terminating
loop.effectbecomes a direct dependency of@shieldedtech/moth-wallet(it
was already in the tree via the SDK) because the capability returns the SDK's
Eithervalues. The same loop is proposed upstream in
docs/upstream-issues/dust-fee-balancing-nontermination.md. -
aa3c276: Pay DUST fees from the largest coin first, so fee balancing terminates.
The wallet SDK's dust fee balancer (
wallet-sdk-dust-wallet4.2.0,
computeBalancingRecipe) loops until the coins it selected cover the fee that
selecting them produced, with no iteration cap and no progress check. Its
default selector takes the smallest coin first, so a wallet holding several
part-drained DUST coins spends a handful of them, which enlarges the
transaction, which raises the fee past what those coins cover — and the loop
never exits. It also never fails: only the first pass can converge, because
that pass is seeded with a negative dust imbalance while every later pass is
seeded with a positive fee, which sends the balancer down its add-an-output
branch and selects no inputs at all. Each pass deserialises and erases proofs
on a fresh WASM transaction while holding the thread, so the wallet stops
responding and its WASM heap grows until the process dies.Moth now sets largest-first selection on the dust wallet through the SDK's
documentedV1Builder.withCoinSelectionextension point
(sync/dust-coin-selection.ts), on both the restore-from-cache and
start-from-secret-key paths. One coin near its generation cap covers a fee
outright, so the first pass converges — which is the only pass that can.This costs nothing in DUST fragmentation: a dust spend is one-in-one-out (the
ledger nullifies the coin and mints a successor worth the remainder), coin
count is pinned to the number of registered NIGHT UTXOs, and a coin's value
regenerates toward its cap. Draining the fullest coin therefore rotates across
backing UTXOs on its own as the drained ones refill, and produces a smaller
transaction than spending eight coins to reach the same fee.This is a mitigation, not a fix. A wallet whose dust is spread evenly
across coins that are all far below fee size still spins, because no single
coin covers the fee — pinned as a test, and filed upstream with the iteration
traces indocs/upstream-issues/dust-fee-balancing-nontermination.md, which
asks for the progress check that would close it. Transaction construction,
signing, and proving are unchanged. -
19a1a23: Pre-seed the DUST state of restored wallets when the indexer proves it safe.
Pre-seeding only ran for a wallet whose birthday was at or after the reference
height — the birthday being the wallet's only local proof that it has no earlier
history to skip. A wallet restored from a mnemonic or hex seed has no birthday,
and a wallet whose cache was cleared has one that predates the reference, so
both walked DUST from genesis: 1.4M events at ~293 events/s, about 78 minutes on
preprod, of which dust is 99%.For the dust part there is a second proof. All of a wallet's DUST descends from
generation entries owned by its dust key, and the indexer can say whether that
key owned any entry at the reference height:Block.dustGenerationEndIndex
gives the generation tree's sizeNat that height, and the bounded
dustGenerations(dustAddress, 0, N − 1)subscription ends withcomplete— no
owned entry before it is a positive "none" (~1.1 s on preprod). When the
birthday rule fails and the dust cache is missing,startWalletSyncruns that
probe and on "none" seeds dust alone from the reference; shielded and
unshielded still scan from genesis, which is quick. The hour becomes the
reference's deserialize plus catch-up.Fails closed: an owned entry, an indexer without the field (pre-4.2), a timeout
or an error all keep the genesis walk, with the reason in the sync progress
line. The decision is the purepreSeedPlan(sync/preseed-parts.ts); the
probe issync/dust-history.ts;IndexerClient.getDustGenerationEndIndexand
dustAddressForKey(a dust address from the typed key, no seed needed) support
it. ADR 0003 records the exception.
@shieldedtech/moth-tui@0.14.0
@shieldedtech/moth-cli@0.14.0
Moth Wallet Extension v0.13.0
What's Changed
Documentation
- docs: enforce DCO contribution policy by @chrisferry in #32
Other changes
- fix: correct OpenSSF Scorecard badge URL by @chrisferry in #18
- fix: skip stable publish when versions are current by @chrisferry in #19
- fix: normalize npm version detection by @chrisferry in #20
- fix: gate stable publish on package changes by @chrisferry in #21
- chore(security): harden Scorecard controls by @chrisferry in #24
- fix(release): reconcile package state by @skylar-simoncelli in #27
- fix(release): preserve queued version runs by @skylar-simoncelli in #28
- fix(release): skip npm validation for published versions by @chrisferry in #26
- fix(release): ignore empty changesets by @chrisferry in #29
- NOTICE is missing the midnight-wallet-cli attribution that source comments point to by @nel349 in #22
- fix(extension): ask which account to unlock when there is more than one by @bobblessinghartley in #33
- test: enforce the isomorphism and bundle boundaries with tests by @jtsang586 in #23
- fix(release): run version install through yarn script by @chrisferry in #42
- docs: document moth transfer as it actually works by @bobblessinghartley in #64
- fix(cli): five usability and correctness fixes from a manual test pass by @bobblessinghartley in #65
- fix(core): batch transfers must not parse money with parseFloat by @bobblessinghartley in #74
- fix(tui): bound the Wallet State coin lists to the terminal height by @GiuseppeSalvatoreShielded in #75
- fix(cli): refuse mainnet at the flag, and generate the oclif manifest by @bobblessinghartley in #52
- fix(sync): report progress that is neither invented nor erased by @bobblessinghartley in #82
- fix(cli): show what a transfer can actually spend by @bobblessinghartley in #83
- fix(tui): stop the sync before freeing the keys on quit by @bobblessinghartley in #84
- feat(network): replace the local network with undeployed everywhere by @jtsang586 in #87
- feat(sync): witness cursors so an indexer renumbering is detected, not silent by @bobblessinghartley in #50
- fix(extension): bound how long shutting down can take by @SupernaviX in #86
- add feature preseeding, timings and DUST across CLI and TUI by @bobblessinghartley in #108
- docs(extension): say how long the revealed seed is, and that the phra… by @bobblessinghartley in #110
- fix(ci): close the scan gate's critical findings by @bobblessinghartley in #114
- fix(release): migrate Changesets v2 workflow by @chrisferry in #115
- feat(extension): restore from and reveal a hex seed by @GiuseppeSalvatoreShielded in #101
- extension: add "Clear cache and resync" to Settings by @chrispalaskas in #124
- core, extension, cli: detect and steer around the devnet dust-ledger wedge by @chrispalaskas in #126
- extension: show what a dApp transaction takes from the wallet before approval by @chrispalaskas in #123
- chore: version packages by @github-actions[bot] in #132
New Contributors
- @skylar-simoncelli made their first contribution in #27
- @nel349 made their first contribution in #22
- @dependabot[bot] made their first contribution in #35
- @GiuseppeSalvatoreShielded made their first contribution in #75
- @SupernaviX made their first contribution in #86
- @github-actions[bot] made their first contribution in #132
Full Changelog: https://github.com/shieldedtech/moth-wallet/compare/@shieldedtech/moth-cli@0.12.1...moth-extension-v0.13.0
@shieldedtech/moth-wallet@0.13.0
Minor Changes
-
a17b719: Show what a dApp transaction takes from the wallet before the user approves it.
When a connected dApp calls
balanceSealedTransactionor
balanceUnsealedTransaction, the wallet is asked to cover whatever the dApp's
transaction is short of — and the approval screen said only "Network fee: paid
in DUST". The user was approving a spend without being told the amount or the
token. The screen now lists, per token, what the wallet has to supply ("You
pay") and any surplus it collects back ("You get back"), plus the number of
contract calls when there are any. If the transaction cannot be decoded, it
says so in a visible warning rather than showing nothing.The amounts come from the transaction itself, before anything is balanced,
booked or spent: core gainssummarizeTransaction/
summarizeConnectorTransaction(sync/tx-summary.ts), which sums the ledger's
Transaction.imbalances(segment)over the guaranteed section and every intent.
A negative imbalance is what the wallet must put in; a positive one is change.
The sign convention is pinned by a test against the real ledger. Fees are not
included — they are only known once the wallet has balanced and proven its own
segment, and are always paid in DUST. -
b49c96d: Give the CLI and TUI the pre-seed, timings and DUST-registration behaviour the
extension already had.Birthdays.
chainTipmoves from the extension's background handlers into
core, andwallet generateon both surfaces records the chain tip as the new
wallet's birthday. Without one thereference.height <= birthdayguard can never
pass, so no CLI or TUI wallet could ever be pre-seeded — the difference between
29.3s and 78.6 min on preprod. Imports still get none, deliberately: a restored
wallet may hold funds at any height, and seeding it past its own history would
lose them silently.moth preseed status|refresh|build. Thin wrappers over core functions
that already existed but had no caller outside the extension.refreshis the
one worth having — 9.1s to catch a reference up, against 53.6 min to rebuild it
from genesis, which is what someone does by hand when the command is missing.
buildsays how long it will take before starting, because an unattended command
that appears to hang for an hour is indistinguishable from a broken one.Phase timings on disk.
createFileTimingStorebacks the existing
storage-agnostic recorder with~/.moth/timings.json— the path
docs/BENCHMARKING.mdalready documented and nothing wrote.moth diagnostics timingsshows the timeline as deltas, and recording stays off until switched on.
A headless sync is where this matters most: it is the surface with no other
signal about where the time went.DUST registration in the TUI.
DustRegistrationNotYetErroris now caught
distinctly, so a wallet whose NIGHT is too new to cover the registration fee is
told "not yet" instead of shown the raw SDK error as a failure. The panel and the
CLI already did this; the TUI was the surface still reporting it as a defect. -
0508a38: Four CLI fixes found by a manual test pass, none of which the test suite could see.
Amounts are parsed strictly (#63).
moth transferusedparseFloatand
Math.round, andparseFloatkeeps whatever prefix it understands:1,5was
accepted as 1 NIGHT, losing a third of the value with no warning;0.0000001
rounded to zero base units and was submitted as a transfer that moved nothing
and still paid a fee;1e3became 1000 NIGHT;1abcbecame 1. A shared
parseNightAmountin core now refuses all of them, in BigInt, with a message
naming what is wrong — anddaemon transferalready did it this way, so the two
paths finally agree.moth transfercan select a token (#62). It hardcodedNIGHT_TOKEN_ID, so a
wallet holding anything else could spend it only throughdaemon transfer --token-id. Same flag name and default here. The positional amount stays a NIGHT
decimal and is refused for other tokens, directing to--amountin raw base units
— mis-scaling a token by NIGHT's 10⁶ would be worse than refusing.moth wallet export-phraseexists (#59).WalletManager.exportPhraseand
exportSeedHexhave always been in core, and the extension exposes them, but no
CLI command did. So the CLI had no backup path — a phrase was shown once at
wallet generateand never again — no way to move a wallet between machines, and
no way to recover a seed for a keystore you hold the passphrase to. Confirmed by
default and refused non-interactively without--yes, followingwallet remove.
A wallet imported from a hex seed says so rather than presenting a seed as a
phrase.wallet addresstakes--wallet(#60). It was the only command in the CLI
requiring--name, which made it the only one that could not act on the active
wallet:wallet use w1thenwallet addressfailed with "Missing required flag
name".--nameremains as an alias.dust registerdistinguishes empty from done (#58).designateForDust
returnsnullboth when every UTXO is already designated and when there are no
NIGHT UTXOs at all, and the command reported the first for both — vacuously true
of an empty wallet, and read as success. An unfunded wallet now gets "No NIGHT to
designate" and a non-zero exit. -
426b757: Detect and steer users away from the devnet dust-ledger wedge (docs/bugs-found #15-style defect).
Some devnets (midnight-node 2.0.0-rc.4 / midnight-ledger 9.1.0.0-rc.3) can
enter a state where a DUST registration leaves the node's dust ledger unable
to reconcile with any wallet's, permanently — every subsequent dust spend,
from every wallet, fails with the sameInvalidDustSpendProof/
Custom error: 170signature that a normal transient race also produces.
Only a chain reset clears it; retrying does not. This is not a Moth defect —
seedocs/upstream-issues/dust-ledger-wedge-invalid-dust-spend-proof.mdfor
the evidence and the draft issue againstmidnightntwrk/midnight-node/
midnight-ledger— but Moth users hit it on shared and local devnets, so
Moth now detects and steers around it rather than retrying forever.Detection (
@shieldedtech/moth-wallet,sync/dust-ledger-health.ts): a
run of consecutive, independently built submissions rejected with the same
ambiguous signature — with the chain confirmed still producing new blocks
meanwhile — is
surfaced asDustLedgerWedgedErrorinstead of a generic failure. Wired into
every fee-paying submission path: the extension's offscreen host,moth transfer/moth dust register, and the daemon'stransferTokens/
dustRegister/dustDeregisterRPCs (used by both the headless CLI daemon
and the TUI).Registration UX (
@shieldedtech/moth-extension): the "Register for
DUST" flow now warns before registering a NIGHT coin that has sat
unregistered for more than a minute — the only pattern with zero known
failures across four documented occurrences is registering within seconds of
funding — and a wallet that has never registered is nudged to do so as soon
as new NIGHT is observed, rather than waiting for the user to find the Dust
screen on their own.Repro harness:
packages/cli/tests/integration/daemon/dust-wedge-repro.test.ts
isolates the fund-to-register delay as the one variable prior occurrences
didn't control for, holding UTXO size fixed at the size every known success
used (10,000,000 NIGHT).No change to transaction construction, signing, or proving — every
registration involved in the underlying defect was accepted by the ledger,
so this only classifies what a rejection means after the fact. -
eb4d56a: The extension can restore an account from a raw hex seed. Previously it
accepted a 24-word phrase and nothing else, which made accounts created from a
seed permanently unreachable there.That was not a policy — a wallet created from a hex seed has no mnemonic and
can never be given one, because BIP-39's phrase-to-seed step is a one-way KDF.
There was nothing to type into the word grid. Meanwhile the TUI
(tui/src/app.tsx) and the CLI (moth wallet import --seed-hex) both had the
path, and the extension's own session model is seed-based end to end: it derives
seedHexat unlock and carries it through every operation. A mnemonic was only
ever a transport format for the seed, and the extension accepted just that one
format at the door.The plumbing was the only thing missing.
walletsin the browser facade is a
WalletManager, soimportFromSeedwas already callable from the offscreen
document;unlockalready handled aseed:keystore.ImportWalletRequestis
now a union — exactly one ofmnemonic/seed, enforced by the type rather
than a runtime check — threaded through the background/offscreen RPC, and the
offscreen host routes to whichever core call fits. The two stay separate calls,
not one with a branch:importruns the BIP-39 checksum,importFromSeed
shape-checks the hex.The restore screen now offers both artifacts as tabs on one page. No
"which do you have?" step first: anyone restoring already knows which they hold,
so that step would cost a click and gather nothing.New in core:
wallet/hex-seed.ts, andimportFromSeednow validates. It had
no validation whatsoever — a malformed seed reached the SDK and surfaced as a
bareInvalid seed, and a merely wrong-length seed was accepted outright,
silently producing a different wallet.checkHexSeedreturns a machine-readable
problem so each surface can word it itself (the extension localises it; the CLI
and TUI usedescribeHexSeedProblem). The TUI and CLI inherit the fix.**The validation is shaped around the fact ...