@shieldedtech/moth-wallet 0.14.1 → 0.15.0-next.20260925125759-5ffbb70
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +1485 -0
- package/dist/contract/call.d.ts.map +1 -1
- package/dist/contract/call.js +11 -49
- package/dist/contract/call.js.map +1 -1
- package/dist/contract/deploy.d.ts.map +1 -1
- package/dist/contract/deploy.js +11 -49
- package/dist/contract/deploy.js.map +1 -1
- package/dist/contract/maintenance.d.ts.map +1 -1
- package/dist/contract/maintenance.js +21 -92
- package/dist/contract/maintenance.js.map +1 -1
- package/dist/contract/wallet-provider.d.ts +30 -0
- package/dist/contract/wallet-provider.d.ts.map +1 -0
- package/dist/contract/wallet-provider.js +48 -0
- package/dist/contract/wallet-provider.js.map +1 -0
- package/dist/daemon/protocol.d.ts +1 -1
- package/dist/daemon/protocol.d.ts.map +1 -1
- package/dist/daemon/protocol.js.map +1 -1
- package/dist/daemon/wallet-handlers.d.ts.map +1 -1
- package/dist/daemon/wallet-handlers.js +30 -6
- package/dist/daemon/wallet-handlers.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -1
- package/dist/index.js.map +1 -1
- package/dist/proof/provider.d.ts +10 -2
- package/dist/proof/provider.d.ts.map +1 -1
- package/dist/proof/provider.js +35 -14
- package/dist/proof/provider.js.map +1 -1
- package/dist/sync/activity.d.ts +1 -1
- package/dist/sync/activity.d.ts.map +1 -1
- package/dist/sync/activity.js +13 -2
- package/dist/sync/activity.js.map +1 -1
- package/dist/sync/dust-transacting.d.ts +29 -3
- package/dist/sync/dust-transacting.d.ts.map +1 -1
- package/dist/sync/dust-transacting.js +87 -28
- package/dist/sync/dust-transacting.js.map +1 -1
- package/dist/sync/ledger-routing.d.ts +80 -0
- package/dist/sync/ledger-routing.d.ts.map +1 -0
- package/dist/sync/ledger-routing.js +130 -0
- package/dist/sync/ledger-routing.js.map +1 -0
- package/dist/sync/operations.d.ts +24 -9
- package/dist/sync/operations.d.ts.map +1 -1
- package/dist/sync/operations.js +46 -35
- package/dist/sync/operations.js.map +1 -1
- package/dist/sync/preseed.d.ts +4 -0
- package/dist/sync/preseed.d.ts.map +1 -1
- package/dist/sync/preseed.js +14 -14
- package/dist/sync/preseed.js.map +1 -1
- package/dist/sync/tx-summary.d.ts +21 -7
- package/dist/sync/tx-summary.d.ts.map +1 -1
- package/dist/sync/tx-summary.js +41 -10
- package/dist/sync/tx-summary.js.map +1 -1
- package/dist/sync/wallet-sync.d.ts +10 -0
- package/dist/sync/wallet-sync.d.ts.map +1 -1
- package/dist/sync/wallet-sync.js +66 -82
- package/dist/sync/wallet-sync.js.map +1 -1
- package/dist/types/wallet.d.ts +19 -15
- package/dist/types/wallet.d.ts.map +1 -1
- package/dist/wallet/address.d.ts +20 -5
- package/dist/wallet/address.d.ts.map +1 -1
- package/dist/wallet/address.js +25 -9
- package/dist/wallet/address.js.map +1 -1
- package/dist/wallet/manager.d.ts.map +1 -1
- package/dist/wallet/manager.js +6 -17
- package/dist/wallet/manager.js.map +1 -1
- package/dist/wallet/sign-message.d.ts +4 -3
- package/dist/wallet/sign-message.d.ts.map +1 -1
- package/dist/wallet/sign-message.js +3 -3
- package/dist/wallet/sign-message.js.map +1 -1
- package/package.json +3 -3
- package/dist/sync/sdk-dedup.d.ts +0 -69
- package/dist/sync/sdk-dedup.d.ts.map +0 -1
- package/dist/sync/sdk-dedup.js +0 -181
- package/dist/sync/sdk-dedup.js.map +0 -1
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,1485 @@
|
|
|
1
|
+
# @shieldedtech/moth-wallet
|
|
2
|
+
|
|
3
|
+
## 0.15.0-next.20260925125759-5ffbb70
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- 773443b: The daemon's `submitTransaction` answers `PROTOCOL_VERSION_MISMATCH` instead of `INVALID_PARAMS`
|
|
8
|
+
when the hex is a transaction built for the other side of the ledger fork, or when the wallets
|
|
9
|
+
cross the fork before it is submitted. Hex that no ledger reads still gets `INVALID_PARAMS`.
|
|
10
|
+
`RpcErrorCode` gains the new member, and the CLI renders it as `INVALID_INPUT` with a "Protocol
|
|
11
|
+
version mismatch" prefix.
|
|
12
|
+
- d07668a: Settings → Network shows where the account's wallets stand on the protocol version line, as the
|
|
13
|
+
wallet SDK reports it: the version transactions are built for, the ledger it puts the wallet on
|
|
14
|
+
(ledger-v8 below the v9 fork, ledger-v9 from it) and whether the three wallets have settled on it
|
|
15
|
+
or are still crossing a fork. Core exposes it as `protocolStatus(facade)`.
|
|
16
|
+
- fa2a009: Upgrade to `@midnightntwrk/wallet-sdk` 2.0.0-rc.0, which brings `@midnightntwrk/ledger-v9`
|
|
17
|
+
1.0.0-rc.5. The SDK runs ledger-v8 below the chain's v9 fork and ledger-v9 from it, and its
|
|
18
|
+
wallets follow a live chain across the boundary, so moth reaches networks on either side with one
|
|
19
|
+
build.
|
|
20
|
+
|
|
21
|
+
- Wallets start from per-role seeds: `WalletKeys` is now `{shielded, unshielded, dust}` seeds
|
|
22
|
+
rather than ledger key objects, and the wallet holds the keys it derives from them for as long
|
|
23
|
+
as it runs. Transaction-building methods no longer take key material.
|
|
24
|
+
- Transactions travel as version-stamped handles (`FinalizedTransaction`). Hex payloads on the
|
|
25
|
+
daemon socket and dApp-supplied transactions are read with the ledger the facade is acting at,
|
|
26
|
+
through the SDK's `WalletFacade.adoptTransaction`; `transactionHashOf` and
|
|
27
|
+
`activeProtocolVersion` read hashes and versions off the handles.
|
|
28
|
+
- Proving routes by protocol version: one proof server serves both sides, and the in-process
|
|
29
|
+
prover, including the extension's in-worker one, is registered per ledger version.
|
|
30
|
+
- The client-side dedup of re-sent boundary events (`sync/sdk-dedup.ts`) is removed; the fix
|
|
31
|
+
landed upstream in both wallet variants.
|
|
32
|
+
- DUST fee balancing keeps its two fixes, largest-first coin selection and a loop that terminates,
|
|
33
|
+
on both sides of the fork: SDK 2.0 still ships the unbounded loop in both dust variants, so the
|
|
34
|
+
dust wallet is `DustWallet` rebuilt through `CustomForkingDustWallet` with both fixes on each.
|
|
35
|
+
- Contract deploy/call/maintenance keep authoring ledger-v8 transactions through midnight-js
|
|
36
|
+
4.1.1 and refuse a network that has crossed to ledger-v9.
|
|
37
|
+
- Message signing returns the schnorr hex value of ledger-v9's tagged signature, so the wire
|
|
38
|
+
format is unchanged for connectors.
|
|
39
|
+
- Browser bundles now carry both ledgers' WASM, as every SDK 2.0 consumer does.
|
|
40
|
+
- The wait for the first facade emission no longer reaches for its own subscription: the 2.0
|
|
41
|
+
facade replays state synchronously on subscribe, which turned that into a startup crash.
|
|
42
|
+
- The SDK's unshielded subscription now reads `protocolVersion` off the indexer's progress frame,
|
|
43
|
+
so unshielded sync needs an indexer that exposes it (midnight-indexer 4.4.0-rc.2 / `25da0487`
|
|
44
|
+
or later). Against an older indexer the unshielded wallet never connects.
|
|
45
|
+
|
|
46
|
+
## 0.14.1
|
|
47
|
+
|
|
48
|
+
### Patch Changes
|
|
49
|
+
|
|
50
|
+
- 0f0afa3: Stop the dedup filter from punching holes in a replay, and say what a non-linear
|
|
51
|
+
insert actually means.
|
|
52
|
+
|
|
53
|
+
`partitionByAppliedIndex` filtered the whole batch by predicate, dropping every
|
|
54
|
+
event with `id <= appliedIndex` wherever it sat. For an ascending batch that is
|
|
55
|
+
the intended boundary-duplicate filter. For an out-of-order one it removes an
|
|
56
|
+
already-applied event from the *middle* and hands the SDK a replay with a gap —
|
|
57
|
+
and the ledger tree then rejects the insert:
|
|
58
|
+
|
|
59
|
+
```
|
|
60
|
+
values inserted non-linearly into dust generation tree;
|
|
61
|
+
expected to insert index 337423, but received 337429.
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
Note the direction: the SDK bug this wrapper exists to work around re-sends a
|
|
65
|
+
boundary event, so the tree receives an index *lower* than it expects. This is
|
|
66
|
+
the mirror image — an index *higher* than expected, because events the tree
|
|
67
|
+
needed never arrived. The workaround for one could produce the other, and only
|
|
68
|
+
for the part whose cursor and tree had drifted apart, which is why a single
|
|
69
|
+
sub-wallet would stall while the others finished.
|
|
70
|
+
|
|
71
|
+
The filter now drops only a leading contiguous run of already-applied events.
|
|
72
|
+
When an already-applied id appears after a fresh one the batch is not the
|
|
73
|
+
ascending stream the filter assumes, so nothing is dropped and the SDK's own
|
|
74
|
+
(more conservative) handling decides. The existing test named "drops only the
|
|
75
|
+
already-applied prefix" described this behaviour already; the implementation now
|
|
76
|
+
matches it.
|
|
77
|
+
|
|
78
|
+
Non-linear insert failures are also no longer bare WASM strings surfacing four
|
|
79
|
+
layers down with no mention of sync state. They now carry the cursor and batch
|
|
80
|
+
that produced them and say that retrying cannot help — the same batch replays
|
|
81
|
+
from the same cursor forever — and that the fix is to clear that part's cache:
|
|
82
|
+
|
|
83
|
+
```
|
|
84
|
+
appliedIndex=1291234, replayed 2 event(s) 1291235..1291240,
|
|
85
|
+
0 dropped as already applied. Clear this wallet part's sync cache and resync.
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
The original error is preserved as `cause`. Unrelated errors pass through
|
|
89
|
+
untouched.
|
|
90
|
+
|
|
91
|
+
## 0.14.0
|
|
92
|
+
|
|
93
|
+
### Minor Changes
|
|
94
|
+
|
|
95
|
+
- 55a878c: Add a `proveTransaction` daemon verb, and honour `MOTH_HOME`.
|
|
96
|
+
|
|
97
|
+
`proveTransaction` builds, balances, proves and signs a transfer, then returns
|
|
98
|
+
the finalized transaction as hex instead of submitting it. Programmatic clients
|
|
99
|
+
that need to hold a proof and submit it later had no verb for the prove half;
|
|
100
|
+
`submitTransaction` already covered the other. The returned hex binds fee-side
|
|
101
|
+
DUST UTXOs by nullifier, so the wallet must stay quiet until submission — the
|
|
102
|
+
L3 modal says so, and `--max-spend` applies as it does to `transferTokens`.
|
|
103
|
+
|
|
104
|
+
`MOTH_HOME` now overrides the `~/.moth` root for both the filesystem storage
|
|
105
|
+
adapter and the daemon socket path, so several isolated instances can run from
|
|
106
|
+
one account. A relative value is rejected rather than resolved against the
|
|
107
|
+
working directory, which would otherwise put a wallet and its socket under
|
|
108
|
+
different roots.
|
|
109
|
+
|
|
110
|
+
### Patch Changes
|
|
111
|
+
|
|
112
|
+
- 3b39fb6: Report the real error when a contract artifact fails to load.
|
|
113
|
+
|
|
114
|
+
`loadContractArtifact` wrapped its whole `managed/contract/` branch in a
|
|
115
|
+
`try`/`catch` that discarded the error, so a module that existed but failed to
|
|
116
|
+
load was reported as `No contract module found in <path>`. The common cause is a
|
|
117
|
+
compact runtime version mismatch, whose own message names both versions and
|
|
118
|
+
points straight at the fix. Only a missing `contract/` directory now falls
|
|
119
|
+
through to the remaining strategies.
|
|
120
|
+
- 3b39fb6: Stop waiting for two equal DUST balances before building a contract transaction.
|
|
121
|
+
|
|
122
|
+
Before building a contract transaction the contract paths waited for the wallet
|
|
123
|
+
to be strictly synced, and then for two consecutive emissions carrying an
|
|
124
|
+
identical DUST balance. `facade.state()` emits roughly every 30 seconds, and a
|
|
125
|
+
resident daemon has just spent DUST on the previous call, so the pair never
|
|
126
|
+
matched on the first try and every call after the first paid a full emission
|
|
127
|
+
cycle.
|
|
128
|
+
|
|
129
|
+
Measured with the wait instrumented, this was 39 s of a 60 s daemon call on a
|
|
130
|
+
local stack and 16-30 s of a ~36 s call on preprod, against 1.7 s of real work.
|
|
131
|
+
Comparing both snapshots at a single instant does not help, because the balance
|
|
132
|
+
genuinely differs after a spend.
|
|
133
|
+
|
|
134
|
+
The pair check is removed. The strict-completion filter stays: that is what
|
|
135
|
+
guards against a stale dust tree root (InvalidDustSpendProof, error 170), and it
|
|
136
|
+
is all the transfer path has ever done. Per call this takes preprod from ~36 s
|
|
137
|
+
to ~27 s and a local stack from 60 s to ~29 s.
|
|
138
|
+
- 3b39fb6: Sign contract transactions through `facade.signRecipe`, so unshielded inputs reach the node signed.
|
|
139
|
+
|
|
140
|
+
The contract paths signed by hand, ending each intent with
|
|
141
|
+
`tx.intents.set(segment, signedIntent)` and discarding the result.
|
|
142
|
+
`Transaction.intents` is a WASM getter that builds a fresh JS Map on every read,
|
|
143
|
+
so this mutated a throwaway copy and the signature never reached WASM. Any
|
|
144
|
+
circuit producing unshielded UTxO inputs — anything calling `receiveUnshielded`
|
|
145
|
+
— was submitted with those inputs and zero signatures, and the node rejected it
|
|
146
|
+
with `1010 Invalid Transaction: Custom error: 192`
|
|
147
|
+
(InputsSignaturesLengthMismatch).
|
|
148
|
+
|
|
149
|
+
All four sites (call, deploy, and both maintenance paths) now call
|
|
150
|
+
`facade.signRecipe`, which returns a new recipe rather than mutating in place —
|
|
151
|
+
the same call the transfer path has always made. It also selects the right
|
|
152
|
+
signer per transaction type and stamps DUST registration signatures, neither of
|
|
153
|
+
which the hand-rolled helper did. The three copies of that helper are deleted.
|
|
154
|
+
|
|
155
|
+
`deploy` was never affected, because it spends only DUST on fees, which is why
|
|
156
|
+
the failure looked specific to `call`.
|
|
157
|
+
- 77edf22: Replace the wallet SDK's DUST fee-balancing loop with one that terminates.
|
|
158
|
+
|
|
159
|
+
The SDK's `computeBalancingRecipe` (`wallet-sdk-dust-wallet` 4.2.0) re-selects
|
|
160
|
+
dust coins until they cover the fee of the transaction they produce, with no
|
|
161
|
+
iteration cap and no progress check. It also seeds its first pass with a
|
|
162
|
+
negative dust imbalance and every later pass with a positive fee; the balancer
|
|
163
|
+
reads the positive seed as a surplus, adds an output and selects nothing, so
|
|
164
|
+
only the first pass can ever converge. A wallet holding several part-drained
|
|
165
|
+
dust coins under-covers on that first pass and the loop then spins forever,
|
|
166
|
+
building and proof-erasing a WASM transaction on the calling thread each time
|
|
167
|
+
until the process runs out of memory.
|
|
168
|
+
|
|
169
|
+
Moth now supplies its own transacting capability through the SDK's documented
|
|
170
|
+
`V1Builder.withTransacting` seam (`sync/dust-transacting.ts`). It keeps the
|
|
171
|
+
SDK's fee arithmetic — `dryRunFee` and `calculateFee`, the WASM parts — and
|
|
172
|
+
replaces only the control flow: each pass covers the outstanding deficit from
|
|
173
|
+
coins not yet selected, then re-prices the transaction with everything selected
|
|
174
|
+
so far. A pass that does not converge has strictly grown the input set, so the
|
|
175
|
+
loop is bounded by the number of coins; running out surfaces as the SDK's own
|
|
176
|
+
`InsufficientFundsError`. Both `estimateFee` and `balanceTransactions` route
|
|
177
|
+
through the replaced method, so the fee preview and the real spend take the
|
|
178
|
+
same path. Coin selection order is unchanged.
|
|
179
|
+
|
|
180
|
+
Trade-off: this couples Moth to the SDK's exported implementation class and
|
|
181
|
+
three of its methods. The test suite pins that surface so an SDK upgrade that
|
|
182
|
+
changes it fails in CI rather than silently reverting to the non-terminating
|
|
183
|
+
loop. `effect` becomes a direct dependency of `@shieldedtech/moth-wallet` (it
|
|
184
|
+
was already in the tree via the SDK) because the capability returns the SDK's
|
|
185
|
+
`Either` values. The same loop is proposed upstream in
|
|
186
|
+
`docs/upstream-issues/dust-fee-balancing-nontermination.md`.
|
|
187
|
+
- aa3c276: Pay DUST fees from the largest coin first, so fee balancing terminates.
|
|
188
|
+
|
|
189
|
+
The wallet SDK's dust fee balancer (`wallet-sdk-dust-wallet` 4.2.0,
|
|
190
|
+
`computeBalancingRecipe`) loops until the coins it selected cover the fee that
|
|
191
|
+
selecting them produced, with no iteration cap and no progress check. Its
|
|
192
|
+
default selector takes the smallest coin first, so a wallet holding several
|
|
193
|
+
part-drained DUST coins spends a handful of them, which enlarges the
|
|
194
|
+
transaction, which raises the fee past what those coins cover — and the loop
|
|
195
|
+
never exits. It also never fails: only the first pass can converge, because
|
|
196
|
+
that pass is seeded with a negative dust imbalance while every later pass is
|
|
197
|
+
seeded with a positive fee, which sends the balancer down its add-an-output
|
|
198
|
+
branch and selects no inputs at all. Each pass deserialises and erases proofs
|
|
199
|
+
on a fresh WASM transaction while holding the thread, so the wallet stops
|
|
200
|
+
responding and its WASM heap grows until the process dies.
|
|
201
|
+
|
|
202
|
+
Moth now sets largest-first selection on the dust wallet through the SDK's
|
|
203
|
+
documented `V1Builder.withCoinSelection` extension point
|
|
204
|
+
(`sync/dust-coin-selection.ts`), on both the restore-from-cache and
|
|
205
|
+
start-from-secret-key paths. One coin near its generation cap covers a fee
|
|
206
|
+
outright, so the first pass converges — which is the only pass that can.
|
|
207
|
+
|
|
208
|
+
This costs nothing in DUST fragmentation: a dust spend is one-in-one-out (the
|
|
209
|
+
ledger nullifies the coin and mints a successor worth the remainder), coin
|
|
210
|
+
count is pinned to the number of registered NIGHT UTXOs, and a coin's value
|
|
211
|
+
regenerates toward its cap. Draining the fullest coin therefore rotates across
|
|
212
|
+
backing UTXOs on its own as the drained ones refill, and produces a smaller
|
|
213
|
+
transaction than spending eight coins to reach the same fee.
|
|
214
|
+
|
|
215
|
+
**This is a mitigation, not a fix.** A wallet whose dust is spread evenly
|
|
216
|
+
across coins that are all far below fee size still spins, because no single
|
|
217
|
+
coin covers the fee — pinned as a test, and filed upstream with the iteration
|
|
218
|
+
traces in `docs/upstream-issues/dust-fee-balancing-nontermination.md`, which
|
|
219
|
+
asks for the progress check that would close it. Transaction construction,
|
|
220
|
+
signing, and proving are unchanged.
|
|
221
|
+
- 19a1a23: Pre-seed the DUST state of restored wallets when the indexer proves it safe.
|
|
222
|
+
|
|
223
|
+
Pre-seeding only ran for a wallet whose birthday was at or after the reference
|
|
224
|
+
height — the birthday being the wallet's only local proof that it has no earlier
|
|
225
|
+
history to skip. A wallet restored from a mnemonic or hex seed has no birthday,
|
|
226
|
+
and a wallet whose cache was cleared has one that predates the reference, so
|
|
227
|
+
both walked DUST from genesis: 1.4M events at ~293 events/s, about 78 minutes on
|
|
228
|
+
preprod, of which dust is 99%.
|
|
229
|
+
|
|
230
|
+
For the dust part there is a second proof. All of a wallet's DUST descends from
|
|
231
|
+
generation entries owned by its dust key, and the indexer can say whether that
|
|
232
|
+
key owned any entry at the reference height: `Block.dustGenerationEndIndex`
|
|
233
|
+
gives the generation tree's size `N` at that height, and the bounded
|
|
234
|
+
`dustGenerations(dustAddress, 0, N − 1)` subscription ends with `complete` — no
|
|
235
|
+
owned entry before it is a positive "none" (~1.1 s on preprod). When the
|
|
236
|
+
birthday rule fails and the dust cache is missing, `startWalletSync` runs that
|
|
237
|
+
probe and on "none" seeds dust alone from the reference; shielded and
|
|
238
|
+
unshielded still scan from genesis, which is quick. The hour becomes the
|
|
239
|
+
reference's deserialize plus catch-up.
|
|
240
|
+
|
|
241
|
+
Fails closed: an owned entry, an indexer without the field (pre-4.2), a timeout
|
|
242
|
+
or an error all keep the genesis walk, with the reason in the sync progress
|
|
243
|
+
line. The decision is the pure `preSeedPlan` (`sync/preseed-parts.ts`); the
|
|
244
|
+
probe is `sync/dust-history.ts`; `IndexerClient.getDustGenerationEndIndex` and
|
|
245
|
+
`dustAddressForKey` (a dust address from the typed key, no seed needed) support
|
|
246
|
+
it. ADR 0003 records the exception.
|
|
247
|
+
|
|
248
|
+
## 0.13.0
|
|
249
|
+
|
|
250
|
+
### Minor Changes
|
|
251
|
+
|
|
252
|
+
- a17b719: Show what a dApp transaction takes from the wallet before the user approves it.
|
|
253
|
+
|
|
254
|
+
When a connected dApp calls `balanceSealedTransaction` or
|
|
255
|
+
`balanceUnsealedTransaction`, the wallet is asked to cover whatever the dApp's
|
|
256
|
+
transaction is short of — and the approval screen said only "Network fee: paid
|
|
257
|
+
in DUST". The user was approving a spend without being told the amount or the
|
|
258
|
+
token. The screen now lists, per token, what the wallet has to supply ("You
|
|
259
|
+
pay") and any surplus it collects back ("You get back"), plus the number of
|
|
260
|
+
contract calls when there are any. If the transaction cannot be decoded, it
|
|
261
|
+
says so in a visible warning rather than showing nothing.
|
|
262
|
+
|
|
263
|
+
The amounts come from the transaction itself, before anything is balanced,
|
|
264
|
+
booked or spent: core gains `summarizeTransaction` /
|
|
265
|
+
`summarizeConnectorTransaction` (`sync/tx-summary.ts`), which sums the ledger's
|
|
266
|
+
`Transaction.imbalances(segment)` over the guaranteed section and every intent.
|
|
267
|
+
A negative imbalance is what the wallet must put in; a positive one is change.
|
|
268
|
+
The sign convention is pinned by a test against the real ledger. Fees are not
|
|
269
|
+
included — they are only known once the wallet has balanced and proven its own
|
|
270
|
+
segment, and are always paid in DUST.
|
|
271
|
+
- b49c96d: Give the CLI and TUI the pre-seed, timings and DUST-registration behaviour the
|
|
272
|
+
extension already had.
|
|
273
|
+
|
|
274
|
+
**Birthdays.** `chainTip` moves from the extension's background handlers into
|
|
275
|
+
core, and `wallet generate` on both surfaces records the chain tip as the new
|
|
276
|
+
wallet's birthday. Without one the `reference.height <= birthday` guard can never
|
|
277
|
+
pass, so no CLI or TUI wallet could ever be pre-seeded — the difference between
|
|
278
|
+
29.3s and 78.6 min on preprod. Imports still get none, deliberately: a restored
|
|
279
|
+
wallet may hold funds at any height, and seeding it past its own history would
|
|
280
|
+
lose them silently.
|
|
281
|
+
|
|
282
|
+
**`moth preseed status|refresh|build`.** Thin wrappers over core functions
|
|
283
|
+
that already existed but had no caller outside the extension. `refresh` is the
|
|
284
|
+
one worth having — 9.1s to catch a reference up, against 53.6 min to rebuild it
|
|
285
|
+
from genesis, which is what someone does by hand when the command is missing.
|
|
286
|
+
`build` says how long it will take before starting, because an unattended command
|
|
287
|
+
that appears to hang for an hour is indistinguishable from a broken one.
|
|
288
|
+
|
|
289
|
+
**Phase timings on disk.** `createFileTimingStore` backs the existing
|
|
290
|
+
storage-agnostic recorder with `~/.moth/timings.json` — the path
|
|
291
|
+
`docs/BENCHMARKING.md` already documented and nothing wrote. `moth diagnostics
|
|
292
|
+
timings` shows the timeline as deltas, and recording stays off until switched on.
|
|
293
|
+
A headless sync is where this matters most: it is the surface with no other
|
|
294
|
+
signal about where the time went.
|
|
295
|
+
|
|
296
|
+
**DUST registration in the TUI.** `DustRegistrationNotYetError` is now caught
|
|
297
|
+
distinctly, so a wallet whose NIGHT is too new to cover the registration fee is
|
|
298
|
+
told "not yet" instead of shown the raw SDK error as a failure. The panel and the
|
|
299
|
+
CLI already did this; the TUI was the surface still reporting it as a defect.
|
|
300
|
+
- 0508a38: Four CLI fixes found by a manual test pass, none of which the test suite could see.
|
|
301
|
+
|
|
302
|
+
**Amounts are parsed strictly (#63).** `moth transfer` used `parseFloat` and
|
|
303
|
+
`Math.round`, and `parseFloat` keeps whatever prefix it understands: `1,5` was
|
|
304
|
+
accepted as **1 NIGHT**, losing a third of the value with no warning; `0.0000001`
|
|
305
|
+
rounded to **zero base units** and was submitted as a transfer that moved nothing
|
|
306
|
+
and still paid a fee; `1e3` became 1000 NIGHT; `1abc` became 1. A shared
|
|
307
|
+
`parseNightAmount` in core now refuses all of them, in BigInt, with a message
|
|
308
|
+
naming what is wrong — and `daemon transfer` already did it this way, so the two
|
|
309
|
+
paths finally agree.
|
|
310
|
+
|
|
311
|
+
**`moth transfer` can select a token (#62).** It hardcoded `NIGHT_TOKEN_ID`, so a
|
|
312
|
+
wallet holding anything else could spend it only through `daemon transfer
|
|
313
|
+
--token-id`. Same flag name and default here. The positional amount stays a NIGHT
|
|
314
|
+
decimal and is refused for other tokens, directing to `--amount` in raw base units
|
|
315
|
+
— mis-scaling a token by NIGHT's 10⁶ would be worse than refusing.
|
|
316
|
+
|
|
317
|
+
**`moth wallet export-phrase` exists (#59).** `WalletManager.exportPhrase` and
|
|
318
|
+
`exportSeedHex` have always been in core, and the extension exposes them, but no
|
|
319
|
+
CLI command did. So the CLI had no backup path — a phrase was shown once at
|
|
320
|
+
`wallet generate` and never again — no way to move a wallet between machines, and
|
|
321
|
+
no way to recover a seed for a keystore you hold the passphrase to. Confirmed by
|
|
322
|
+
default and refused non-interactively without `--yes`, following `wallet remove`.
|
|
323
|
+
A wallet imported from a hex seed says so rather than presenting a seed as a
|
|
324
|
+
phrase.
|
|
325
|
+
|
|
326
|
+
**`wallet address` takes `--wallet` (#60).** It was the only command in the CLI
|
|
327
|
+
requiring `--name`, which made it the only one that could not act on the active
|
|
328
|
+
wallet: `wallet use w1` then `wallet address` failed with "Missing required flag
|
|
329
|
+
name". `--name` remains as an alias.
|
|
330
|
+
|
|
331
|
+
**`dust register` distinguishes empty from done (#58).** `designateForDust`
|
|
332
|
+
returns `null` both when every UTXO is already designated and when there are no
|
|
333
|
+
NIGHT UTXOs at all, and the command reported the first for both — vacuously true
|
|
334
|
+
of an empty wallet, and read as success. An unfunded wallet now gets "No NIGHT to
|
|
335
|
+
designate" and a non-zero exit.
|
|
336
|
+
- 426b757: Detect and steer users away from the devnet dust-ledger wedge (docs/bugs-found #15-style defect).
|
|
337
|
+
|
|
338
|
+
Some devnets (midnight-node 2.0.0-rc.4 / midnight-ledger 9.1.0.0-rc.3) can
|
|
339
|
+
enter a state where a DUST registration leaves the node's dust ledger unable
|
|
340
|
+
to reconcile with any wallet's, permanently — every subsequent dust spend,
|
|
341
|
+
from every wallet, fails with the same `InvalidDustSpendProof` /
|
|
342
|
+
`Custom error: 170` signature that a normal transient race also produces.
|
|
343
|
+
Only a chain reset clears it; retrying does not. This is not a Moth defect —
|
|
344
|
+
see `docs/upstream-issues/dust-ledger-wedge-invalid-dust-spend-proof.md` for
|
|
345
|
+
the evidence and the draft issue against `midnightntwrk/midnight-node` /
|
|
346
|
+
`midnight-ledger` — but Moth users hit it on shared and local devnets, so
|
|
347
|
+
Moth now detects and steers around it rather than retrying forever.
|
|
348
|
+
|
|
349
|
+
**Detection** (`@shieldedtech/moth-wallet`, `sync/dust-ledger-health.ts`): a
|
|
350
|
+
run of consecutive, independently built submissions rejected with the same
|
|
351
|
+
ambiguous signature — with the chain confirmed still producing new blocks
|
|
352
|
+
meanwhile — is
|
|
353
|
+
surfaced as `DustLedgerWedgedError` instead of a generic failure. Wired into
|
|
354
|
+
every fee-paying submission path: the extension's offscreen host, `moth
|
|
355
|
+
transfer` / `moth dust register`, and the daemon's `transferTokens` /
|
|
356
|
+
`dustRegister` / `dustDeregister` RPCs (used by both the headless CLI daemon
|
|
357
|
+
and the TUI).
|
|
358
|
+
|
|
359
|
+
**Registration UX** (`@shieldedtech/moth-extension`): the "Register for
|
|
360
|
+
DUST" flow now warns before registering a NIGHT coin that has sat
|
|
361
|
+
unregistered for more than a minute — the only pattern with zero known
|
|
362
|
+
failures across four documented occurrences is registering within seconds of
|
|
363
|
+
funding — and a wallet that has never registered is nudged to do so as soon
|
|
364
|
+
as new NIGHT is observed, rather than waiting for the user to find the Dust
|
|
365
|
+
screen on their own.
|
|
366
|
+
|
|
367
|
+
**Repro harness**: `packages/cli/tests/integration/daemon/dust-wedge-repro.test.ts`
|
|
368
|
+
isolates the fund-to-register delay as the one variable prior occurrences
|
|
369
|
+
didn't control for, holding UTXO size fixed at the size every known success
|
|
370
|
+
used (10,000,000 NIGHT).
|
|
371
|
+
|
|
372
|
+
No change to transaction construction, signing, or proving — every
|
|
373
|
+
registration involved in the underlying defect was accepted by the ledger,
|
|
374
|
+
so this only classifies what a rejection means after the fact.
|
|
375
|
+
- eb4d56a: **The extension can restore an account from a raw hex seed.** Previously it
|
|
376
|
+
accepted a 24-word phrase and nothing else, which made accounts created from a
|
|
377
|
+
seed permanently unreachable there.
|
|
378
|
+
|
|
379
|
+
That was not a policy — a wallet created from a hex seed **has no mnemonic and
|
|
380
|
+
can never be given one**, because BIP-39's phrase-to-seed step is a one-way KDF.
|
|
381
|
+
There was nothing to type into the word grid. Meanwhile the TUI
|
|
382
|
+
(`tui/src/app.tsx`) and the CLI (`moth wallet import --seed-hex`) both had the
|
|
383
|
+
path, and the extension's own session model is seed-based end to end: it derives
|
|
384
|
+
`seedHex` at unlock and carries it through every operation. A mnemonic was only
|
|
385
|
+
ever a transport format for the seed, and the extension accepted just that one
|
|
386
|
+
format at the door.
|
|
387
|
+
|
|
388
|
+
The plumbing was the only thing missing. `wallets` in the browser facade *is* a
|
|
389
|
+
`WalletManager`, so `importFromSeed` was already callable from the offscreen
|
|
390
|
+
document; `unlock` already handled a `seed:` keystore. `ImportWalletRequest` is
|
|
391
|
+
now a union — exactly one of `mnemonic` / `seed`, enforced by the type rather
|
|
392
|
+
than a runtime check — threaded through the background/offscreen RPC, and the
|
|
393
|
+
offscreen host routes to whichever core call fits. The two stay separate calls,
|
|
394
|
+
not one with a branch: `import` runs the BIP-39 checksum, `importFromSeed`
|
|
395
|
+
shape-checks the hex.
|
|
396
|
+
|
|
397
|
+
The restore screen now offers both artifacts as tabs on one page. No
|
|
398
|
+
"which do you have?" step first: anyone restoring already knows which they hold,
|
|
399
|
+
so that step would cost a click and gather nothing.
|
|
400
|
+
|
|
401
|
+
**New in core: `wallet/hex-seed.ts`, and `importFromSeed` now validates.** It had
|
|
402
|
+
no validation whatsoever — a malformed seed reached the SDK and surfaced as a
|
|
403
|
+
bare `Invalid seed`, and a merely wrong-*length* seed was accepted outright,
|
|
404
|
+
silently producing a different wallet. `checkHexSeed` returns a machine-readable
|
|
405
|
+
problem so each surface can word it itself (the extension localises it; the CLI
|
|
406
|
+
and TUI use `describeHexSeedProblem`). The TUI and CLI inherit the fix.
|
|
407
|
+
|
|
408
|
+
**The validation is shaped around the fact that a hex seed has no checksum.**
|
|
409
|
+
Measured against the wallet SDK, `HDWallet.fromSeed` accepts any 16–64 byte
|
|
410
|
+
seed and refuses 15 or 65 — and every accepted length derives a *different*
|
|
411
|
+
wallet. So:
|
|
412
|
+
|
|
413
|
+
- change one character and there is **no error**, just a different, valid, empty
|
|
414
|
+
wallet;
|
|
415
|
+
- truncate a paste and the same is true;
|
|
416
|
+
- whereas one wrong word in a phrase fails `validateMnemonic`.
|
|
417
|
+
|
|
418
|
+
The bounds therefore match what the SDK actually accepts rather than a rule of
|
|
419
|
+
our own, with a test that fails if an SDK bump moves them. Lengths other than 32
|
|
420
|
+
or 64 bytes — the two sizes real tooling emits — are **warned about, not
|
|
421
|
+
refused**: a truncated paste looks exactly like a seed genuinely created at that
|
|
422
|
+
length, and refusing would lock such a wallet out. And the field is deliberately
|
|
423
|
+
**not** a password input: reading the seed back against a backup is the only
|
|
424
|
+
check available, so masking it would remove the sole defence.
|
|
425
|
+
|
|
426
|
+
Note those two sizes are not interchangeable. A 32-byte seed is what the Midnight
|
|
427
|
+
node toolkit and `moth wallet import --seed-hex` deal in; the 64-byte one is the
|
|
428
|
+
BIP-39 seed a phrase expands to, and what `exportSeedHex` returns for a
|
|
429
|
+
phrase-backed wallet. Truncating the latter to the former gives a different
|
|
430
|
+
wallet.
|
|
431
|
+
|
|
432
|
+
Restored accounts keep `createdHere: false` and no birthday, so they scan from
|
|
433
|
+
genesis — they may hold funds at any height, and seeding one past its own history
|
|
434
|
+
would hide them (ADR 0003, rule 4).
|
|
435
|
+
|
|
436
|
+
**The TUI's hex import was that validation.** `SeedEntryScreen` matched
|
|
437
|
+
`/^[0-9a-fA-F]{64}$/` and did it *before* `importFromSeed`, so core's new check
|
|
438
|
+
never ran and the screen refused every length the SDK accepts bar one — including
|
|
439
|
+
the 128-character seed this release teaches the extension to reveal. Round-trip
|
|
440
|
+
a phrase-backed account (extension → Accounts → Reveal → Hex seed) into the
|
|
441
|
+
TUI's hex import and it answered "Invalid hex seed. Must be exactly 64 hex
|
|
442
|
+
characters", while the CLI took the same string. The check now lives in
|
|
443
|
+
`tui/src/screens/onboarding/seed-input.ts` and delegates to `checkHexSeed`, so
|
|
444
|
+
the TUI inherits the bounds and the unusual-length warning instead of
|
|
445
|
+
re-deciding them; a test sweeps 16..64 bytes so the screen cannot silently drift
|
|
446
|
+
tighter than core again.
|
|
447
|
+
|
|
448
|
+
Also corrected: three places documented a BIP-39 seed as 64 hex characters. It is
|
|
449
|
+
128 — 64 hex characters is a 32-byte seed, a different artifact that derives a
|
|
450
|
+
different wallet. `core/src/sync/operations.ts` and two spots in
|
|
451
|
+
`docs/spec/wallet-service/05-key-management.md`. That claim is precisely what
|
|
452
|
+
would lead someone to write `length === 64` validation and reject the seeds this
|
|
453
|
+
app exports (#99).
|
|
454
|
+
|
|
455
|
+
Closes #98.
|
|
456
|
+
- 3e131e2: Detect an indexer renumbering instead of silently syncing from the wrong place.
|
|
457
|
+
|
|
458
|
+
Sync cursors are indexer-assigned event sequence numbers, and nothing ties an id
|
|
459
|
+
to a block — `DustLedgerEvent` carries only `id`, `raw`, `maxId` and
|
|
460
|
+
`protocolVersion`. So when the same URL starts serving a differently-numbered
|
|
461
|
+
stream, a stored cursor names a different event and the sync resumes at the wrong
|
|
462
|
+
point without erroring. The only guard was a string comparison of `indexerUrl`,
|
|
463
|
+
which by definition cannot see a backend swap behind an unchanged name — and it
|
|
464
|
+
lived in the extension's background, so the CLI and TUI had no check at all.
|
|
465
|
+
|
|
466
|
+
This has already happened on preprod. The default indexer had a 22-wide hole in
|
|
467
|
+
its dust id space; the host now serving that name numbers contiguously, so cursors
|
|
468
|
+
written before the change sit 22 events too high. The pre-seed reference committed
|
|
469
|
+
for the extension stores dust cursor `1431375`, which under the current numbering
|
|
470
|
+
is 22 events beyond the state the snapshot holds — verified against the live
|
|
471
|
+
indexer, where that id now yields digest `3f3576deb45ad350` while the event the
|
|
472
|
+
reference actually stopped at yields `11c8cf9fd5a736f2`.
|
|
473
|
+
|
|
474
|
+
A cursor is now stored with a **witness**: the hash of the event found at that id.
|
|
475
|
+
On resume the id is re-read and compared. Same event means the numbering is
|
|
476
|
+
unchanged; a different event means it moved and the cursor is refused, failing
|
|
477
|
+
closed to a genesis sync — the direction ADR-0003 already establishes as always
|
|
478
|
+
safe.
|
|
479
|
+
|
|
480
|
+
A witness rather than one global indexer fingerprint, because a fingerprint has to
|
|
481
|
+
be sampled at a fixed id and any id below the point where two numberings diverge
|
|
482
|
+
returns the *same* event from both. Sampled at preprod's hole (989781), old and
|
|
483
|
+
new both return the event new calls 989781 — old's first existing id at or above
|
|
484
|
+
that probe was 989803, the same event — so a fingerprint there would have matched
|
|
485
|
+
across the exact cutover it existed to detect. The divergence point is not
|
|
486
|
+
knowable in advance; a witness has no such blind spot, because it asks only about
|
|
487
|
+
the id the cache actually depends on.
|
|
488
|
+
|
|
489
|
+
Three paths are gated: the warm read verifies before handing a reference to any
|
|
490
|
+
wallet, a build records witnesses for the cursors it stops at, and
|
|
491
|
+
`refreshEmptyRefCache` refuses to resume across a mismatch — resuming would carry
|
|
492
|
+
the old numbering forward into a reference that then looks freshly built, which
|
|
493
|
+
destroys the evidence.
|
|
494
|
+
|
|
495
|
+
Scope. Only shielded and dust are witnessed: they ride the global ledger-event
|
|
496
|
+
numbering and are the two the preprod change moved, where unshielded is keyed by
|
|
497
|
+
address. Per-wallet caches are not yet gated — normal sync persistence is written
|
|
498
|
+
by the SDK's own serialization rather than through `saveCachedState`, so covering
|
|
499
|
+
those needs a separate seam. A reference with no witness is treated as
|
|
500
|
+
unverifiable rather than invalid, so upgrading does not force a chain walk on
|
|
501
|
+
everyone at once; new references carry witnesses and the population converges.
|
|
502
|
+
- e31eaf8: Record a witness per cursor in exported references, and refuse a bundle without one.
|
|
503
|
+
|
|
504
|
+
A published reference records cursors that are indexer-assigned event sequence
|
|
505
|
+
numbers, so its correctness depends on an indexer that the bundle says nothing
|
|
506
|
+
about. That is how the preprod bundle stayed in use after the numbering underneath
|
|
507
|
+
it moved: the bytes were intact, the checksums matched, and the cursors had
|
|
508
|
+
quietly stopped naming the events they were written for.
|
|
509
|
+
|
|
510
|
+
`export-preseed.mjs` now reads the event at each cursor and records its hash in
|
|
511
|
+
the manifest under `witnesses`, alongside `height` and the per-part sizes. It
|
|
512
|
+
refuses to export at all if a cursor cannot be witnessed — including the case
|
|
513
|
+
where the indexer returns no event at or after the cursor, which means the
|
|
514
|
+
reference is *ahead* of the indexer it is being exported against and is itself the
|
|
515
|
+
renumbering signal.
|
|
516
|
+
|
|
517
|
+
The extension's installer requires them. A manifest without a witness for shielded
|
|
518
|
+
and dust is rejected, and the witnesses are written to the store before the height
|
|
519
|
+
— the height is what marks a reference usable, so a reference that reads as usable
|
|
520
|
+
without its witnesses is one that skips verification.
|
|
521
|
+
|
|
522
|
+
The asymmetry with local references is deliberate. A witnessless reference already
|
|
523
|
+
on disk is treated as unverifiable rather than invalid, because the alternative
|
|
524
|
+
forces every existing user into a chain walk on upgrade. A witnessless *bundle* is
|
|
525
|
+
an artefact we control and can re-cut, so refusing it costs one slower first sync
|
|
526
|
+
and trusting it costs a wallet silently resuming at the wrong event. The bundle
|
|
527
|
+
currently in the repository has no witnesses and will therefore no longer install;
|
|
528
|
+
a reference rebuilt from genesis against the current indexer replaces it.
|
|
529
|
+
- b49c96d: Move a pre-seed reference between machines: `moth preseed export` / `import`.
|
|
530
|
+
|
|
531
|
+
Building a reference IS the chain walk — tens of minutes, once per network per
|
|
532
|
+
machine. That cost is identical for everyone, because a reference holds public
|
|
533
|
+
chain state and nothing else, so it is work that should be done once and shared
|
|
534
|
+
rather than repeated by every developer who clones the repo. ADR 0005 called for
|
|
535
|
+
these two actions; the rest of the command shipped without them.
|
|
536
|
+
|
|
537
|
+
The on-disk shape is the one `scripts/export-preseed.mjs` already writes and CI
|
|
538
|
+
already publishes: gzipped state per sub-wallet plus a manifest. One format, so a
|
|
539
|
+
reference exported here can be dropped into the extension package, and one
|
|
540
|
+
downloaded from a release can be imported here.
|
|
541
|
+
|
|
542
|
+
`export` never writes the reference wallet's mnemonic. That is the only secret in
|
|
543
|
+
the arrangement — the state blobs are public chain data, but the mnemonic
|
|
544
|
+
controls the wallet they were built from, and a published reference is meant to
|
|
545
|
+
be safe to hand to strangers. The command says so in its own output.
|
|
546
|
+
|
|
547
|
+
`import` refuses rather than guesses. A bundle for another network would seed
|
|
548
|
+
wallets from a chain they have never been on, and the mismatch is silent
|
|
549
|
+
afterwards. A bundle older than what is already present is a downgrade that costs
|
|
550
|
+
catch-up time on every wallet created from then on; `--force` allows it, for
|
|
551
|
+
replacing a corrupt newer reference with a known-good older one.
|
|
552
|
+
|
|
553
|
+
Every part is decompressed before any part is written. Unpacking as it went left
|
|
554
|
+
the store holding new shielded and unshielded state beside an old dust state when
|
|
555
|
+
a later part turned out to be corrupt — a mixture that never existed on chain,
|
|
556
|
+
with a height key that still looked consistent. The height is written last, since
|
|
557
|
+
it is what marks a reference usable.
|
|
558
|
+
- c1c462e: **Reveal no longer offers a recovery phrase for accounts that do not have one.**
|
|
559
|
+
|
|
560
|
+
The first version accepted either choice and then answered with the seed for a
|
|
561
|
+
seed-restored account, because that account genuinely has no phrase — BIP-39's
|
|
562
|
+
phrase-to-seed step is one-way, so one cannot be worked back out. Correct
|
|
563
|
+
values, but it read as the selection being ignored: pick "recovery phrase", get
|
|
564
|
+
a hex seed, with the explanation arriving only after the password was entered.
|
|
565
|
+
|
|
566
|
+
The phrase option is now disabled for those accounts, with the reason shown
|
|
567
|
+
next to it, and the dialog opens on the seed instead.
|
|
568
|
+
|
|
569
|
+
To grey it out *before* asking for a password, the UI has to know which artifact
|
|
570
|
+
an account holds. `WalletInfo.backupKind` (`'mnemonic' | 'seed'`) records it:
|
|
571
|
+
`generate` and `import` write `'mnemonic'`, `importFromSeed` writes `'seed'`.
|
|
572
|
+
|
|
573
|
+
`undefined` on accounts written before the field, and deliberately not defaulted
|
|
574
|
+
— guessing `'mnemonic'` would tell a seed-imported account it has a phrase,
|
|
575
|
+
which is the bug this exists to prevent. Unknown leaves the option open and lets
|
|
576
|
+
the revealed value explain itself as before. **`unlock` backfills the field**,
|
|
577
|
+
since the keystore payload (`seed:<hex>` versus a mnemonic) is what distinguishes
|
|
578
|
+
them and the password is the only thing that reveals it — so an account someone
|
|
579
|
+
actually opens stops being unknown after one unlock. The write happens only when
|
|
580
|
+
the stored value is missing or wrong, alongside the address backfill already
|
|
581
|
+
there, so a normal unlock does not touch storage.
|
|
582
|
+
- 04f1aa4: **`undeployed` replaces `local` as the local devnet network.** Every interface now
|
|
583
|
+
offers it, including the extension, which could not select it before.
|
|
584
|
+
|
|
585
|
+
The two were duplicate presets for the same thing. `undeployed` is the id the
|
|
586
|
+
Midnight tooling, `docs/TESTING.md`, and this repo's own README instructions all
|
|
587
|
+
use for the local stack, and it points at the node port that stack listens on —
|
|
588
|
+
`9944`. `local` pointed at `9933`, which nothing in the documented stack serves,
|
|
589
|
+
so selecting **Local** in the extension connected to a closed port. It has been
|
|
590
|
+
that way since the first commit.
|
|
591
|
+
|
|
592
|
+
`local` was kept out of the extension's picker by a comment claiming the wallet
|
|
593
|
+
could not derive addresses for `undeployed`. That was false: `mn_addr_undeployed1…`,
|
|
594
|
+
`mn_dust_undeployed1…` and `mn_shield-addr_undeployed1…` have always derived, and
|
|
595
|
+
`address-parity`'s network loop stopping at `qanet` is why nothing contradicted it.
|
|
596
|
+
The loop now covers `undeployed` and `stagenet`, and a new test holds
|
|
597
|
+
`SUPPORTED_NETWORKS` equal to the keys of `DEFAULT_NETWORKS`, so a preset no
|
|
598
|
+
interface can reach — or an offered network with no preset — fails the suite.
|
|
599
|
+
|
|
600
|
+
**Breaking, with a migration.** `local` is gone from `SUPPORTED_NETWORKS` and
|
|
601
|
+
`DEFAULT_NETWORKS`, and the extension rejects it on save. Read paths resolve it via
|
|
602
|
+
`canonicalNetworkId`, exported from core: the extension's stored selection, a
|
|
603
|
+
wallet's meta record, the TUI's `lastNetwork`, `--network local`, and
|
|
604
|
+
`createMothBrowser({ network: 'local' })` all continue to work and land on
|
|
605
|
+
`undeployed`. Per-network birthdays and endpoint overrides move across with it, so
|
|
606
|
+
a migrated wallet keeps its pre-seed shortcut instead of resyncing from genesis.
|
|
607
|
+
Stored records are rewritten lazily, only when something else is already saving
|
|
608
|
+
them. `local` stays in `ALL_NETWORKS` so addresses already handed out still resolve.
|
|
609
|
+
|
|
610
|
+
Sync caches are keyed by network, so a migrated account resyncs once under the new
|
|
611
|
+
key — correct, since the node URL genuinely changes — and its old entries are left
|
|
612
|
+
behind rather than cleaned up.
|
|
613
|
+
|
|
614
|
+
Also fixed alongside: the four localhost indexer fallbacks pointed at
|
|
615
|
+
`http://localhost:8088` without the `/api/v4/graphql` path the indexer client posts
|
|
616
|
+
queries to, and the README's network table listed `devnet` as localhost, omitted
|
|
617
|
+
`undeployed`, and gave qanet hostnames (`rpc.qanet.dev.midnight.network`) that do
|
|
618
|
+
not resolve. Mainnet stays out of both the table and the `--network` reference:
|
|
619
|
+
the CLI refuses it and the extension keeps it out of the picker, so documenting
|
|
620
|
+
its endpoints only invites someone to try.
|
|
621
|
+
|
|
622
|
+
### Patch Changes
|
|
623
|
+
|
|
624
|
+
- b49c96d: Bound the sync-engine teardown, and stop a wedged one from pinning everything
|
|
625
|
+
behind it.
|
|
626
|
+
|
|
627
|
+
`facade.stop()` closes the wallet SDK's submission service, which first awaits
|
|
628
|
+
the Polkadot client the facade was built with. That client is created with
|
|
629
|
+
`ApiPromise.create({throwOnConnect: false})`, so against a node that never
|
|
630
|
+
answers it neither resolves nor rejects — WsProvider simply keeps retrying. The
|
|
631
|
+
stop therefore had no failure path, and every caller inherited the hang. The
|
|
632
|
+
trigger is a node URL that does not answer, which is exactly the state a user is
|
|
633
|
+
in while editing one, Local network being the common case.
|
|
634
|
+
|
|
635
|
+
Three consequences, all fixed here:
|
|
636
|
+
|
|
637
|
+
- **Settings → Network's Save button spun for ever and discarded the edit.**
|
|
638
|
+
`saveNetworkConfig` awaited the stop before persisting, so the save neither
|
|
639
|
+
completed nor failed and the next attempt started from the same broken
|
|
640
|
+
endpoint. Settings are now written before the engine is touched and rolled back
|
|
641
|
+
if the switch fails, and a stop the offscreen document never answers closes that
|
|
642
|
+
document — Chrome destroys it without its cooperation, which is the only
|
|
643
|
+
teardown that reaches a wedged one.
|
|
644
|
+
- **The idle teardown never closed the offscreen document,** so an idle wallet
|
|
645
|
+
kept its worker and WASM heap alive instead of letting the service worker
|
|
646
|
+
suspend.
|
|
647
|
+
- **Locking never freed the worker holding key material,** because `lockNow`'s
|
|
648
|
+
forced teardown waited on the same stop.
|
|
649
|
+
|
|
650
|
+
`facade.stop()` is now raced against a 5s bound. The offscreen `syncStop` no
|
|
651
|
+
longer waits unboundedly on a start that may never finish, and still stops that
|
|
652
|
+
engine whenever it does come up, so an abandoned start cannot keep syncing behind
|
|
653
|
+
a new one.
|
|
654
|
+
- f736ebd: Settings → Network gains "Clear cache and resync".
|
|
655
|
+
|
|
656
|
+
A local network that goes down and comes back as a new chain from genesis
|
|
657
|
+
leaves the wallet holding state for a chain that no longer exists: the
|
|
658
|
+
account's serialized sync state, its pending submissions, and the network's
|
|
659
|
+
pre-seed reference that every fresh sync is seeded from. The engine cannot
|
|
660
|
+
tell, and the only ways out were switching indexer URLs or removing the
|
|
661
|
+
account. The new action, behind a confirmation, stops sync, drops all of that
|
|
662
|
+
for the active account on its network, clears the cached balance snapshot so
|
|
663
|
+
the loading screen shows, and starts syncing again from the start of the
|
|
664
|
+
chain. Nothing is spent.
|
|
665
|
+
|
|
666
|
+
Core gains `clearEmptyRefCache(networkId, store)` (`sync/preseed.ts`), which
|
|
667
|
+
removes the reference's state parts, height, cursor witnesses and mnemonic and
|
|
668
|
+
forgets the in-process memo — without the last, a worker that had already
|
|
669
|
+
verified the reference would keep handing the stale one out.
|
|
670
|
+
- 2dabc50: Make `moth config` usable, and add smoke coverage for the class of bug it was.
|
|
671
|
+
|
|
672
|
+
`config` declared an optional positional argument (`action`) ahead of a required
|
|
673
|
+
one (`key`). @oclif/core rejects that outright — with `action` absent, a single
|
|
674
|
+
value is ambiguous between an action and a key — so every invocation failed at
|
|
675
|
+
spec validation and the command body never ran. `action` is now required, which
|
|
676
|
+
changes no working behaviour because nothing worked.
|
|
677
|
+
|
|
678
|
+
Nothing caught this because no test invoked the command. Two probes now cover the
|
|
679
|
+
class:
|
|
680
|
+
|
|
681
|
+
- **Positional order, checked statically from source.** This is the one that
|
|
682
|
+
bites: reverting the fix produces `config: required "key" follows optional
|
|
683
|
+
"action"`.
|
|
684
|
+
- **A `--help` sweep over all 35 commands**, which catches a broken flag
|
|
685
|
+
definition, a bad example, or an import that throws on load.
|
|
686
|
+
|
|
687
|
+
Worth recording why it takes two. `--help` does not validate positional-argument
|
|
688
|
+
order: with the bad spec in place, `moth config --help` prints help perfectly
|
|
689
|
+
happily while bare `moth config` reports "Invalid argument spec". So the help
|
|
690
|
+
sweep would not have caught the bug that prompted it, and the order rule has to be
|
|
691
|
+
checked separately. Invoking every command bare would catch it, but would also
|
|
692
|
+
run them.
|
|
693
|
+
- 9afd580: Refuse mainnet at the `--network` flag, not in one of its consumers.
|
|
694
|
+
|
|
695
|
+
The refusal lived inside `BaseCommand.getNetworkConfig`, and twelve commands never
|
|
696
|
+
call it — including both that create wallets. `moth wallet generate --network
|
|
697
|
+
mainnet` derived mainnet addresses, wrote a keystore, printed a recovery phrase
|
|
698
|
+
and exited 0, with no warning shown. A guard in one consumer is not a guard; it is
|
|
699
|
+
a convention that holds wherever someone remembered it.
|
|
700
|
+
|
|
701
|
+
It now hangs off the `--network` flag that every command inherits through
|
|
702
|
+
`baseFlags`, so no command can take a network id without it. `getNetworkConfig`
|
|
703
|
+
keeps the check as defence in depth, for an id arriving from stored config or from
|
|
704
|
+
a caller assembling flags itself, and both now route through one
|
|
705
|
+
`assertNotMainnet`.
|
|
706
|
+
|
|
707
|
+
Verified across the paths the issue did not cover: `wallet generate`, `wallet
|
|
708
|
+
import`, `wallet use` and `tui` all now print the warning and exit 1 without
|
|
709
|
+
writing a keystore, while `--network preprod` is untouched.
|
|
710
|
+
|
|
711
|
+
Also guards `config set default-network mainnet`, which is the second way a
|
|
712
|
+
network id enters the CLI — `WalletManager` falls back to `config.defaultNetwork`
|
|
713
|
+
for a wallet with no network of its own, so a stored value reaches the same code
|
|
714
|
+
paths without `--network` ever being used. Note that path is currently unreachable
|
|
715
|
+
for an unrelated reason: `moth config` declares an optional argument before a
|
|
716
|
+
required one, which oclif rejects, so every invocation of that command fails
|
|
717
|
+
before it runs. Filed separately.
|
|
718
|
+
- b49c96d: Read the birthday back, so a CLI or TUI wallet can actually pre-seed.
|
|
719
|
+
|
|
720
|
+
The birthday was written and never read. `startWalletSync`'s pre-seed gate is
|
|
721
|
+
`(isNewWallet || birthday)`, and no CLI command passed either — eleven of them
|
|
722
|
+
stopped at `walletName`, and the TUI hook passed `isNewWallet` but no birthday,
|
|
723
|
+
so the guard `emptyRef.height <= birthday` could never be reached. The effect was
|
|
724
|
+
silent: `moth balance -n preprod -v` showed no pre-seed line at all and dust began
|
|
725
|
+
at 0%, with the reference sitting unused.
|
|
726
|
+
|
|
727
|
+
Every sync call site now passes it, resolved through a new
|
|
728
|
+
`WalletManager.birthdayOn(name, networkId)`. Per network on purpose: `list()`
|
|
729
|
+
resolves against the wallet's own `meta.network`, so a sync driven by `--network`
|
|
730
|
+
was reading a height belonging to a different chain, or nothing at all. It never
|
|
731
|
+
throws — a wallet with no meta asserts nothing, and "no claim" means scan from
|
|
732
|
+
genesis, which is slow but never wrong.
|
|
733
|
+
|
|
734
|
+
Guarded by a test that walks the AST of every `startWalletSync` call in the CLI
|
|
735
|
+
and TUI and fails any that omits the birthday, since nothing else would notice
|
|
736
|
+
this regressing. Verified by deliberately dropping the argument.
|
|
737
|
+
- c2f8b73: Re-cut the pre-seed bundles from genesis, and add one for qanet.
|
|
738
|
+
|
|
739
|
+
The preprod bundle recorded dust cursor `1431375`, written under the indexer's old
|
|
740
|
+
numbering. Under the numbering now served, that id names an event 22 positions
|
|
741
|
+
later than the state the snapshot holds, so every wallet seeded from it resumed
|
|
742
|
+
past 22 dust events — no error, just missing generation history (#40).
|
|
743
|
+
|
|
744
|
+
All three references were rebuilt from genesis rather than refreshed, because a
|
|
745
|
+
refresh resumes from the stored cursor and would have carried the old numbering
|
|
746
|
+
forward into a bundle that then looked freshly built:
|
|
747
|
+
|
|
748
|
+
| Network | Height | Build | Dust cursor |
|
|
749
|
+
| --- | --- | --- | --- |
|
|
750
|
+
| preprod | 2,203,416 | 55 min | 1,449,958 (was 1,431,375) |
|
|
751
|
+
| preview | 519,470 | 3 min | 141,062 |
|
|
752
|
+
| qanet | 2,314,786 | 14 min | 346,693 |
|
|
753
|
+
|
|
754
|
+
Each manifest now carries a witness per cursor, so a consumer can tell whether the
|
|
755
|
+
numbering it was written under still holds — these are the first bundles that can
|
|
756
|
+
be verified rather than trusted, and the first that the installer will accept.
|
|
757
|
+
|
|
758
|
+
qanet ships for the first time. It costs 140 KB, not the several megabytes preprod
|
|
759
|
+
does: its chain is longer but has far fewer dust events, and dust is what makes a
|
|
760
|
+
reference large. The control that offers on-device warming probes which networks
|
|
761
|
+
ship a reference rather than listing them, so no code changed to add it.
|
|
762
|
+
- b49c96d: Move the pre-seed commands from `moth dust preseed` to `moth preseed`.
|
|
763
|
+
|
|
764
|
+
DUST is why the pre-seed matters — the 4.9 MB blob, the ~1.4M events, the tens of
|
|
765
|
+
minutes, where shielded and unshielded take seconds — which is what put it under
|
|
766
|
+
`dust`. But that describes the motivation, not the thing: the pre-seed writes all
|
|
767
|
+
three sub-wallet caches, and a reference is per-network machine state in `~/.moth`
|
|
768
|
+
shared by every wallet there, whereas `moth dust` groups per-wallet token
|
|
769
|
+
operations. A command tree should say what a thing is, and someone whose first
|
|
770
|
+
sync is crawling searches for "preseed" rather than reasoning their way to DUST.
|
|
771
|
+
|
|
772
|
+
Settled while the command had not shipped, so the rename costs no compatibility.
|
|
773
|
+
|
|
774
|
+
Each action is now a real subcommand — `preseed status|import|refresh|build|export`
|
|
775
|
+
— instead of one command taking an action argument. `--timeout` therefore belongs
|
|
776
|
+
to `build` and `--force` to `import`, rather than every flag hanging off the group
|
|
777
|
+
with "(build only)" in its description, and each gets its own `--help`. The group
|
|
778
|
+
carries an oclif topic description; without one the help listed the whole group
|
|
779
|
+
under whichever subcommand sorted first.
|
|
780
|
+
- b49c96d: Require all three parts of a pre-seed reference, and drop `node:zlib` from core.
|
|
781
|
+
|
|
782
|
+
Two findings from review on the CLI/TUI parity work.
|
|
783
|
+
|
|
784
|
+
**A missing part was as damaging as a corrupt one.** Both `exportReference` and
|
|
785
|
+
`importReference` checked only dust, so a bundle without shielded state imported
|
|
786
|
+
the other two over an existing reference and moved the height key with them. The
|
|
787
|
+
store was left holding shielded at the old height and the rest at the new one — a
|
|
788
|
+
mixture that never existed on chain, reported as ready by
|
|
789
|
+
`loadUsableRefStates` because the height key still looked consistent, and that
|
|
790
|
+
inflated height then feeding the `emptyRef.height <= birthday` guard, seeding
|
|
791
|
+
wallets whose birthday fell between the two. Both functions now require every
|
|
792
|
+
part: export returns null, import refuses and names each missing file.
|
|
793
|
+
|
|
794
|
+
**`node:zlib` had no business in core.** Nothing in the browser or extension
|
|
795
|
+
packages imported `preseed-portable` yet, but it is re-exported from core's
|
|
796
|
+
barrel — and that barrel reaches 36 Node builtins where the browser package's
|
|
797
|
+
walked graph reaches none, so one careless import would have carried zlib into
|
|
798
|
+
every dependent DApp bundle. Compression now goes through `CompressionStream` and
|
|
799
|
+
`DecompressionStream`, which Node 18+ and every current browser provide, so the
|
|
800
|
+
module is genuinely isomorphic rather than allow-listed as an exception. Gzip
|
|
801
|
+
level is not selectable through that API, so bundles written here compress
|
|
802
|
+
slightly less than `scripts/export-preseed.mjs` does at level 9; sizes are
|
|
803
|
+
recorded in the manifest either way, and decompression is level-agnostic.
|
|
804
|
+
- 9be5669: Show what a transfer can actually spend.
|
|
805
|
+
|
|
806
|
+
A synced wallet reported 500 NIGHT and refused a 10 NIGHT transfer with
|
|
807
|
+
`Insufficient funds`. Both figures were true and neither was reconcilable from
|
|
808
|
+
outside the wallet.
|
|
809
|
+
|
|
810
|
+
The displayed balance counts coins reserved by transactions in flight, and does
|
|
811
|
+
so deliberately — dropping them flashes the balance to zero mid-send. But the SDK
|
|
812
|
+
spends from `availableUtxos` alone, so the number shown was never the number that
|
|
813
|
+
could be spent, and nothing surfaced the difference.
|
|
814
|
+
|
|
815
|
+
`moth balance` now prints the split when anything is reserved, and stays quiet
|
|
816
|
+
otherwise:
|
|
817
|
+
|
|
818
|
+
```
|
|
819
|
+
NIGHT:
|
|
820
|
+
unshielded: 500.000000 (500000000 STARS)
|
|
821
|
+
available: 0.000000 ← what a transfer can use
|
|
822
|
+
reserved: 500.000000 (a transaction in flight holds these)
|
|
823
|
+
```
|
|
824
|
+
|
|
825
|
+
JSON gains `unshieldedAvailable` and `unshieldedReserved` beside the existing
|
|
826
|
+
fields, so nothing reading it today breaks. The transfer's insufficient-funds
|
|
827
|
+
path names the number that blocked it, and says nothing when a reservation was
|
|
828
|
+
not the cause.
|
|
829
|
+
|
|
830
|
+
Nothing new is computed — `WalletBalances.coins` already carried the split.
|
|
831
|
+
|
|
832
|
+
This makes the state visible; it does not stop reservations outliving their
|
|
833
|
+
transactions. A wallet already in that state still needs its sync cache cleared.
|
|
834
|
+
- ea1793d: Report sync progress that is neither invented nor erased.
|
|
835
|
+
|
|
836
|
+
Two defects in how progress reached the surfaces, close enough together in
|
|
837
|
+
`extractBalancesPartial` that fixing them apart would mean resolving the same
|
|
838
|
+
twenty lines twice.
|
|
839
|
+
|
|
840
|
+
**A partial emission erased a sub-wallet.** Each sub-wallet's coins and its
|
|
841
|
+
progress were read inside a single `try`, and the coin loops reached into the
|
|
842
|
+
state without the optional chaining used one line above on the balances:
|
|
843
|
+
|
|
844
|
+
```ts
|
|
845
|
+
const sb = state.shielded?.balances; // guarded
|
|
846
|
+
for (const c of state.shielded.availableCoins) { // not guarded — throws here
|
|
847
|
+
…
|
|
848
|
+
subProgress.shielded = {applied, total}; // never reached
|
|
849
|
+
```
|
|
850
|
+
|
|
851
|
+
An emission carrying no slice for a part threw in the loop and skipped the
|
|
852
|
+
progress assignment, leaving `{applied: 0, total: 0}` — which `fraction()` treats
|
|
853
|
+
as **complete**, correctly for a sub-wallet with genuinely nothing to apply and
|
|
854
|
+
catastrophically for one whose slice was simply absent. The TUI alternated about
|
|
855
|
+
once a second between real figures and `synced · 0 / 0` with no balance. Coins
|
|
856
|
+
and progress now read separately, all six coin loops are guarded, and each part
|
|
857
|
+
carries its previous value forward when an emission says nothing about it;
|
|
858
|
+
progress does not go backwards inside a session. A genuinely 0/0 part still
|
|
859
|
+
counts as complete rather than stalling the overall figure.
|
|
860
|
+
|
|
861
|
+
**The ETA assumed every sync starts at zero.** `etaSeconds` was
|
|
862
|
+
`elapsedMs / percentage - elapsedMs`, which treats cumulative progress as this
|
|
863
|
+
session's work. Dust resumes from cache constantly, so a run that restored at
|
|
864
|
+
~65% and then ran 152s was read as "67% in 152s" — fifteen times the real rate.
|
|
865
|
+
Measured on preprod it promised 1m15s at 67% and 2m23s at 81% against a true
|
|
866
|
+
~10m, and the estimate *climbed* as elapsed time corrected the fiction. The rate
|
|
867
|
+
now comes from a per-session baseline: the same inputs give 41m and 12m19s,
|
|
868
|
+
falling as the run proceeds. Below 0.2 points of movement it reports nothing,
|
|
869
|
+
since an admitted unknown beats a number derived from noise.
|
|
870
|
+
|
|
871
|
+
Both bugs predate the CLI/TUI parity work, which only made the first visible by
|
|
872
|
+
putting per-sub-wallet counters on screen. The daemon and extension read the same
|
|
873
|
+
balances.
|
|
874
|
+
- 316ca82: Document `moth transfer` as it actually works.
|
|
875
|
+
|
|
876
|
+
The README showed `moth transfer <amount> NIGHT --to <addr>` on two rows. That form
|
|
877
|
+
does not parse — `transfer` declares one positional and rejects the second with
|
|
878
|
+
`Unexpected argument: NIGHT`. It was the documented invocation, so it was the first
|
|
879
|
+
thing a new user would type.
|
|
880
|
+
|
|
881
|
+
Corrected to `moth transfer [<amount>] [--to <addr>]`, and the rows now say what
|
|
882
|
+
was previously stated nowhere: the in-process command is NIGHT-only, with the
|
|
883
|
+
token hardcoded and no flag to change it. A row for `moth daemon transfer` covers
|
|
884
|
+
the path that *can* move other tokens, including the distinction between
|
|
885
|
+
`--amount` (raw smallest units, any token) and `--night` (a decimal converted at
|
|
886
|
+
10⁶ STARS, refused for anything but NIGHT).
|
|
887
|
+
|
|
888
|
+
Docs only. Whether the in-process command should grow token selection is the open
|
|
889
|
+
half of #62.
|
|
890
|
+
- 89f34aa: Stop the sync before freeing the keys when the TUI quits.
|
|
891
|
+
|
|
892
|
+
Quitting printed a wall of WASM errors over the exiting terminal, once per live
|
|
893
|
+
sync:
|
|
894
|
+
|
|
895
|
+
```
|
|
896
|
+
Wallet.Other: Error while applying sync update
|
|
897
|
+
cause: Error: Dust secret key was cleared
|
|
898
|
+
at DustLocalState.replayEventsWithChanges
|
|
899
|
+
```
|
|
900
|
+
|
|
901
|
+
The quit handler called `lockAll()` and then `exit()`, zeroing the
|
|
902
|
+
`DustSecretKey` in the WASM heap while the dust sync was still mid-batch. The
|
|
903
|
+
only `stop()` lived in an unmount cleanup, ran after `exit()`, and was
|
|
904
|
+
fire-and-forget, so the sync's next batch reached for a key that no longer
|
|
905
|
+
existed.
|
|
906
|
+
|
|
907
|
+
`useBalance` now exposes an awaited `stop()` — unsubscribe, await the facade's
|
|
908
|
+
stop, bounded by a timeout so a sync that will not settle cannot keep the TUI
|
|
909
|
+
open — and both quit paths await it before locking.
|
|
910
|
+
|
|
911
|
+
Nothing was corrupted: the wallet was exiting and its state was already
|
|
912
|
+
persisted. It simply looked like a crash every time, and would have buried a real
|
|
913
|
+
error.
|
|
914
|
+
|
|
915
|
+
The non-quit unmount path (Ctrl-C, a crash, the process ending) now stops before
|
|
916
|
+
locking as well, which narrows the window rather than closing it — a React
|
|
917
|
+
cleanup cannot await, so a batch already inside the WASM call can still find the
|
|
918
|
+
key gone.
|
|
919
|
+
|
|
920
|
+
## 0.12.1
|
|
921
|
+
|
|
922
|
+
## 0.12.0
|
|
923
|
+
|
|
924
|
+
### Minor Changes
|
|
925
|
+
|
|
926
|
+
- ea15676: Coordinate the first public package release under the Moth package names.
|
|
927
|
+
|
|
928
|
+
This is a minor bump because no package has previously been published under the
|
|
929
|
+
new names. The release remains experimental and unsupported, so it stays below
|
|
930
|
+
1.0.0. Package names, CLI identifiers, environment variables, connector identity,
|
|
931
|
+
and local state paths are aligned before publication; no compatibility shim is
|
|
932
|
+
required for an unpublished package.
|
|
933
|
+
|
|
934
|
+
### Patch Changes
|
|
935
|
+
|
|
936
|
+
- be98f55: Measure test coverage in CI, and share the core test fixtures.
|
|
937
|
+
|
|
938
|
+
Adds a second job to `.github/workflows/ci.yml` that runs `yarn test:coverage`
|
|
939
|
+
and archives `lcov.info`. The measurement step is advisory: V8 instrumentation
|
|
940
|
+
slows the scrypt-backed keystore and seed-export suites enough to trip vitest's
|
|
941
|
+
internal worker RPC timeout, so the run exits non-zero even though every test
|
|
942
|
+
passes and the report is written. `continue-on-error` is scoped to that step
|
|
943
|
+
rather than the job, so a run that produces no report at all still fails on the
|
|
944
|
+
upload. The strict gate stays the `test` job. Baseline at the time of writing:
|
|
945
|
+
62.54% lines, 83.76% branch over 686 tests, with entrypoints and screens excluded
|
|
946
|
+
as shells the E2E tier covers.
|
|
947
|
+
|
|
948
|
+
The three `exportSeedHex` tests no longer carry a per-test 15s timeout. A
|
|
949
|
+
literal there overrides the project config — which already raises the timeout
|
|
950
|
+
precisely because these derive keys at the v2 scrypt parameters — and made them
|
|
951
|
+
the only thing failing a `--coverage` run.
|
|
952
|
+
|
|
953
|
+
Shared test fixtures now live in `packages/core/tests/helpers/`. Every core test
|
|
954
|
+
that needs an in-memory `StorageAdapter` or the reference mnemonic takes it from
|
|
955
|
+
there — four hand-rolled `MemoryStorage` classes and four copies of the mnemonic
|
|
956
|
+
are gone, so a change to `StorageAdapter` now breaks compilation at every call
|
|
957
|
+
site, and no test can drift from the seed the address-parity fixtures pin. The
|
|
958
|
+
keystore suite shares one encrypted keystore across its shape and tamper
|
|
959
|
+
assertions while keeping an independent full-strength round trip, cutting its
|
|
960
|
+
scrypt derivations from thirteen to nine.
|
|
961
|
+
|
|
962
|
+
The extension's network picker test now asserts that every network in
|
|
963
|
+
`SUPPORTED_NETWORKS` is both named and described in the rendered markup, with
|
|
964
|
+
developer mode on so the gated mainnet is covered too. The radio count derives
|
|
965
|
+
from the state's `available` list rather than any literal, so gating a second
|
|
966
|
+
network cannot fail a correct picker; a hardcoded count disagreed with the
|
|
967
|
+
picker for twelve days after `local` was added without label entries.
|
|
968
|
+
`NETWORK_LABELS` and `NETWORK_DESCRIPTIONS` are exported for that assertion.
|
|
969
|
+
|
|
970
|
+
Root vitest config: `projects` is now globbed rather than listed, so a new
|
|
971
|
+
package cannot be gated by CI while staying invisible to the coverage number,
|
|
972
|
+
and `coverage.exclude` extends vitest's defaults rather than replacing them.
|
|
973
|
+
|
|
974
|
+
Removes the root `lint` script and turbo's `lint` task. No package defined a
|
|
975
|
+
`lint` script, so `turbo run lint` linted nothing and reported success, which
|
|
976
|
+
read as a passing gate — worse than having none. `CONTRIBUTING.md` no longer
|
|
977
|
+
claims a linter config lives in the repo.
|
|
978
|
+
|
|
979
|
+
The root `vitest` range now matches `@vitest/coverage-v8`, which declares an
|
|
980
|
+
exact peer on the vitest it ships with, so the two cannot drift onto an
|
|
981
|
+
unsupported pairing.
|
|
982
|
+
|
|
983
|
+
## 0.2.0
|
|
984
|
+
|
|
985
|
+
### Minor Changes
|
|
986
|
+
|
|
987
|
+
- b157dd2: Add the browser extension wallet and dApp connector workflow.
|
|
988
|
+
|
|
989
|
+
The wallet core now supports browser-safe persisted sync state, activity history,
|
|
990
|
+
message signing, staged transfer construction and submission, fee estimation,
|
|
991
|
+
and DUST and intent operations. Add the WXT side-panel extension and Connector
|
|
992
|
+
Lab mock dApp, expose the new APIs through the browser adapter, and keep the CLI
|
|
993
|
+
and TUI integrations compatible with asynchronous sync cache handling and the
|
|
994
|
+
Ink runtime.
|
|
995
|
+
|
|
996
|
+
- 36cb067: Add selectable proof-server and local WASM proving across the core wallet, CLI,
|
|
997
|
+
terminal dashboard, browser extension, and dApp connector API.
|
|
998
|
+
- fc93b31: Add the opt-in daemon for sharing one live wallet between a long-running host
|
|
999
|
+
(the TUI dashboard or `moth daemon serve`) and CLI clients over an authenticated
|
|
1000
|
+
Unix-socket or TCP RPC. The daemon owns the sync engine and spending keys;
|
|
1001
|
+
clients route the build/balance/prove/sign/submit pipeline through it, so keys
|
|
1002
|
+
never leave the host process. Adds the `moth daemon serve|transfer|call|deploy|
|
|
1003
|
+
submit-tx|dust register|dust deregister|key gen|key list|key revoke|maintenance`
|
|
1004
|
+
subcommands, API-key authentication with read/write scopes, an append-only audit
|
|
1005
|
+
log, and an L3 confirmation queue (interactive modal, or headless auto-approve
|
|
1006
|
+
gated behind an explicit flag + env var).
|
|
1007
|
+
- fc93b31: Adopt a single derive-and-drop key model. `deriveWalletKeys(seedHex)` produces a
|
|
1008
|
+
typed `WalletKeys` bundle once at unlock and the raw BIP-39 seed is dropped;
|
|
1009
|
+
`UnlockedWallet` no longer exposes it. Every Midnight write path accepts
|
|
1010
|
+
`WalletKeys` directly (`sendTokensWithKeys`, `designateForDustWithKeys`,
|
|
1011
|
+
`dedesignateFromDustWithKeys`, and the contract call/deploy/maintenance paths),
|
|
1012
|
+
with the seed-based functions kept as thin wrappers so both API shapes remain
|
|
1013
|
+
available. `WalletManager.exportSeedHex` is the one deliberate exception, for a
|
|
1014
|
+
key-holder that must re-supply a serializable secret across a process/document
|
|
1015
|
+
boundary (the extension's offscreen document, where WASM keys can't cross the
|
|
1016
|
+
message channel). See docs/spec/wallet-service/05-key-management.md (D-KM-3).
|
|
1017
|
+
- fc93b31: Report the NIGHT actually registered for DUST generation.
|
|
1018
|
+
|
|
1019
|
+
`DustGeneration` gains `registeredNight` — the sum of the NIGHT UTXOs flagged
|
|
1020
|
+
as registered. Registration binds the NIGHT key, but each UTXO generates via
|
|
1021
|
+
its own on-chain record, so three amounts can differ: the balance, the
|
|
1022
|
+
registered NIGHT, and the NIGHT generating right now (`designated`). The
|
|
1023
|
+
extension's DUST screen now attributes "Total possible" to the generating
|
|
1024
|
+
amount instead of the whole balance, quantifies NIGHT that is registered but
|
|
1025
|
+
not yet generating, and offers registration whenever unregistered NIGHT
|
|
1026
|
+
exists (previously the CTA vanished after the first registration).
|
|
1027
|
+
|
|
1028
|
+
`DustGeneration` also gains `newestRegisteredAt`, and `clearDustSyncCache`
|
|
1029
|
+
evicts only the dust sub-wallet's cached state. Together they power the
|
|
1030
|
+
extension's automatic dust-view repair: when registered NIGHT older than the
|
|
1031
|
+
grace period still has no generation records, the sync host rebuilds just the
|
|
1032
|
+
dust view (transaction-free, cooldown-guarded) instead of requiring a manual
|
|
1033
|
+
deregister + full resync.
|
|
1034
|
+
|
|
1035
|
+
`designateForDust` now rejects an invalid DUST receiver instead of silently
|
|
1036
|
+
falling back to the wallet's own address, `dedesignateFromDust` is exported
|
|
1037
|
+
through the browser adapter, and the extension's DUST screen gains a "Stop
|
|
1038
|
+
generating" flow plus a receiver-address field on registration (prefilled with
|
|
1039
|
+
the wallet's own DUST address; any valid DUST address is accepted).
|
|
1040
|
+
|
|
1041
|
+
- 1c597ff: Add an optional auth header for the node, for endpoints that rate-limit.
|
|
1042
|
+
|
|
1043
|
+
preprod's node answers 403 to unauthenticated callers and accepts requests
|
|
1044
|
+
carrying an operator-issued bypass header. Settings → Network gains two fields
|
|
1045
|
+
under the endpoint URLs: a header name (pre-filled with
|
|
1046
|
+
`x-shielded-ratelimit-bypass`, editable so a different operator or a renamed
|
|
1047
|
+
header needs no code change) and a masked value.
|
|
1048
|
+
|
|
1049
|
+
**This needs `declarativeNetRequestWithHostAccess`, and that is not incidental.**
|
|
1050
|
+
A browser cannot set headers on a WebSocket handshake — `new WebSocket(url,
|
|
1051
|
+
protocols)` takes no header argument — and the node connection is a WebSocket, so
|
|
1052
|
+
no JavaScript reaches it. declarativeNetRequest's `modifyHeaders` action does,
|
|
1053
|
+
because it rewrites the request before it leaves the browser and its resource
|
|
1054
|
+
types include `websocket`. The `WithHostAccess` variant reuses the
|
|
1055
|
+
`https://*.midnight.network/*` host permission already declared rather than
|
|
1056
|
+
widening access; it grants header rewriting on those hosts and nothing else.
|
|
1057
|
+
|
|
1058
|
+
Scoped to the node host only. The indexer is not rate-limited today, and a
|
|
1059
|
+
credential should reach as few destinations as possible. The rule is dynamic
|
|
1060
|
+
rather than a static ruleset — a static one would ship in the package — and is
|
|
1061
|
+
removed when the header is cleared or the node URL changes, so a credential is
|
|
1062
|
+
never left pointing at a host the wallet no longer uses.
|
|
1063
|
+
|
|
1064
|
+
The value is a secret and is treated as one: masked in the UI, never logged,
|
|
1065
|
+
excluded from developer mode (which shows endpoint, HTTP status and attempt
|
|
1066
|
+
count), and excluded from the diagnostics export, which promises "labels,
|
|
1067
|
+
durations and sizes only". It ships with an empty value — only the header _name_
|
|
1068
|
+
has a default — so no credential can enter the package.
|
|
1069
|
+
|
|
1070
|
+
Worth stating plainly: it is stored in `storage.local`, **not** in the encrypted
|
|
1071
|
+
keystore, so unlike seeds it is not protected by the wallet passphrase and is
|
|
1072
|
+
readable by anything with access to the browser profile. The help text says so.
|
|
1073
|
+
|
|
1074
|
+
Both the name and value are re-validated at the background boundary rather than
|
|
1075
|
+
trusted from the panel: names must match the RFC 7230 token characters and values
|
|
1076
|
+
must contain no CR or LF, so neither can smuggle a second header. Surrounding
|
|
1077
|
+
whitespace is trimmed, since pasting a token reliably introduces it.
|
|
1078
|
+
|
|
1079
|
+
Two related fixes this exposed. `endpointOverridesFor` collapsed to `null`
|
|
1080
|
+
whenever the URLs matched the preset, which would have silently discarded a
|
|
1081
|
+
header set against default endpoints — the common case, since the endpoint
|
|
1082
|
+
needing the header is the preset one. And `getNetworkConfig` did not carry the
|
|
1083
|
+
header through, so nothing downstream could see it.
|
|
1084
|
+
|
|
1085
|
+
- 24cb16c: Keep each network's synced state and birthday, so switching networks stops costing a full rescan.
|
|
1086
|
+
|
|
1087
|
+
Switching a wallet between networks used to be doubly expensive, and the second
|
|
1088
|
+
cost was the larger one.
|
|
1089
|
+
|
|
1090
|
+
`setNetwork` discarded the wallet's `birthday` on every move. That is defensible
|
|
1091
|
+
in isolation — a height on preprod means nothing on preview — but `syncEnsure`
|
|
1092
|
+
passes `isNewWallet: false`, so the pre-seed block is entered only when a birthday
|
|
1093
|
+
exists. A switched wallet therefore had no birthday anywhere, could never satisfy
|
|
1094
|
+
`reference.height <= birthday`, and walked from genesis on every network it
|
|
1095
|
+
touched, no matter how many references were built or shipped.
|
|
1096
|
+
|
|
1097
|
+
`walletSetNetwork` also cleared the sync cache for BOTH sides of the move, on the
|
|
1098
|
+
grounds that "switching back must also perform a fresh scan rather than revive
|
|
1099
|
+
state the user explicitly reset" — which conflated switching networks with
|
|
1100
|
+
resetting sync state. Resetting is its own deliberate action on the DUST screen.
|
|
1101
|
+
The keys were already per-network (`sync/<networkId>/<wallet>/<part>.dat`), so
|
|
1102
|
+
the wipe was a choice, not a constraint, and it grew expensive as dust came to
|
|
1103
|
+
dominate: a return trip meant 78.6 min on preprod.
|
|
1104
|
+
|
|
1105
|
+
Birthdays are now per network — `birthdays: Record<string, number>` — recorded on
|
|
1106
|
+
first arrival and never overwritten on return, because a wallet may have
|
|
1107
|
+
transacted on a network before leaving and a later tip would skip that history.
|
|
1108
|
+
The legacy single value is folded into the map under the network it belonged to
|
|
1109
|
+
rather than dropped.
|
|
1110
|
+
|
|
1111
|
+
Recording is gated on a new explicit `createdHere` flag. An imported wallet may
|
|
1112
|
+
hold funds on any chain at any height, so it never gets a birthday and keeps
|
|
1113
|
+
scanning from genesis. That distinction used to be implied by `birthday` being
|
|
1114
|
+
present at all (generate set it, import did not); splitting birthdays per network
|
|
1115
|
+
dissolved that signal, so it is now stored outright. Wallets written before the
|
|
1116
|
+
field exists read as imported — the conservative direction, since a slow sync
|
|
1117
|
+
costs time whereas the opposite error hides funds.
|
|
1118
|
+
|
|
1119
|
+
Sync caches are no longer cleared on a network switch. Both networks' state
|
|
1120
|
+
coexists and a return trip resumes from the last known good state, exactly as an
|
|
1121
|
+
ordinary restart does. Indexer changes still clear, since a different indexer can
|
|
1122
|
+
disagree about history.
|
|
1123
|
+
|
|
1124
|
+
The Settings copy said "Changing the network or indexer clears local sync data";
|
|
1125
|
+
that is now only true of the indexer, and the switch dialog no longer warns about
|
|
1126
|
+
losing state it keeps.
|
|
1127
|
+
|
|
1128
|
+
- 1f69f66: Add `refreshEmptyRefCache` — sync an existing reference forward instead of rebuilding it.
|
|
1129
|
+
|
|
1130
|
+
`ensureEmptyRefCache` short-circuits on a warm reference, so updating one appeared
|
|
1131
|
+
to require deleting it and walking the chain again: 53.6 min on preprod. That was
|
|
1132
|
+
never a limitation of the mechanism, only of the entry points.
|
|
1133
|
+
`buildEmptyRefCache` already resumed from whatever reference state the store held
|
|
1134
|
+
— it syncs under `EMPTY_REF_WALLET`, and `startWalletSync` restores that wallet's
|
|
1135
|
+
cache like any other. The only thing in the way was the early return.
|
|
1136
|
+
|
|
1137
|
+
`refreshEmptyRefCache` bypasses it, sharing the same in-flight dedup so a refresh
|
|
1138
|
+
cannot start a second chain walk beside a build already running.
|
|
1139
|
+
|
|
1140
|
+
Measured on preview: **9.1s to advance 25,660 blocks to zero stale**, against 96s
|
|
1141
|
+
to rebuild the same reference from genesis — and against 53.6 min for preprod's,
|
|
1142
|
+
which is the rebuild this would have avoided.
|
|
1143
|
+
|
|
1144
|
+
A stale reference is safe to use; it only means more catch-up for the wallets it
|
|
1145
|
+
seeds, at roughly half a second per hour of age. So this is an optimisation rather
|
|
1146
|
+
than a repair — run it before cutting a release, or on a schedule.
|
|
1147
|
+
|
|
1148
|
+
ADR 0004 was written on the premise that no refresh path existed and made building
|
|
1149
|
+
one the precondition for the CI and distribution work. That premise is corrected
|
|
1150
|
+
there, along with everything it gated.
|
|
1151
|
+
|
|
1152
|
+
- fc93b31: Add account display labels and local token names.
|
|
1153
|
+
|
|
1154
|
+
`WalletManager.setLabel` stores a user-chosen display label in the wallet's
|
|
1155
|
+
metadata (surfaced on `WalletInfo`/`UnlockedWallet`); the storage name stays the
|
|
1156
|
+
immutable key, so keystores, sync caches and sessions survive renames. The
|
|
1157
|
+
extension exposes this as "Rename account" and also lets shielded token rows be
|
|
1158
|
+
given local display names.
|
|
1159
|
+
|
|
1160
|
+
- 6766583: Make the phase-timings recorder storage-agnostic, so the CLI, TUI and daemon can use it.
|
|
1161
|
+
|
|
1162
|
+
The recorder was extension-only by construction: it imported `wxt/browser` and
|
|
1163
|
+
wrote to `storage.local`, so the three Node surfaces had no equivalent even though
|
|
1164
|
+
they run the same core sync engine and emit the same progress stream.
|
|
1165
|
+
|
|
1166
|
+
The arithmetic and the policy are identical everywhere — delta from the previous
|
|
1167
|
+
entry, a bounded history, an enabled cache so the hot path does not hit storage,
|
|
1168
|
+
and a never-throw guarantee so an instrument can never break the path it measures.
|
|
1169
|
+
Only persistence differs. So persistence is now a four-method `TimingStore`
|
|
1170
|
+
interface and everything else lives in `diagnostics/timings.ts`, isomorphic and
|
|
1171
|
+
dependency-free: no node builtins, no extension APIs, no WASM. Same split as
|
|
1172
|
+
`storage/adapter.ts` versus `storage/fs-adapter.ts`.
|
|
1173
|
+
|
|
1174
|
+
Three stores ship: `FilesystemTimingStore` (`~/.moth/timings.json`, for CLI/TUI/
|
|
1175
|
+
daemon), the extension's `storage.local` adapter, and an in-memory one for tests
|
|
1176
|
+
and short-lived processes. Because the shape is shared, a timeline captured from
|
|
1177
|
+
the CLI is directly comparable with one from the panel.
|
|
1178
|
+
|
|
1179
|
+
Two behaviours are deliberate and pinned by tests. A store that cannot answer is
|
|
1180
|
+
treated as disabled rather than raising, since recording is the optional
|
|
1181
|
+
behaviour and the failure mode must be "no data", never "broken wallet". But
|
|
1182
|
+
`setEnabled` and `clear` still propagate: those are deliberate user actions with
|
|
1183
|
+
UI behind them, and silently doing nothing would leave a toggle lying about its
|
|
1184
|
+
own state. `clear()` drops entries while keeping the enabled flag, which is how
|
|
1185
|
+
one phase gets isolated mid-session without losing what follows.
|
|
1186
|
+
|
|
1187
|
+
The extension's public API is unchanged — `record`, `getTimings`, `clearTimings`,
|
|
1188
|
+
`setTimingsEnabled`, `timingsEnabled`, `MAX_ENTRIES` and `TimingEntry` all keep
|
|
1189
|
+
their names and behaviour, so no call site moved.
|
|
1190
|
+
|
|
1191
|
+
docs/BENCHMARKING.md gains a CLI/TUI/daemon section, documents
|
|
1192
|
+
`dust-proving-check.mjs` alongside the other two instruments, corrects the setup
|
|
1193
|
+
command (the repo pins yarn; corepack rejects npm), notes that a slow `submitting`
|
|
1194
|
+
row is usually the relay backoff rather than the wallet, and flags that
|
|
1195
|
+
`sync-benchmark --json`'s `percentage` field changed meaning when progress moved
|
|
1196
|
+
to the slowest sub-wallet.
|
|
1197
|
+
|
|
1198
|
+
### Patch Changes
|
|
1199
|
+
|
|
1200
|
+
- fc93b31: Expose the outgoing transfer count on activity entries.
|
|
1201
|
+
|
|
1202
|
+
`ActivityEntry` gains `outputs` — the number of external destination outputs a
|
|
1203
|
+
(possibly batched) transaction carried, counted from the external unshielded
|
|
1204
|
+
created UTxOs the wallet can see. This lets the UI represent a multi-transfer
|
|
1205
|
+
send as one entry that reads "3 transfers" instead of silently showing only the
|
|
1206
|
+
first output. Shielded recipients are unknowable (their notes can't be
|
|
1207
|
+
decrypted), so a shielded-only batch reports 0 and the UI falls back to the
|
|
1208
|
+
count of distinct tokens moved.
|
|
1209
|
+
|
|
1210
|
+
- fc93b31: Count booked (pending) unshielded inputs in the reported balance.
|
|
1211
|
+
|
|
1212
|
+
A send or DUST registration reserves its own NIGHT UTxOs (moved from available
|
|
1213
|
+
to pending) while the transaction is in flight, settling them back to the
|
|
1214
|
+
wallet on apply. The balance previously counted only available coins, so a
|
|
1215
|
+
full-balance registration flashed the displayed balance down to zero until the
|
|
1216
|
+
transaction applied. Unshielded pending holds only these booked inputs — never
|
|
1217
|
+
incoming coins — so folding them into the balance is safe and never
|
|
1218
|
+
over-counts receipts. Shielded is unchanged (its pending includes incoming).
|
|
1219
|
+
|
|
1220
|
+
- 12881a3: Make sync-benchmark actually measure the pre-seeded path.
|
|
1221
|
+
|
|
1222
|
+
The script could not measure the thing it exists to measure, and said nothing
|
|
1223
|
+
about it. Two independent faults, either of which was sufficient:
|
|
1224
|
+
|
|
1225
|
+
- The measured wallet was given a bare `InMemorySyncStateStore`, but
|
|
1226
|
+
`ensureEmptyRefCache` looks for the reference in whatever store the wallet was
|
|
1227
|
+
given. So it searched an empty store, found nothing, and measured the unseeded
|
|
1228
|
+
path. A run in the same process as `--warm-reference` only found the reference
|
|
1229
|
+
through the module-level `refCache`, never from disk.
|
|
1230
|
+
- The birthday was read before warming. Warming takes minutes to an hour and the
|
|
1231
|
+
chain moves under it, so the birthday came out older than the reference the run
|
|
1232
|
+
had just built, `reference.height <= birthday` failed, and the guard correctly
|
|
1233
|
+
refused to seed — while the run announced "reference ready … should start at
|
|
1234
|
+
tip".
|
|
1235
|
+
|
|
1236
|
+
Both are fixed. The birthday is now read after any warm, and the measured wallet
|
|
1237
|
+
gets an overlay store that reads the reference keys through to disk while keeping
|
|
1238
|
+
every write in memory — so a run still never leaves wallet state in `~/.moth`,
|
|
1239
|
+
and never mutates the reference it is measuring against.
|
|
1240
|
+
|
|
1241
|
+
Verified on preview: 94.5s unseeded, **2.2s** seeded, 96.0s to build the
|
|
1242
|
+
reference. Previously both modes reported 94.5s.
|
|
1243
|
+
|
|
1244
|
+
`emptyRefHeightKey` is now exported from the core barrel; the overlay needs it to
|
|
1245
|
+
know which keys belong to the reference.
|
|
1246
|
+
|
|
1247
|
+
This also means any previously recorded "warm reference" figure came from a run
|
|
1248
|
+
that measured the unseeded path, and should be re-measured rather than trusted.
|
|
1249
|
+
docs/BENCHMARKING.md now says so, documents the `Pre-seed complete` line to check
|
|
1250
|
+
for, and records the preview numbers.
|
|
1251
|
+
|
|
1252
|
+
- c7d1ef7: Apply the SDK console-noise filter in the browser, not just in Node.
|
|
1253
|
+
|
|
1254
|
+
`SDK_NOISE` has always listed the strings the wallet SDK and @polkadot emit on
|
|
1255
|
+
every reconnect — `API-WS`, `disconnected from`, `Abnormal Closure`,
|
|
1256
|
+
`RPC-CORE`, `subscribeRuntimeVersion`. But the installer bailed out when
|
|
1257
|
+
`process` was undefined, so the CLI, TUI and daemon got a clean console while the
|
|
1258
|
+
extension — the surface most users actually see — got all of it.
|
|
1259
|
+
|
|
1260
|
+
The console patch now runs everywhere. The stdout/stderr interception stays
|
|
1261
|
+
Node-only, since @polkadot's logger writes directly to those streams there and a
|
|
1262
|
+
browser worker has no equivalent; so does the `unhandledRejection` handler,
|
|
1263
|
+
deliberately, because a swallowed rejection in the worker would hide failures the
|
|
1264
|
+
extension has no other channel to report.
|
|
1265
|
+
|
|
1266
|
+
The curated list is unchanged, and the rule behind it still holds: specific
|
|
1267
|
+
strings only, never broad patterns that could swallow a security-relevant error.
|
|
1268
|
+
|
|
1269
|
+
One thing this cannot reach: `WebSocket connection to '…' failed: 403`. Chrome
|
|
1270
|
+
emits that from its network stack rather than from JavaScript, so no console
|
|
1271
|
+
patch touches it. That is the right outcome — it is not benign. It means the node
|
|
1272
|
+
is genuinely unreachable, and the relay banner now says so in the UI while the
|
|
1273
|
+
backoff keeps the repetition to roughly one line a minute instead of twenty-four.
|
|
1274
|
+
|
|
1275
|
+
- 6f914fc: Clear nine Dependabot advisories in transitive build dependencies.
|
|
1276
|
+
|
|
1277
|
+
All ten open alerts are transitive, and every one arrives through build or dev
|
|
1278
|
+
tooling — `web-ext` (via WXT's Firefox support), `changesets`, `vite`,
|
|
1279
|
+
`node-notifier`. None is reachable from the shipped extension bundle, checked
|
|
1280
|
+
against `.output/chrome-mv3`. That lowers the severity in practice but does not
|
|
1281
|
+
make them worth leaving.
|
|
1282
|
+
|
|
1283
|
+
Resolved via `resolutions`, which this repo already uses for the same purpose:
|
|
1284
|
+
|
|
1285
|
+
| package | was | now | severity |
|
|
1286
|
+
| --------------- | ------ | ------ | -------- |
|
|
1287
|
+
| shell-quote | 1.7.3 | 1.10.0 | critical |
|
|
1288
|
+
| adm-zip | 0.5.18 | 0.6.0 | high |
|
|
1289
|
+
| brace-expansion | 5.0.7 | 5.0.9 | high |
|
|
1290
|
+
| js-yaml | 4.3.0 | 4.3.1 | high |
|
|
1291
|
+
| tmp | 0.2.5 | 0.2.7 | high |
|
|
1292
|
+
| uuid | 8.3.2 | 11.1.1 | medium |
|
|
1293
|
+
| esbuild | — | 0.28.2 | low |
|
|
1294
|
+
|
|
1295
|
+
Pinned to the lowest patched major rather than `>=`. An open-ended range let
|
|
1296
|
+
yarn take js-yaml 5.2.3 and uuid 14.0.1 — several majors beyond what the
|
|
1297
|
+
advisories require, and a far larger change than the fix warrants.
|
|
1298
|
+
|
|
1299
|
+
`elliptic` (low, via `browserify-sign`) has no patched version published and is
|
|
1300
|
+
left open. It is a transitive dependency of a build-time crypto shim, not of the
|
|
1301
|
+
wallet's own cryptography, which runs through the Midnight ledger WASM.
|
|
1302
|
+
|
|
1303
|
+
- 771338d: Tell the user when registration needs time, instead of failing and blaming the
|
|
1304
|
+
proof server.
|
|
1305
|
+
|
|
1306
|
+
Registering NIGHT for DUST generation is self-funding: a `DustRegistration`
|
|
1307
|
+
carries `allow_fee_payment`, and the ledger lets the transaction pay its own fee
|
|
1308
|
+
from the DUST its NIGHT _would have_ generated had it been registered all along.
|
|
1309
|
+
That is what stops the obvious deadlock — DUST pays fees, and registering is how
|
|
1310
|
+
you get DUST.
|
|
1311
|
+
|
|
1312
|
+
Self-funding is not free. `generationless_fee_availability` caps the backdated
|
|
1313
|
+
amount at `elapsed × night_value × generation_decay_rate`, which starts at zero.
|
|
1314
|
+
So a freshly funded wallet cannot cover the fee yet, and the wait is _inversely
|
|
1315
|
+
proportional to the balance_: at the ledger's defaults a 0.3 DUST fee needs ~36s
|
|
1316
|
+
at 1,000 NIGHT, ~6 min at 100, ~1 hour at 10, and ~10 hours at 1.
|
|
1317
|
+
|
|
1318
|
+
Reported from preprod as a red failure card reading "That didn't go through",
|
|
1319
|
+
with the raw SDK message and a footnote suggesting the proof server. Two of those
|
|
1320
|
+
three were wrong: nothing went wrong, and proving never happened — the SDK refuses
|
|
1321
|
+
before building.
|
|
1322
|
+
|
|
1323
|
+
`estimateRegistrationAffordability` (core, pure, WASM-free) turns the SDK's
|
|
1324
|
+
per-UTxO figures into an answer: affordable now, affordable in N seconds, or never
|
|
1325
|
+
at this holding. That last case matters — when the ceiling is below the fee,
|
|
1326
|
+
"wait" is the wrong advice and "hold more NIGHT" is the right one.
|
|
1327
|
+
|
|
1328
|
+
`designateForDust` throws `DustRegistrationNotYetError` carrying that estimate.
|
|
1329
|
+
Because the guard sits in core, it reaches every run mode at once — extension,
|
|
1330
|
+
CLI, TUI and daemon RPC all route through the same function.
|
|
1331
|
+
|
|
1332
|
+
Two decisions worth recording. The estimate is computed only on the failure path,
|
|
1333
|
+
so a registration that was always going to succeed pays nothing for it. And
|
|
1334
|
+
whether to raise the typed error is decided by the affordability numbers, not by
|
|
1335
|
+
matching the SDK's message text — string-matching would need re-matching on every
|
|
1336
|
+
SDK release, and would fail silently when it drifted.
|
|
1337
|
+
|
|
1338
|
+
The panel now shows "Not quite yet", says nothing was spent, and gives a localized
|
|
1339
|
+
wait ("Ready in about 8 hours"). `mayBeProvingFailure` suppresses the proof-server
|
|
1340
|
+
footnote for every outcome decided before proving.
|
|
1341
|
+
|
|
1342
|
+
`moth dust register` gains a pre-flight and `--wait` (with `--wait-timeout`). The
|
|
1343
|
+
pre-flight matters more on the CLI than in the panel: without it the only way to
|
|
1344
|
+
learn the wait is to fail, and re-running means paying for a full sync first.
|
|
1345
|
+
`--wait` polls rather than sleeping the predicted duration blind, since the
|
|
1346
|
+
estimate moves if the wallet's NIGHT changes underneath it.
|
|
1347
|
+
|
|
1348
|
+
Also corrects the documentation. Four files stated that the ledger imposes a 3h
|
|
1349
|
+
grace period before DUST appears. `dust_grace_period` is 3 hours, but it bounds how
|
|
1350
|
+
stale a transaction's declared `ctime` may be — it is not a delay before
|
|
1351
|
+
generation starts, which is linear from the UTxO's creation with a time-to-cap of
|
|
1352
|
+
about a week. The observation behind the claim was real; the mechanism was
|
|
1353
|
+
invented to fit it. The guides are corrected in place; ADR 0003 is annotated
|
|
1354
|
+
rather than rewritten, since it is a dated record of what was decided.
|
|
1355
|
+
|
|
1356
|
+
- fc93b31: Fix contract circuit calls being rejected with "expected proof-preimage-versioned".
|
|
1357
|
+
|
|
1358
|
+
`callCircuit` previously used a hand-rolled proof provider that POSTed the bare
|
|
1359
|
+
proof-preimage, producing an unversioned `midnight:proof-preimage:` payload that
|
|
1360
|
+
ledger-v8's `check` rejects. Circuit calls now generate proofs through the
|
|
1361
|
+
selectable proof provider: for server proving it routes through the SDK's
|
|
1362
|
+
proving provider and the ledger's versioned `createProvingPayload`/
|
|
1363
|
+
`createCheckPayload` (attaching the circuit's wrapped-IR from the ZK config),
|
|
1364
|
+
and it also supports local WASM proving. Verified with an on-chain preprod mint.
|
|
1365
|
+
|
|
1366
|
+
- ba86b72: Bump ws from 8.20.1 to 8.21.0.
|
|
1367
|
+
- fc93b31: Fix the transparent keystore KDF upgrade writing to a phantom storage key
|
|
1368
|
+
(`wallets/<name>` instead of `wallets/<name>.keystore`). The re-encryption to
|
|
1369
|
+
stronger scrypt parameters never persisted, so it re-ran on every unlock and v1
|
|
1370
|
+
keystores were never actually upgraded. It now writes back to the real keystore
|
|
1371
|
+
path and upgrades once.
|
|
1372
|
+
- b7e2f00: add local network support
|
|
1373
|
+
- 0f9369f: Pre-seed each sub-wallet independently, so a DUST rebuild stops walking from genesis.
|
|
1374
|
+
|
|
1375
|
+
The pre-seed gate tested the SHIELDED cache alone, as a proxy for "this wallet has
|
|
1376
|
+
no state yet". That proxy failed exactly where it mattered most.
|
|
1377
|
+
|
|
1378
|
+
`clearDustSyncCache` evicts the dust cache and nothing else, which is what the DUST
|
|
1379
|
+
screen's "Rebuild records" does. Shielded was therefore still present, the gate
|
|
1380
|
+
stayed shut, and dust walked all 1.4M events from genesis — 78.6 min on preprod —
|
|
1381
|
+
with a perfectly usable reference sitting in the store untouched. "Rebuild
|
|
1382
|
+
records" is precisely what a user reaches for when dust looks wrong, so the
|
|
1383
|
+
narrow, careful-looking operation was the slowest to recover, while a full
|
|
1384
|
+
indexer-change wipe (which clears all four parts) re-seeded and finished in
|
|
1385
|
+
seconds.
|
|
1386
|
+
|
|
1387
|
+
The gate now opens when ANY seedable part is missing, and each part is written
|
|
1388
|
+
only where absent — a part that already has a cache is at least as far along as
|
|
1389
|
+
the reference, so seeding over it would discard progress.
|
|
1390
|
+
|
|
1391
|
+
Mixed heights are coherent, and were verified rather than assumed. The
|
|
1392
|
+
sub-wallets carry independent cursors, so dust can restore at the reference's
|
|
1393
|
+
height while shielded and unshielded resume at tip, each catching up on its own
|
|
1394
|
+
stream. Two measurements on preview:
|
|
1395
|
+
|
|
1396
|
+
- dust rewound to the reference (64,771) with shielded at tip (64,982): fully
|
|
1397
|
+
synced in 1.0s, balances identical.
|
|
1398
|
+
- a real DUST rebuild on a funded, dust-registered wallet: `Pre-seed complete —
|
|
1399
|
+
dust at chain tip`, dust resumed at 64,771 instead of 0, synced in 1.0s, with
|
|
1400
|
+
NIGHT, the DUST registration and the DUST balance all preserved.
|
|
1401
|
+
|
|
1402
|
+
The decision moves to `sync/preseed-parts.ts`, WASM-free so it is unit-testable
|
|
1403
|
+
without loading the ledger — the same split as `sync/progress.ts`. Its tests pin
|
|
1404
|
+
the case that was broken: shielded and unshielded cached, dust absent, must seed
|
|
1405
|
+
dust.
|
|
1406
|
+
|
|
1407
|
+
- bf49ced: Sync the pre-seed reference wallet to chain tip before serializing it.
|
|
1408
|
+
|
|
1409
|
+
`buildEmptyRefCache` started the reference wallet and stopped it immediately.
|
|
1410
|
+
`startWalletSync` resolves on the first balance emission (or a 5s timeout), so
|
|
1411
|
+
`stop()` serialized a wallet that had applied nothing: every sub-wallet snapshot
|
|
1412
|
+
was written with `offset: 0`, which the SDK reads back as `appliedIndex: 0n` —
|
|
1413
|
+
its "stream from genesis" sentinel. The pre-seed then reported "shielded +
|
|
1414
|
+
unshielded + dust at chain tip" while seeding genesis, and had done so for as
|
|
1415
|
+
long as the cache had existed. Shielded and unshielded hid it because their
|
|
1416
|
+
genesis scan is cheap; dust made it visible as an hour of syncing.
|
|
1417
|
+
|
|
1418
|
+
Measured on preprod, brand-new empty wallet, cold cache:
|
|
1419
|
+
|
|
1420
|
+
- before: dust synced at 4715.8s, total 78.6 min (99.2% of it dust,
|
|
1421
|
+
1,382,732 events at ~293/s)
|
|
1422
|
+
- after: total 49.2s, of which 46.7s is one DustLocalState.deserialize
|
|
1423
|
+
|
|
1424
|
+
Building the reference now costs one full chain walk (71.3 min on preprod) per
|
|
1425
|
+
network per machine, so it must not sit on the wallet-startup path — waiting there
|
|
1426
|
+
would block the user's own wallet for over an hour. `ensureEmptyRefCache` therefore
|
|
1427
|
+
no longer builds by default: it returns a reference already at tip, or nothing.
|
|
1428
|
+
Deliberate builds go through the new `warmEmptyRefCache()`, intended for a
|
|
1429
|
+
background task or an explicit command. A warm on-disk reference is picked up in
|
|
1430
|
+
0.02s, and a build that times out leaves its partial state for the next attempt to
|
|
1431
|
+
resume rather than handing out a useless snapshot.
|
|
1432
|
+
|
|
1433
|
+
`loadUsableRefStates` gates on the serialized cursor, so a reference at offset 0
|
|
1434
|
+
is never again treated as warm. Also corrects the docblock claiming dust cannot be
|
|
1435
|
+
pre-seeded: dust ledger events are global (the indexer streams `dustLedgerEvents`
|
|
1436
|
+
keyed by a global id) and an empty wallet has no designations of its own, so the
|
|
1437
|
+
reference's generation tree and cursor do transfer — now verified end to end.
|
|
1438
|
+
|
|
1439
|
+
Also adds the birthday guard that making this work turned from latent into live.
|
|
1440
|
+
The pre-seed condition is `(isNewWallet || birthday) && no shielded cache`, which
|
|
1441
|
+
admits any wallet merely missing a cache — including a funded one after a cache
|
|
1442
|
+
reset, a storage eviction, or a restore from mnemonic. Seeding such a wallet from
|
|
1443
|
+
a reference newer than its own first activity starts it past its own history and
|
|
1444
|
+
drops funds from view. Harmless while the reference sat at offset 0 (seeding
|
|
1445
|
+
genesis is always safe); a real hazard once it carries a tip cursor.
|
|
1446
|
+
|
|
1447
|
+
Pre-seeding now requires `birthday !== undefined && reference.height <= birthday`.
|
|
1448
|
+
The height is recorded separately at build time (`emptyRefHeightKey`) because the
|
|
1449
|
+
snapshots' `offset` is an event index, not a block height, and the two are not
|
|
1450
|
+
comparable — 1,382,805 against 1,977,245 on preprod. It is read after the sync
|
|
1451
|
+
completes, which can only overstate it and therefore only make the check stricter,
|
|
1452
|
+
and a reference with no recorded height is treated as unusable rather than
|
|
1453
|
+
guessed at. Existing callers are unaffected: the TUI passes `isNewWallet` without
|
|
1454
|
+
a birthday and the CLI passes neither, so both keep the slow path.
|
|
1455
|
+
|
|
1456
|
+
Nothing calls `warmEmptyRefCache()` yet, so no shipped surface changes behaviour:
|
|
1457
|
+
new wallets still sync from genesis until a caller warms the reference.
|
|
1458
|
+
|
|
1459
|
+
- 0f197e2: Preserve transaction identities in activity entries and submitted transaction records so applied transactions replace their pending rows instead of appearing as duplicates.
|
|
1460
|
+
- fc93b31: Fix transfer submission to return `transactionHash()` instead of the facade's
|
|
1461
|
+
last intent identifier. Tx history, indexer status queries and explorers are all
|
|
1462
|
+
keyed by the transaction hash, so the old value matched nothing — leaving the
|
|
1463
|
+
extension's activity feed stuck on "Pending" after a transfer had applied.
|
|
1464
|
+
- 2fde86f: Report sync progress from the slowest sub-wallet, not the shielded one.
|
|
1465
|
+
|
|
1466
|
+
Progress read shielded indices only, on the stated assumption that shielded was
|
|
1467
|
+
the slowest sub-wallet. It is not — dust is, by two orders of magnitude: a full
|
|
1468
|
+
dust walk is ~1.4M events at a few hundred per second, where shielded covers the
|
|
1469
|
+
same range in under a minute.
|
|
1470
|
+
|
|
1471
|
+
Observed on preprod: a wallet reporting "100% (0s remaining)" with dust at
|
|
1472
|
+
178,029/1,395,558 and roughly 69 minutes of work left. That is worse than
|
|
1473
|
+
reporting nothing, because it stops the user waiting.
|
|
1474
|
+
|
|
1475
|
+
Progress is now the minimum across all three sub-wallets. A sub-wallet with
|
|
1476
|
+
nothing relevant to apply (total 0) counts as complete rather than stalled — a
|
|
1477
|
+
fresh wallet's unshielded progress is legitimately 0/0 and must not drag the
|
|
1478
|
+
minimum to zero. The figure never rounds up to 100% while the facade still says
|
|
1479
|
+
unsynced, since rendering a near-complete fraction as "100% (0s remaining)" is
|
|
1480
|
+
the specific lie this change exists to remove. The ETA follows the same fraction,
|
|
1481
|
+
so it reflects whichever sub-wallet is actually behind rather than one that
|
|
1482
|
+
finished a minute in.
|
|
1483
|
+
|
|
1484
|
+
The arithmetic moved to sync/progress.ts so it can be unit-tested without loading
|
|
1485
|
+
WASM — the same split as types/tokens.ts and the extension's dust-heal.ts.
|