Skip to content

Releases

Moth Wallet Extension v0.14.1

Choose a tag to compare

@github-actions github-actions released this 20 Sep 23:21
ab235d7

What's Changed

Bug fixes

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

Choose a tag to compare

@github-actions github-actions released this 20 Sep 23:22
ab235d7

Patch Changes

  • 0f0afa3: Stop the dedup filter from punching holes in a replay, and say what a non-linear
    insert actually means.

    partitionByAppliedIndex filtered the whole batch by predicate, dropping every
    event with id <= appliedIndex wherever 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

Choose a tag to compare

@github-actions github-actions released this 20 Sep 23:22
ab235d7

Patch Changes

  • Updated dependencies [0f0afa3]
    • @shieldedtech/moth-wallet@0.14.1

@shieldedtech/moth-cli@0.14.1

Choose a tag to compare

@github-actions github-actions released this 20 Sep 23:22
ab235d7

Patch Changes

  • Updated dependencies [0f0afa3]
    • @shieldedtech/moth-wallet@0.14.1
    • @shieldedtech/moth-tui@0.14.1

Moth Wallet Extension v0.14.0

Choose a tag to compare

@github-actions github-actions released this 19 Sep 23:19
128b8c2

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

New Contributors

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

Choose a tag to compare

@github-actions github-actions released this 19 Sep 23:19
128b8c2

Minor Changes

  • 55a878c: Add a proveTransaction daemon verb, and honour MOTH_HOME.

    proveTransaction builds, 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;
    submitTransaction already 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-spend applies as it does to transferTokens.

    MOTH_HOME now overrides the ~/.moth root 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.

    loadContractArtifact wrapped its whole managed/contract/ branch in a
    try/catch that discarded the error, so a module that existed but failed to
    load was reported as No 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 missing contract/ 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.intents is 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 calling receiveUnshielded
    — was submitted with those inputs and zero signatures, and the node rejected it
    with 1010 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.

    deploy was never affected, because it spends only DUST on fees, which is why
    the failure looked specific to call.

  • 77edf22: Replace the wallet SDK's DUST fee-balancing loop with one that terminates.

    The SDK's computeBalancingRecipe (wallet-sdk-dust-wallet 4.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.withTransacting seam (sync/dust-transacting.ts). It keeps the
    SDK's fee arithmetic — dryRunFee and calculateFee, 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. Both estimateFee and balanceTransactions route
    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. effect becomes a direct dependency of @shieldedtech/moth-wallet (it
    was already in the tree via the SDK) because the capability returns the SDK's
    Either values. 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-wallet 4.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
    documented V1Builder.withCoinSelection extension 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 in docs/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 size N at that height, and the bounded
    dustGenerations(dustAddress, 0, N − 1) subscription ends with complete — 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, startWalletSync runs 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 pure preSeedPlan (sync/preseed-parts.ts); the
    probe is sync/dust-history.ts; IndexerClient.getDustGenerationEndIndex and
    dustAddressForKey (a dust address from the typed key, no seed needed) support
    it. ADR 0003 records the exception.

@shieldedtech/moth-tui@0.14.0

Choose a tag to compare

@github-actions github-actions released this 19 Sep 23:19
128b8c2

Patch Changes

  • Updated dependencies [3b39fb6]
  • Updated dependencies [3b39fb6]
  • Updated dependencies [3b39fb6]
  • Updated dependencies [55a878c]
  • Updated dependencies [77edf22]
  • Updated dependencies [aa3c276]
  • Updated dependencies [19a1a23]
    • @shieldedtech/moth-wallet@0.14.0

@shieldedtech/moth-cli@0.14.0

Choose a tag to compare

@github-actions github-actions released this 19 Sep 23:19
128b8c2

Patch Changes

  • Updated dependencies [3b39fb6]
  • Updated dependencies [3b39fb6]
  • Updated dependencies [3b39fb6]
  • Updated dependencies [55a878c]
  • Updated dependencies [77edf22]
  • Updated dependencies [aa3c276]
  • Updated dependencies [19a1a23]
    • @shieldedtech/moth-wallet@0.14.0
    • @shieldedtech/moth-tui@0.14.0

Moth Wallet Extension v0.13.0

Choose a tag to compare

@github-actions github-actions released this 03 Sep 15:54
0a6ac60

What's Changed

Documentation

Other changes

New Contributors

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

Choose a tag to compare

@github-actions github-actions released this 03 Sep 15:55
0a6ac60

Minor Changes

  • a17b719: Show what a dApp transaction takes from the wallet before the user approves it.

    When a connected dApp calls balanceSealedTransaction or
    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 gains summarizeTransaction /
    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. chainTip moves from the extension's background handlers into
    core, and wallet generate on both surfaces records the chain tip as the new
    wallet's birthday. Without one the reference.height <= birthday guard 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. refresh is 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.
    build says 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. createFileTimingStore backs the existing
    storage-agnostic recorder with ~/.moth/timings.json — the path
    docs/BENCHMARKING.md already documented and nothing wrote. moth diagnostics timings shows 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. DustRegistrationNotYetError is 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 transfer used parseFloat and
    Math.round, and parseFloat keeps whatever prefix it understands: 1,5 was
    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; 1e3 became 1000 NIGHT; 1abc became 1. A shared
    parseNightAmount in core now refuses all of them, in BigInt, with a message
    naming what is wrong — and daemon transfer already did it this way, so the two
    paths finally agree.

    moth transfer can select a token (#62). It hardcoded NIGHT_TOKEN_ID, so a
    wallet holding anything else could spend it only through daemon transfer --token-id. Same flag name and default here. The positional amount stays a NIGHT
    decimal and is refused for other tokens, directing to --amount in raw base units
    — mis-scaling a token by NIGHT's 10⁶ would be worse than refusing.

    moth wallet export-phrase exists (#59). WalletManager.exportPhrase and
    exportSeedHex have 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 generate and 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, following wallet remove.
    A wallet imported from a hex seed says so rather than presenting a seed as a
    phrase.

    wallet address takes --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 w1 then wallet address failed with "Missing required flag
    name". --name remains as an alias.

    dust register distinguishes empty from done (#58). designateForDust
    returns null both 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 same InvalidDustSpendProof /
    Custom error: 170 signature that a normal transient race also produces.
    Only a chain reset clears it; retrying does not. This is not a Moth defect —
    see docs/upstream-issues/dust-ledger-wedge-invalid-dust-spend-proof.md for
    the evidence and the draft issue against midnightntwrk/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 as DustLedgerWedgedError instead of a generic failure. Wired into
    every fee-paying submission path: the extension's offscreen host, moth transfer / moth dust register, and the daemon's transferTokens /
    dustRegister / dustDeregister RPCs (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
    seedHex at 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. wallets in the browser facade is a
    WalletManager, so importFromSeed was already callable from the offscreen
    document; unlock already handled a seed: keystore. ImportWalletRequest is
    now a union — exactly one of mnemonic / 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: import runs 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, and importFromSeed now validates. It had
    no validation whatsoever — a malformed seed reached the SDK and surfaced as a
    bare Invalid seed, and a merely wrong-length seed was accepted outright,
    silently producing a different wallet. checkHexSeed returns a machine-readable
    problem so each surface can word it itself (the extension localises it; the CLI
    and TUI use describeHexSeedProblem). The TUI and CLI inherit the fix.

    **The validation is shaped around the fact ...

Read more