wative-core 2.2.1 → 2.4.0
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 +634 -13
- package/LICENSE +114 -0
- package/README.md +144 -5
- package/dist/artifacts/evm/index.cjs +1 -1
- package/dist/artifacts/evm/index.d.cts +2 -2
- package/dist/artifacts/evm/index.d.ts +2 -2
- package/dist/artifacts/evm/index.js +1 -1
- package/dist/artifacts/svm/index.cjs +1 -1
- package/dist/artifacts/svm/index.d.cts +3 -3
- package/dist/artifacts/svm/index.d.ts +3 -3
- package/dist/artifacts/svm/index.js +1 -1
- package/dist/{container-provider-C59yqd5Z.d.ts → container-provider-D2QluIe_.d.ts} +10 -3
- package/dist/{container-provider-i614LDE1.d.cts → container-provider-DAUXclw_.d.cts} +10 -3
- package/dist/{evm-BLml33LX.d.cts → evm-GxBuNjq7.d.ts} +2 -2
- package/dist/{evm-DTzqGlL6.d.ts → evm-qXdZWugm.d.cts} +2 -2
- package/dist/{file-sink-B73MuigK.d.cts → file-sink-B0EjCBvG.d.ts} +8 -5
- package/dist/{file-sink-LS4hyXXi.d.ts → file-sink-DSOZbsJM.d.cts} +8 -5
- package/dist/index.cjs +1 -1
- package/dist/index.d.cts +25 -8
- package/dist/index.d.ts +25 -8
- package/dist/index.js +1 -1
- package/dist/index.node.cjs +1 -1
- package/dist/index.node.d.cts +6 -6
- package/dist/index.node.d.ts +6 -6
- package/dist/index.node.js +1 -1
- package/dist/node/index.cjs +1 -1
- package/dist/node/index.d.cts +3 -3
- package/dist/node/index.d.ts +3 -3
- package/dist/node/index.js +1 -1
- package/dist/{svm-DieFbh9k.d.ts → svm-B_c0L-rJ.d.cts} +2 -2
- package/dist/{svm-CCzqNAWM.d.cts → svm-Ca4Xyw0Y.d.ts} +2 -2
- package/dist/{workspace-CamTHxso.d.cts → workspace-DdaY7sWa.d.cts} +23 -4
- package/dist/{workspace-CamTHxso.d.ts → workspace-DdaY7sWa.d.ts} +23 -4
- package/examples/15-package-resolution.test.cjs +187 -1
- package/examples/18-envelope-v3.test.cjs +106 -0
- package/package.json +6 -3
package/CHANGELOG.md
CHANGED
|
@@ -2,32 +2,653 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to `wative-core` are documented here. The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/) and the project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
4
4
|
|
|
5
|
+
## [2.4.0] — 2026-08-11
|
|
6
|
+
|
|
7
|
+
Six breaking items, each one an operation that reported success without having
|
|
8
|
+
done what it said. A broadcast whose reply never arrived was reported as one
|
|
9
|
+
that never went out — on both chains. Moving an account between workspaces saved
|
|
10
|
+
its later changes to the wrong place; changing a password could leave the old one
|
|
11
|
+
working; erasing a browser workspace could leave everything in it; and restoring
|
|
12
|
+
a backup over an existing workspace kept records the backup did not contain. The
|
|
13
|
+
rest either clears secrets from memory sooner, or accepts input that was being
|
|
14
|
+
refused for no reason.
|
|
15
|
+
|
|
16
|
+
### Security
|
|
17
|
+
- **BREAKING. A broadcast whose outcome is unknown no longer reports as one that
|
|
18
|
+
was refused — on both chains.** When the connection to an endpoint died during
|
|
19
|
+
a broadcast, or the endpoint answered with an error status or something that
|
|
20
|
+
was not a reply, the send reported `failed`: the one status meaning "that never
|
|
21
|
+
went out, send it again". Those bytes may well have reached the network. On
|
|
22
|
+
EVM a rebuilt transaction takes a fresh position from a node whose count the
|
|
23
|
+
relayed one already advanced; on Solana there is no position to collide at all,
|
|
24
|
+
so a rebuild takes a fresh blockhash, becomes a different transaction, and
|
|
25
|
+
nothing dedupes the two. Either way both can land.
|
|
26
|
+
|
|
27
|
+
Such a send now reports `timeout`, meaning the outcome is unknown, and the
|
|
28
|
+
identifier of the transaction that was signed is reported so it can be looked
|
|
29
|
+
up before anything else is sent.
|
|
30
|
+
|
|
31
|
+
An endpoint that answers with a reason it refused still reports `failed`,
|
|
32
|
+
because nothing was sent and sending again is safe. That is the ordinary way a
|
|
33
|
+
send fails — an underpriced or badly-positioned transaction on EVM, an expired
|
|
34
|
+
blockhash or a failed simulation on Solana — and for an expired blockhash
|
|
35
|
+
rebuilding is exactly the right response.
|
|
36
|
+
- **BREAKING. An endpoint that refuses is reported differently from one that
|
|
37
|
+
never answered.** Every way a request could go wrong reported the same thing,
|
|
38
|
+
including the one case where the endpoint read the request and declined it.
|
|
39
|
+
Those are opposite facts for a broadcast, and a caller needs to tell them
|
|
40
|
+
apart to know whether sending again is safe. A refusal now carries its own
|
|
41
|
+
error code and the reason the endpoint gave; a lost connection, an error
|
|
42
|
+
status, or an unreadable reply still report the endpoint as unreachable. The
|
|
43
|
+
two chains previously disagreed about which code a refusal carried; they now
|
|
44
|
+
agree.
|
|
45
|
+
- **A value an endpoint sent is cleaned before it appears in an error.** Two
|
|
46
|
+
errors raised while reading a reply placed that reply into the message exactly
|
|
47
|
+
as received, so control characters chosen by the endpoint were reproduced into
|
|
48
|
+
whatever log the caller writes, and an overlong value was repeated in full.
|
|
49
|
+
The value is still shown, since for a number the rejected text is the
|
|
50
|
+
diagnosis, but it is cleaned and shortened.
|
|
51
|
+
|
|
52
|
+
### Security
|
|
53
|
+
- **BREAKING. Changing an account's password now reports failure rather than a
|
|
54
|
+
success that did not happen.** If the account was locked between the moment
|
|
55
|
+
the change was requested and the moment it was carried out, the recovery
|
|
56
|
+
phrase was quietly left under the old password while everything else about the
|
|
57
|
+
change was saved. The account then reported that its password had been
|
|
58
|
+
changed, after which the new password failed and the old one still worked —
|
|
59
|
+
the exact reverse of the promise, in the situation you change a password for.
|
|
60
|
+
An account managed by its workspace was additionally recorded as having a
|
|
61
|
+
password of its own, which made it unopenable the next time it was loaded.
|
|
62
|
+
Two changes started at the same time could also both report success, with the
|
|
63
|
+
second overwriting the first using a password the first had already retired.
|
|
64
|
+
Both cases now report failure and leave the account exactly as it was.
|
|
65
|
+
- **BREAKING. Erasing a browser workspace now reports whether it happened.**
|
|
66
|
+
Erasing one while the same application still had it open reported success and
|
|
67
|
+
left every stored record in the browser. Because nothing released the
|
|
68
|
+
workspace, that was the ordinary outcome rather than a rare one. The workspace
|
|
69
|
+
is now closed first so the erase can finish. An erase that cannot be confirmed
|
|
70
|
+
is reported as unconfirmed rather than as failed — it may still complete on its
|
|
71
|
+
own, and erasing again is always safe. Erasing where the browser offers no
|
|
72
|
+
storage at all now reports that, instead of returning quietly.
|
|
73
|
+
- **Secrets are cleared from memory once they are no longer needed.** Several
|
|
74
|
+
intermediate values produced while deriving a key were left behind after use,
|
|
75
|
+
and a derived key dropped from the internal cache was discarded without being
|
|
76
|
+
cleared, putting it beyond the reach of the cleanup that runs when a workspace
|
|
77
|
+
locks. Unlocking an account whose recovery phrase could not be read also left
|
|
78
|
+
that phrase in memory although the account reported itself locked. Keys and
|
|
79
|
+
addresses are unchanged: derivation was checked byte for byte against the
|
|
80
|
+
previous release and against the published Argon2 test vectors.
|
|
81
|
+
- **An endpoint that is not text is refused rather than saved unchecked.**
|
|
82
|
+
`rpcUrl` was the only network field with no type check, so a value that was not
|
|
83
|
+
text skipped every endpoint check — including the one that refuses addresses on
|
|
84
|
+
your local network — and was stored anyway. Leaving the field out reported an
|
|
85
|
+
error from outside this library rather than its own, including while loading a
|
|
86
|
+
stored network. An empty endpoint still means no endpoint configured.
|
|
87
|
+
- **A rejected name or colour is quoted before it is reported.** Both were placed
|
|
88
|
+
into the error message exactly as supplied, so control characters in them were
|
|
89
|
+
reproduced into whatever log you write. A name is not always your own — it can
|
|
90
|
+
come from a filename on disk, or from a backup being restored.
|
|
91
|
+
|
|
92
|
+
### Fixed
|
|
93
|
+
- **BREAKING. An account moved to another workspace now belongs to the workspace
|
|
94
|
+
holding it.** Moving one wrote a single record into the receiving workspace and
|
|
95
|
+
then kept sending every later change back to the workspace it came from.
|
|
96
|
+
Reopening the receiving workspace showed the account without those changes, and
|
|
97
|
+
closing the original one made the account unusable even though the receiving
|
|
98
|
+
workspace was open and still listed it. For an imported private key, discarding
|
|
99
|
+
the original workspace lost the key. The account now saves to the workspace
|
|
100
|
+
holding it, unlocks with that workspace's password, and is closed by it.
|
|
101
|
+
Locking the receiving workspace while the move is still going on now closes the
|
|
102
|
+
account rather than leaving it open and unreachable. Re-deleting an account
|
|
103
|
+
that was moved in reports that it is already gone, rather than reporting it as
|
|
104
|
+
belonging elsewhere.
|
|
105
|
+
- **BREAKING. Restoring a backup over an existing workspace now replaces it.**
|
|
106
|
+
Records the backup did not contain were left in place beside the restored ones,
|
|
107
|
+
still under the previous password. They could not be opened, could not be seen,
|
|
108
|
+
kept their names reserved, and made removing a network fail from then on. Only
|
|
109
|
+
what the backup carries is kept now, and anything it omits is removed. A backup
|
|
110
|
+
that cannot be restored in full is refused before anything changes, and a
|
|
111
|
+
backup carrying no configuration is refused outright rather than leaving a
|
|
112
|
+
workspace no password can open. The workspace is closed at the end, since the
|
|
113
|
+
password that opened it belonged to the workspace that was just replaced.
|
|
114
|
+
- **A wallet can be labelled in your own script.** Tags and token symbols
|
|
115
|
+
accepted the letters of every script but refused the marks that most of them
|
|
116
|
+
require, so Devanagari, Bengali, Tamil, Thai, Arabic and Hebrew could not be
|
|
117
|
+
used at all. They now are. A label that would display as blank, or as nothing
|
|
118
|
+
at all, is still refused. Token symbols containing `+` are also accepted, which
|
|
119
|
+
several real tokens need.
|
|
120
|
+
- **The two spellings of one password are one password.** The same password can
|
|
121
|
+
arrive in two forms depending on the keyboard, clipboard or operating system it
|
|
122
|
+
came from, and both have always unlocked a wallet. Several places compared the
|
|
123
|
+
forms directly instead, so a correct password could be reported as wrong when
|
|
124
|
+
re-entered on an already-open workspace or account, when confirming a record's
|
|
125
|
+
password, and when creating an account that shares the workspace password.
|
|
126
|
+
Checking a new password against previous ones now recognises the same password
|
|
127
|
+
in a different form as a reuse.
|
|
128
|
+
- **A deleted account's name can be used again.** Every account name seen when a
|
|
129
|
+
workspace opened was reserved for as long as it stayed open, and deleting the
|
|
130
|
+
account did not release it. Creating an account under that name again quietly
|
|
131
|
+
produced a numbered variant, and adding one back was refused with a message
|
|
132
|
+
asking you to restore a file that had just been deleted.
|
|
133
|
+
- **A token address pasted with a leading space is accepted.** It was refused
|
|
134
|
+
here while the same address was accepted everywhere else in the library.
|
|
135
|
+
- **A hex value carrying two prefixes is refused rather than decoded.**
|
|
136
|
+
`0x0x41` produced two bytes with a zero in front instead of an error.
|
|
137
|
+
- **Moving an account needs the networks it uses to exist in the receiving
|
|
138
|
+
workspace.** An account refers to its networks by name, so moving one into a
|
|
139
|
+
workspace that does not have a network it uses wrote a record that could not
|
|
140
|
+
be read back. The move reported success, and after reopening the account was
|
|
141
|
+
simply absent — not listed, not reported as held back, and not even holding
|
|
142
|
+
its name, while a record containing its recovery phrase and every key stayed
|
|
143
|
+
in the workspace, unreachable and impossible to remove. The move is now
|
|
144
|
+
refused before anything is written, naming the missing network.
|
|
145
|
+
- **An account cannot be left holding no keys.** Both ways of removing wallets
|
|
146
|
+
checked that at least one wallet remained, which does not mean at least one
|
|
147
|
+
key remains: a wallet can legitimately be added without addresses, so removing
|
|
148
|
+
the wallet carrying an account's only keys left an account with none. It still
|
|
149
|
+
reported itself as an account, and reopening it produced a shell with nothing
|
|
150
|
+
in it. Removing wallets that hold no keys is unaffected, as is removing an
|
|
151
|
+
account outright.
|
|
152
|
+
|
|
153
|
+
## [2.3.9] — 2026-08-10
|
|
154
|
+
|
|
155
|
+
Three breaking items. Two are on paths that were producing results no
|
|
156
|
+
counterparty could act on; the third changes how a refused broadcast is
|
|
157
|
+
reported, on both chains, so that it cannot be read as "safe to send again". Everything else here narrows what error messages reveal, or
|
|
158
|
+
accepts input that was being refused for no reason.
|
|
159
|
+
|
|
160
|
+
### Security
|
|
161
|
+
- **BREAKING. The endpoint-reply check now covers Solana.** 2.3.6 stopped an
|
|
162
|
+
endpoint choosing which transaction was watched after a broadcast, but that
|
|
163
|
+
check only ever applied to EVM sends — the Solana lane still took the reply at
|
|
164
|
+
face value, so an endpoint could hand back an identifier of its own and report
|
|
165
|
+
results for a transaction you never sent. That reply is now checked against the
|
|
166
|
+
transaction that was actually signed, and everything afterwards follows the
|
|
167
|
+
signed value. A send refused
|
|
168
|
+
for this reason reports `timeout`, not `failed`, because the endpoint did
|
|
169
|
+
acknowledge it and the transaction may well have been relayed — look it up by
|
|
170
|
+
its signed signature before sending anything else, and do not rebuild it with a
|
|
171
|
+
fresh blockhash. The signed identifier is still reported, so it can be looked
|
|
172
|
+
up. Endpoints that answer correctly are unaffected.
|
|
173
|
+
- **BREAKING. A refused broadcast reports `timeout`, not `failed`, on EVM too.**
|
|
174
|
+
The same check on the EVM side already refused an endpoint that acknowledged a
|
|
175
|
+
broadcast with the wrong identifier, but reported the result as `failed` — the
|
|
176
|
+
one status a caller reads as "that never went out, send it again". The
|
|
177
|
+
endpoint did acknowledge it, so those bytes may be on the network: a retry can
|
|
178
|
+
then take a fresh position and both transactions land. It now reports
|
|
179
|
+
`timeout`, meaning the outcome is unknown, and the error still names both
|
|
180
|
+
identifiers. The transaction continues to report the identifier it signed, and
|
|
181
|
+
awaiting its submission now fails with that same explanation rather than
|
|
182
|
+
reporting that it was never sent.
|
|
183
|
+
- **An RPC URL missing its scheme was handed back with one attached.** Provider
|
|
184
|
+
dashboards present an endpoint host-first with the key in the path and no
|
|
185
|
+
scheme. Pasting that produced an error that repeated the whole value with
|
|
186
|
+
`https://` prefixed — a ready-to-use secret URL, in a message that can reach a
|
|
187
|
+
log file or a crash reporter. Both refusals for an unparseable RPC URL now say
|
|
188
|
+
what is wrong without repeating any of it. A rejected scheme and a blocked
|
|
189
|
+
host are still named, since those are not the caller's secret.
|
|
190
|
+
- **A value rejected as a malformed number is no longer shown in full.** This
|
|
191
|
+
completes the sweep 2.3.8 began, which was scoped to address fields and noted
|
|
192
|
+
that amounts and gas prices were still shown whole — that note no longer
|
|
193
|
+
applies. A private key or recovery phrase pasted into an amount, a gas price or
|
|
194
|
+
a gas limit used to appear in the error in its entirety. Such a value is now
|
|
195
|
+
shortened. It is shortened rather than removed because for a number the
|
|
196
|
+
rejected text is the diagnosis: a thousands separator, a stray decimal point or
|
|
197
|
+
an exponent is still perfectly legible.
|
|
198
|
+
- **The last two address errors stopped repeating their value.** A token
|
|
199
|
+
contract address that is not a valid address, and a network's multicall
|
|
200
|
+
address, were the two sites the 2.3.8 sweep left. Adding a custom token by
|
|
201
|
+
contract address is something an end user does by pasting, and the same slip
|
|
202
|
+
applies. Both now name the field instead of showing its contents.
|
|
203
|
+
|
|
204
|
+
### Fixed
|
|
205
|
+
- **Malformed typed data, measured.** Of twenty-four malformed payload shapes,
|
|
206
|
+
twenty-two are now refused with this library's own error code and none escapes
|
|
207
|
+
as an untyped error. The remaining two — a domain given as an array or a
|
|
208
|
+
number — are signed deliberately, because every other implementation signs
|
|
209
|
+
them identically and refusing them would break real callers.
|
|
210
|
+
- **BREAKING. A field whose declared type is not a type at all is now refused.**
|
|
211
|
+
A type is a name followed by zero or more array suffixes and nothing else.
|
|
212
|
+
Anything trailing the suffix — `uint256[]extra`, `uint256[2]junk` — or an
|
|
213
|
+
unclosed bracket such as `uint256[` was accepted, and produced a signature no
|
|
214
|
+
counterparty could check. Such a payload now reports
|
|
215
|
+
`PARAMETER_ERROR` naming the type, which is what other implementations already
|
|
216
|
+
do. Every valid type signs exactly as before, including nested and fixed-size
|
|
217
|
+
arrays such as `uint256[2][3]`, a zero-padded length such as `uint256[01]`, and
|
|
218
|
+
the shorthands `uint` and `int`.
|
|
219
|
+
- **An amount too large to exist is refused when you set it, not when you
|
|
220
|
+
sign.** Every numeric field of an EVM transaction holds a 256-bit value, and
|
|
221
|
+
a larger one was accepted and carried all the way to signing, which then
|
|
222
|
+
failed with a message naming neither the field nor the reason. Such a value
|
|
223
|
+
is now refused immediately and by name. The largest legal value is still
|
|
224
|
+
accepted, and nothing was ever silently altered.
|
|
225
|
+
- **A wallet's address list can no longer be rewritten in place.** It is
|
|
226
|
+
declared read-only and, unlike an account's wallet list, nothing enforced
|
|
227
|
+
that: assigning an entry, truncating it, pushing to it or deleting from it
|
|
228
|
+
were all accepted, and the next ordinary save wrote the result through. Both
|
|
229
|
+
lists now refuse those, and both also refuse being frozen or having their
|
|
230
|
+
prototype changed — neither of which writes anything itself, but both of which
|
|
231
|
+
make a later legitimate change fail from somewhere the caller cannot connect
|
|
232
|
+
to what they did. Reading, iterating and the collections' own methods are
|
|
233
|
+
unchanged.
|
|
234
|
+
- **More invisible characters are removed from names.** Free-text names — a
|
|
235
|
+
wallet's display name, an asset or network name — are cleaned of characters
|
|
236
|
+
that carry no glyph before anything else looks at them, so nothing can hide
|
|
237
|
+
inside a word. Five that were missing are now included, among them the
|
|
238
|
+
directional isolates, which belong to the same family as the overrides that
|
|
239
|
+
were already covered. Emoji are untouched.
|
|
240
|
+
- **A malformed Solana address is reported the same way wherever it is given.**
|
|
241
|
+
Depending on which call it came through, the same mistake produced either this
|
|
242
|
+
library's own `PARAMETER_ERROR` or a bare decoding error with no code — and in
|
|
243
|
+
one case nothing at all until much later, from a place that could no longer
|
|
244
|
+
say which address was at fault. Every one of them now reports
|
|
245
|
+
`PARAMETER_ERROR` and names the field. Nothing that built before is refused.
|
|
246
|
+
- **A typed-data payload that declares `EIP712Domain` as null no longer throws a
|
|
247
|
+
raw error.** Naming that key as a field's type, with the key itself set to
|
|
248
|
+
`null` or `undefined`, produced a `TypeError` from inside the encoder instead
|
|
249
|
+
of a `PARAMETER_ERROR`. Leaving the key present but empty while nothing
|
|
250
|
+
references it is a normal payload and still signs, byte for byte as before.
|
|
251
|
+
- **A Solana fee payer naming the sending account is no longer refused.**
|
|
252
|
+
Signing rejected any fee payer that was not the exact same string as the
|
|
253
|
+
sender, so supplying the sender's own key as a public-key object or as raw key
|
|
254
|
+
bytes — both of which build correctly for external signing — was turned away,
|
|
255
|
+
as was `null`. All three now sign, and describe a transaction identical to
|
|
256
|
+
leaving the field out. A fee payer naming a genuinely different account is
|
|
257
|
+
still refused, and is still available for external signing.
|
|
258
|
+
|
|
259
|
+
## [2.3.8] — 2026-08-09
|
|
260
|
+
|
|
261
|
+
Typed-data signing (`signTypedData`) is stricter about payloads that were
|
|
262
|
+
producing signatures no counterparty can verify. If you sign typed data, read
|
|
263
|
+
the three breaking items below. Nothing else here changes what is accepted.
|
|
264
|
+
|
|
265
|
+
### Security
|
|
266
|
+
- **Four address errors no longer repeat the value they rejected.** A
|
|
267
|
+
destination that is not a valid address, a malformed Solana recipient, a
|
|
268
|
+
Solana fee payer that differs from the sender, and a transaction whose sender
|
|
269
|
+
does not match the address signing it: each used to include the value it had
|
|
270
|
+
just refused. Pasting a private key into a destination field is an easy slip,
|
|
271
|
+
and the message could then reach anything that records errors — a log file, a
|
|
272
|
+
crash reporter, a support ticket. Each now names the field that was wrong
|
|
273
|
+
instead of showing its contents. An address that IS valid is still shown, so
|
|
274
|
+
a checksum typo remains easy to spot. Note this covers address fields: a
|
|
275
|
+
value rejected as a malformed *number* — an amount, a gas price — is still
|
|
276
|
+
shown in full.
|
|
277
|
+
- **BREAKING. A domain carrying an unrecognised field is now refused.** Only
|
|
278
|
+
`name`, `version`, `chainId`, `verifyingContract` and `salt` are part of an
|
|
279
|
+
EIP-712 domain. A misspelled or mis-cased field — `chainID` with a capital D,
|
|
280
|
+
or keys lower-cased by a JSON round-trip — was previously dropped in silence,
|
|
281
|
+
and the signature was then bound to no chain and no contract at all. Such a
|
|
282
|
+
payload is refused rather than signed. A domain with no fields still signs,
|
|
283
|
+
unchanged.
|
|
284
|
+
|
|
285
|
+
### Fixed
|
|
286
|
+
- **BREAKING. A field typed `uint` or `int` now signs as `uint256`/`int256`.**
|
|
287
|
+
These are the same types under a shorter name and every other implementation
|
|
288
|
+
treats them that way; this one did not, so the signature verified as a
|
|
289
|
+
different address than the actual signer, with no error anywhere. Any
|
|
290
|
+
signature previously produced for such a field changes — the new one is the
|
|
291
|
+
one a counterparty accepts. Fields with an explicit width are unaffected.
|
|
292
|
+
- **BREAKING. A fixed-size array field must hold exactly its declared number of
|
|
293
|
+
values.** `uint256[2]` given three values used to sign something no verifier
|
|
294
|
+
would evaluate; it is now refused.
|
|
295
|
+
- **Malformed typed data reports this library's error.** Bad values in `message`
|
|
296
|
+
— a missing or `null` nested struct, a number out of range for its declared
|
|
297
|
+
width, a malformed address or byte length, an unreadable field — now report
|
|
298
|
+
`PARAMETER_ERROR` naming the field, instead of escaping as a plain error with
|
|
299
|
+
no code. Payloads that signed before still sign, and produce the same
|
|
300
|
+
signature; only the error changed.
|
|
301
|
+
- **A malformed recipient on a native SOL transfer reports this library's
|
|
302
|
+
error.** Such an address was always rejected, but by an underlying library, as
|
|
303
|
+
a plain error carrying no code — so it could not be handled alongside every
|
|
304
|
+
other failure. It now reports `PARAMETER_ERROR` naming the field, and says
|
|
305
|
+
whether the address is not valid base58 or is the wrong length. Every address
|
|
306
|
+
that built before still builds; only the error changed.
|
|
307
|
+
|
|
308
|
+
## [2.3.7] — 2026-08-08
|
|
309
|
+
|
|
310
|
+
A security and correctness release. Upgrade if you display names your users
|
|
311
|
+
typed, import Solana keys, send Solana transactions, or rely on the password
|
|
312
|
+
strength report.
|
|
313
|
+
|
|
314
|
+
### Security
|
|
315
|
+
- **A hidden character could smuggle a dangerous link into a display name.**
|
|
316
|
+
Names, tags and other free-form text are cleaned before storage, and that
|
|
317
|
+
cleaning removed dangerous prefixes like `javascript:` before it removed
|
|
318
|
+
zero-width and control characters. A single invisible character placed inside
|
|
319
|
+
such a prefix therefore survived the check and was then tidied away, handing
|
|
320
|
+
back exactly the thing that was supposed to have been removed. Anything
|
|
321
|
+
putting a stored name on a page received a working link. All affected
|
|
322
|
+
prefixes and every invisible-character class are covered.
|
|
323
|
+
- **The password strength report described a different password than the one in
|
|
324
|
+
use.** Passwords are normalised before the key is derived, but the strength
|
|
325
|
+
check read the text exactly as typed. A password written in decomposed form —
|
|
326
|
+
ordinary for some keyboards and languages — was reported as longer and
|
|
327
|
+
stronger than the secret actually protecting the wallet, and could pass a
|
|
328
|
+
minimum-length rule the real one fails. The report now measures what is
|
|
329
|
+
actually used. Nothing about existing wallets or how keys are derived changes,
|
|
330
|
+
and the warning about passwords that shift form between machines is unchanged.
|
|
331
|
+
|
|
332
|
+
### Fixed
|
|
333
|
+
- **A malformed Solana private key is now refused in this library's terms.**
|
|
334
|
+
Such a key was always rejected, but by an underlying library, as a plain error
|
|
335
|
+
carrying no error code — so it could not be handled alongside every other
|
|
336
|
+
failure. It now reports `INVALID_PRIVATE_KEY`, matching what the EVM side
|
|
337
|
+
already did.
|
|
338
|
+
- **A rejected logging change no longer takes effect anyway.** Changing sinks or
|
|
339
|
+
the log level while the workspace is locked correctly fails — but the new
|
|
340
|
+
settings were applied before that failure, leaving the previous sinks closed
|
|
341
|
+
and, for file logging, records being written under a configuration you were
|
|
342
|
+
told had been rejected. Nothing changes now unless the change succeeds.
|
|
343
|
+
- **Closing RPC clients now closes all of them.** Solana connections were left
|
|
344
|
+
open, each holding a network connection and two timers, once per endpoint used
|
|
345
|
+
for the lifetime of the process.
|
|
346
|
+
- **An expired Solana transaction now says so.** Previously it was polled for the
|
|
347
|
+
full two minutes and then reported as a timeout — the same answer given for a
|
|
348
|
+
slow node, though the two call for opposite responses. A timeout means the
|
|
349
|
+
outcome is unknown and sending another transaction risks paying twice; an
|
|
350
|
+
expired one can never be accepted, so replacing it is safe. It now ends as
|
|
351
|
+
dropped, saying the blockhash expired.
|
|
352
|
+
- **Wallet lookup by tag is no longer case-sensitive**, matching how the same
|
|
353
|
+
method already looked up accounts and addresses. Tags keep the capitalisation
|
|
354
|
+
you gave them.
|
|
355
|
+
|
|
356
|
+
### Added
|
|
357
|
+
- **`workspace.damagedAccountSlugs`** lists account records that are present but
|
|
358
|
+
could not be opened. Such a record is deliberately kept rather than discarded,
|
|
359
|
+
and creating an account with the same name will not overwrite it — but until
|
|
360
|
+
now nothing reported that it existed, and the only visible sign was new
|
|
361
|
+
accounts receiving unexpected names. The list is read-only: removing such a
|
|
362
|
+
record is left to you, because it may still hold a recoverable recovery
|
|
363
|
+
phrase.
|
|
364
|
+
|
|
365
|
+
## [2.3.6] — 2026-08-08
|
|
366
|
+
|
|
367
|
+
A correctness release for anyone who sends transactions. Several ways a
|
|
368
|
+
transaction could do something other than what you asked, or be reported as
|
|
369
|
+
something it was not, all of them silent.
|
|
370
|
+
|
|
371
|
+
### Security
|
|
372
|
+
- **A redirect could carry your RPC request to a host you never configured.**
|
|
373
|
+
The RPC address you set was checked, but a redirect from that address was
|
|
374
|
+
followed automatically — and the kinds that preserve the request body would
|
|
375
|
+
re-send the whole call somewhere else, including addresses the check exists to
|
|
376
|
+
block. Redirects are no longer followed, and one is now reported as a
|
|
377
|
+
redirect, naming where it was being sent, rather than as an unreachable node.
|
|
378
|
+
- **The transaction being watched was chosen by the endpoint.** After a
|
|
379
|
+
broadcast, the identifier the endpoint replied with was taken at face value
|
|
380
|
+
and everything afterwards followed it — so an endpoint could hand back an
|
|
381
|
+
identifier of its own and report a result for a transaction you never sent,
|
|
382
|
+
while yours went unwatched. That reply is now checked against the transaction
|
|
383
|
+
that was actually signed. An endpoint that does not acknowledge a broadcast
|
|
384
|
+
still reports no identifier, exactly as before.
|
|
385
|
+
(Scope correction, added in 2.3.9: this covered EVM sends only. The wording
|
|
386
|
+
above did not say so, and the Solana lane kept taking the reply at face value
|
|
387
|
+
until 2.3.9.)
|
|
388
|
+
- **A mistyped token contract address was silently corrected.** The same
|
|
389
|
+
checksum repair that was fixed for transaction destinations in 2.3.5 also
|
|
390
|
+
applied where you supply a token's contract address. It is now refused.
|
|
391
|
+
Addresses given entirely in lower or upper case are unaffected.
|
|
392
|
+
|
|
393
|
+
### Fixed
|
|
394
|
+
- **Call data with an odd number of digits changed the function being called.**
|
|
395
|
+
It was padded on the left, which shifts every byte — turning a token transfer
|
|
396
|
+
into a call to something else, with the arguments shifted too. The
|
|
397
|
+
transaction signed, sent and mined perfectly well; it simply did something
|
|
398
|
+
else. Such call data is now refused, because what was intended cannot be
|
|
399
|
+
known.
|
|
400
|
+
- **An automatic nonce could be handed out more than once.** The nonce was
|
|
401
|
+
requested from the node every time and nothing recorded what had already been
|
|
402
|
+
used, so transactions prepared before the node caught up all received the same
|
|
403
|
+
one. Sending three transfers in a row let one arrive while the rest could
|
|
404
|
+
never be mined, and all three reported as sent. A nonce you supply yourself is
|
|
405
|
+
still honoured exactly as given, so replacing an in-flight transaction with a
|
|
406
|
+
higher fee works as before.
|
|
407
|
+
- **A mined transaction could be reported as a failed send.** One unreadable
|
|
408
|
+
field in an otherwise successful receipt — the gas figures — was enough to
|
|
409
|
+
report the whole send as failed and to discard the receipt proving the funds
|
|
410
|
+
moved. Those figures are now read tolerantly and simply omitted when they
|
|
411
|
+
cannot be read.
|
|
412
|
+
- **An address will not sign for one chain and send to another.** An address and
|
|
413
|
+
the network it is pointed at could disagree about which chain they belong to;
|
|
414
|
+
signing now refuses rather than producing a signature for one chain and
|
|
415
|
+
handing it to another.
|
|
416
|
+
- **A signed transaction's fields can no longer be changed.** Editing a field
|
|
417
|
+
after signing was accepted and reported back, while the bytes that would be
|
|
418
|
+
sent still carried the original values. This covers both EVM and Solana
|
|
419
|
+
transactions. Build a new transaction to change something.
|
|
420
|
+
- **A wallet list handed to you can no longer be modified in place.** Assigning
|
|
421
|
+
to it changed the account's own list, and the next save wrote that through.
|
|
422
|
+
- **A dropped network no longer comes back.** Saving a network after removing it
|
|
423
|
+
added it again, in memory and on disk.
|
|
424
|
+
- **A blank address filter no longer matches the first address**, and passing
|
|
425
|
+
something that is not an account now reports a parameter error rather than an
|
|
426
|
+
unrelated type error.
|
|
427
|
+
- **Creating an account no longer overwrites one another handle just created.**
|
|
428
|
+
Two handles open on the same workspace could each create an account with the
|
|
429
|
+
same name, leaving one record holding the second recovery phrase and
|
|
430
|
+
destroying the first.
|
|
431
|
+
|
|
432
|
+
## [2.3.5] — 2026-08-07
|
|
433
|
+
|
|
434
|
+
A security release. Upgrade if you send EVM transactions, or if you keep more
|
|
435
|
+
than one account in a workspace.
|
|
436
|
+
|
|
437
|
+
### Security
|
|
438
|
+
- **A mistyped destination address was silently corrected.** An EVM address
|
|
439
|
+
carries a checksum whose whole purpose is to catch a mistyped character. When
|
|
440
|
+
a transaction destination arrived with a checksum that did not match, it was
|
|
441
|
+
quietly rewritten into a valid-looking address rather than refused — so a
|
|
442
|
+
single wrong character could send funds to an address you never typed, with no
|
|
443
|
+
warning. Such a destination is now rejected. Addresses given entirely in lower
|
|
444
|
+
or upper case carry no checksum to check and are unaffected, and address
|
|
445
|
+
lookup stays as forgiving as before.
|
|
446
|
+
- **An account could overwrite another account's stored secrets.** On macOS and
|
|
447
|
+
Windows, where file names ignore case, creating an account could write over a
|
|
448
|
+
stored account whose name differed only in capitalisation — destroying the
|
|
449
|
+
sealed recovery phrase it held. Names are now checked the way the file system
|
|
450
|
+
actually compares them.
|
|
451
|
+
- **Two addresses reached cloud metadata services.** The RPC URL check missed one
|
|
452
|
+
IPv6 spelling of the metadata address, and the well-known metadata host names
|
|
453
|
+
entirely.
|
|
454
|
+
- **Sealed material no longer appears when an object is logged.** The stored
|
|
455
|
+
recovery phrase and stored keys were ordinary visible properties, so they
|
|
456
|
+
surfaced in `console.log` output and in anything that walks an object's
|
|
457
|
+
properties.
|
|
458
|
+
- **A password policy option set to `undefined` no longer disables it.** Building
|
|
459
|
+
an options object from optional values — the ordinary way to forward
|
|
460
|
+
settings — removed the minimum-length rule entirely, so short passwords stopped
|
|
461
|
+
being reported.
|
|
462
|
+
|
|
463
|
+
### Fixed
|
|
464
|
+
- **`confirmed` no longer fires for a reverted transaction.** Subscribers were
|
|
465
|
+
told a transaction had confirmed, receiving a receipt whose own success flag
|
|
466
|
+
was false, and only afterwards told it failed.
|
|
467
|
+
- **A stray file no longer blocks removing a network.** A single duplicate left
|
|
468
|
+
in the storage folder — the kind a file manager creates — made network removal
|
|
469
|
+
refuse permanently.
|
|
470
|
+
- **A log file prefix can no longer choose the directory.** A prefix containing
|
|
471
|
+
path separators wrote the log outside the folder it was given. Sizes and file
|
|
472
|
+
counts are validated too.
|
|
473
|
+
- **Aborting a transaction cannot leave a stale finish behind.** Internal
|
|
474
|
+
finishing can no longer be triggered while a transaction is still in flight.
|
|
475
|
+
- **Key material is cleared in three more places** where an unusual failure —
|
|
476
|
+
one bad address, an interrupted derivation, a rolled-back add — previously left
|
|
477
|
+
it in memory.
|
|
478
|
+
|
|
479
|
+
## [2.3.4] — 2026-08-07
|
|
480
|
+
|
|
481
|
+
A security release. Upgrade if you hold keys in a long-running process, or if
|
|
482
|
+
you log anything from a Solana wallet.
|
|
483
|
+
|
|
484
|
+
### Security
|
|
485
|
+
- **Key material could survive `lock()`.** Two separate cases. Records sealed by
|
|
486
|
+
the optional faster storage format left one readable copy of each record's key
|
|
487
|
+
in memory after the workspace was locked — and because that format seals each
|
|
488
|
+
secret the same way, such a copy is the secret itself. Separately, part of the
|
|
489
|
+
password-derivation scratch space was not being cleared, leaving
|
|
490
|
+
password-derived bytes resident. Both are now cleared, and both are covered by
|
|
491
|
+
checks that can actually observe the memory in question.
|
|
492
|
+
- **`lock()` could return while an account stayed unlocked.** An `unlock` that
|
|
493
|
+
landed at the same moment as a `lock` could complete inside it: the workspace
|
|
494
|
+
reported locked while the account still held its mnemonic and could sign, and
|
|
495
|
+
no later `lock()` could reach it. Locking is now closed to that race.
|
|
496
|
+
|
|
497
|
+
### Fixed
|
|
498
|
+
- **Signing typed data with a `bytes` field.** A `Uint8Array` or `Buffer` was
|
|
499
|
+
hashed as text rather than as its bytes, so the signature did not match what
|
|
500
|
+
any other implementation computes — and two containers holding the same bytes
|
|
501
|
+
gave two different results. Passing a `0x` string was, and remains, correct.
|
|
502
|
+
Text that is not `0x`-hex is now rejected rather than silently hashed a
|
|
503
|
+
second, incompatible way.
|
|
504
|
+
- **Malformed typed data now raises the library's own error.** Several shapes —
|
|
505
|
+
including a `primaryType` naming a built-in type, and a struct that contains
|
|
506
|
+
itself — escaped as a raw `TypeError` with no error code. (This entry
|
|
507
|
+
originally gave a count. It was not reproducible against the released build
|
|
508
|
+
and has been replaced by the property it was trying to describe; the current
|
|
509
|
+
coverage is stated under 2.3.9.)
|
|
510
|
+
- **Solana signatures and transaction ids are no longer removed from logs.** The
|
|
511
|
+
redactor treated any long base58 value as a secret, which is exactly the shape
|
|
512
|
+
of a signature, so the one value worth correlating on disappeared silently.
|
|
513
|
+
Redaction by field NAME was broadened at the same time, so tokens, passcodes
|
|
514
|
+
and recovery keys that previously slipped through are now covered.
|
|
515
|
+
- **Logging a non-string no longer throws.** `logger.info(x)` where `x` was
|
|
516
|
+
undefined, a number or an object took down the caller.
|
|
517
|
+
- **Adopting an account into another workspace** now refuses when that
|
|
518
|
+
workspace's password would not open it, instead of writing a record that can
|
|
519
|
+
never be opened again.
|
|
520
|
+
- **Adding an account no longer loses concurrent work.** Wallets derived while
|
|
521
|
+
the account was being added were kept in memory but never written.
|
|
522
|
+
|
|
523
|
+
## [2.3.3] — 2026-08-07
|
|
524
|
+
|
|
525
|
+
A security fix for typed-data signing. Upgrade if you use `signTypedData`.
|
|
526
|
+
|
|
527
|
+
### Security
|
|
528
|
+
- **A signature could be produced for a value other than the one you were shown.**
|
|
529
|
+
When a typed-data payload declared a custom type whose name collides with one of
|
|
530
|
+
EIP-712's own built-in type names, a field of that type was not bound by the
|
|
531
|
+
signature: every value produced the same result, and an independent verifier
|
|
532
|
+
read the field as a fixed value nobody chose. The same collision could also
|
|
533
|
+
detach a signature from the chain and contract it was meant for. Because the
|
|
534
|
+
payload is normally supplied by the site requesting the signature, this did not
|
|
535
|
+
require anything unusual from the wallet holder. Such payloads are now rejected
|
|
536
|
+
with `PARAMETER_ERROR`, which is also what other EIP-712 implementations do, so
|
|
537
|
+
no payload that they accept is affected.
|
|
538
|
+
|
|
539
|
+
### Fixed
|
|
540
|
+
- **A retired wallet index could come back.** Dropping a wallet records its index
|
|
541
|
+
so it is never derived again. If another account setting was changed at the same
|
|
542
|
+
moment, that record could be lost, and the next `deriveWallets()` returned the
|
|
543
|
+
same address and private key as the wallet that was dropped.
|
|
544
|
+
- **`sliceWallets` could empty an account.** It kept no minimum, so on an account
|
|
545
|
+
whose first wallet had already been dropped it could remove every remaining
|
|
546
|
+
wallet, leaving an account with no keys and reporting no error. It now refuses,
|
|
547
|
+
as `wallet.drop()` already did.
|
|
548
|
+
- **Aborting a transaction no longer reports a confirmation afterwards.**
|
|
549
|
+
`abort()` could still fire `confirmed` listeners for a receipt that arrived
|
|
550
|
+
after the abort, having already reported the failure. The receipt itself is
|
|
551
|
+
still kept on the tracker — if the transaction did land, you can still see it —
|
|
552
|
+
but it is no longer announced as a confirmation, and the abort remains the
|
|
553
|
+
reported cause.
|
|
554
|
+
- **Secrets are cleared in two cases where they were kept.** A wallet drop, or a
|
|
555
|
+
password reset, that failed because the workspace was locked at that moment
|
|
556
|
+
could leave key material in memory that a later `lock()` would not reach.
|
|
557
|
+
|
|
558
|
+
### Changed
|
|
559
|
+
- ⚠️ **`whenFinalized()` now rejects instead of never settling.** Transaction
|
|
560
|
+
tracking ends at first inclusion, so on EVM — and on Solana whenever the node
|
|
561
|
+
reports `confirmed` first — this promise previously neither resolved nor
|
|
562
|
+
rejected. It now rejects with `UNSUPPORTED_OP`. A Solana node that reports
|
|
563
|
+
finality directly still resolves as before.
|
|
564
|
+
|
|
565
|
+
**Migration.** `void tracker.whenFinalized().then(...)` used to do nothing,
|
|
566
|
+
because the promise never settled; it is now an unhandled rejection and will
|
|
567
|
+
terminate a Node process. Attach a `.catch()`, or await it. Awaiting it
|
|
568
|
+
alongside `whenConfirmed()` is unaffected.
|
|
569
|
+
- **A `types.EIP712Domain` list that disagrees with your domain is now rejected**
|
|
570
|
+
rather than ignored. The domain separator has always been computed from the
|
|
571
|
+
domain object itself, so a mismatched list previously produced a signature that
|
|
572
|
+
your own verifier would reject, with nothing to point at the cause. An empty or
|
|
573
|
+
absent list still works, so the JSON-RPC shape is unaffected.
|
|
574
|
+
|
|
575
|
+
- ⚠️ **Documented late — this shipped in 2.3.3 without a note.** `record.unlock(password)`
|
|
576
|
+
on a record that is already open now re-checks the password instead of returning
|
|
577
|
+
success. Records arrive already open in the common case, so a caller using
|
|
578
|
+
`record.unlock(pw)` as a password check was previously told yes for **every**
|
|
579
|
+
string, including a wrong one. It also refuses once the workspace has been locked
|
|
580
|
+
or the record dropped, where a retained handle used to keep answering password
|
|
581
|
+
guesses indefinitely.
|
|
582
|
+
|
|
583
|
+
**Migration.** If you relied on `unlock()` succeeding for an already-open record,
|
|
584
|
+
pass the real password. Reading `.value` is unaffected — that is a snapshot you
|
|
585
|
+
already hold, and it is deliberately not gated.
|
|
586
|
+
|
|
587
|
+
## [2.3.2]
|
|
588
|
+
|
|
589
|
+
Not released.
|
|
590
|
+
|
|
591
|
+
## [2.3.1] — 2026-08-07
|
|
592
|
+
|
|
593
|
+
A types-only fix. Six types that appear in the public API could not be imported,
|
|
594
|
+
so TypeScript users could call a method but not write down what it took or
|
|
595
|
+
returned. Nothing changed at runtime, and JavaScript users are unaffected.
|
|
596
|
+
|
|
597
|
+
### Fixed
|
|
598
|
+
- `OpenOptions` (the options bag for `Workspace.open()`), `PasswordCheckContext`,
|
|
599
|
+
`NetworkConfig`, `AbiItem`, `SplTokenArgs` and `SplTokenAccountSet` are now
|
|
600
|
+
exported and can be imported by name.
|
|
601
|
+
|
|
602
|
+
## [2.3.0] — 2026-08-06
|
|
603
|
+
|
|
604
|
+
Unlocking is much faster. Drop-in from 2.2.x; wallets from 1.x need a manual step first.
|
|
605
|
+
|
|
606
|
+
### Added
|
|
607
|
+
- An optional storage format that keeps unlocking fast however many addresses an account holds.
|
|
608
|
+
|
|
609
|
+
### Changed
|
|
610
|
+
- Unlocking is roughly 25x faster. In a browser, allow `'wasm-unsafe-eval'` in your Content-Security-Policy, or it quietly falls back to a much slower path.
|
|
611
|
+
|
|
612
|
+
### Removed
|
|
613
|
+
- Wallets created by 1.x can no longer be opened — export their secrets with 2.2.x before upgrading.
|
|
614
|
+
|
|
5
615
|
## [2.2.1] — 2026-07-29
|
|
6
616
|
|
|
7
|
-
Documentation and packaging only
|
|
617
|
+
Documentation and packaging only. `dist/` is byte-identical to 2.2.0 — no source changed, so the published build was deliberately left exactly as built and verified for that release.
|
|
8
618
|
|
|
9
619
|
### Changed
|
|
10
|
-
- The runnable examples
|
|
11
|
-
-
|
|
620
|
+
- **The runnable examples ship as `examples/` instead of `tests/`.** They are the only human-readable code in the published package, and `tests/` reads as internal scaffolding nobody is invited to open. The `files` allowlist follows. Consumers who referenced `wative-core/tests/...` by path must update; nothing importable from the package entry changed.
|
|
621
|
+
- **README** gains npm / CI / node / license badges, an ASCII diagram of the containment model (`Workspace > Account > Wallet > Address`), and a section distinguishing the two account kinds. The HD/PK difference is not in the nesting but in what a `Wallet` *means* — a derivation slot holding both an EVM and a Solana key under HD, versus a single imported key on one chain under PK. That was previously described in prose only, and it is the thing new users get wrong first.
|
|
12
622
|
|
|
13
623
|
### Fixed
|
|
14
|
-
-
|
|
624
|
+
- **`11-subpath-imports` example** imported the chain artifacts by relative path into `dist/` rather than by subpath, despite its own header documenting the subpath form. It resolved inside the repo and nowhere else, so it passed in-repo while being broken for every real installation. Caught by the new published-package CI job before release.
|
|
625
|
+
|
|
626
|
+
### Internal
|
|
627
|
+
- `.github/workflows/ci.yml`: runs the examples against this build on Node 22.12 and 24, and separately installs `wative-core` from the registry into a clean directory and runs the same files against it — exercising the real tarball's `files` list, `exports` map and dependency resolution. Runs weekly as well, since a published package sits on a moving dependency graph.
|
|
628
|
+
- The private development repo is now guarded against reaching npm (`private`, a `prepublishOnly` refusal, and a `publishConfig` registry pointing nowhere). It shares a package name with the public repo, so a stray publish there would have succeeded and replaced the package.
|
|
15
629
|
|
|
16
630
|
## [2.2.0] — 2026-07-28
|
|
17
631
|
|
|
18
|
-
|
|
632
|
+
> **The browser entry is here.** The main entry now resolves for a browser with **zero** unresolvable Node builtins, and ships an IndexedDB storage backend. **No public API was removed** — existing Node imports keep working unchanged, so this remains a minor.
|
|
19
633
|
|
|
20
634
|
### Added
|
|
21
|
-
-
|
|
22
|
-
-
|
|
23
|
-
-
|
|
635
|
+
- **`IdbProvider` — IndexedDB storage backend.** Created via the async factory `IdbProvider.create(name, opts)`, which negotiates durable storage before returning. Exposes `durability` (`"persistent" | "best-effort"`) and a `quota` snapshot.
|
|
636
|
+
- **`ContainerProvider`** — the storage-agnostic base extracted from `HybridProvider`. A custom backend now implements only six primitives (`_exist`, `_listItems`, `_read`, `_write`, `_remove`, `_ensureDir`) and inherits record framing, the encrypted envelope and its identity AAD, the key layout and the container session. Previously `Provider` declared the record API abstract, so a custom provider had to reimplement encryption — which is why the shipped custom-provider example carried a hand-rolled XOR cipher and a warning to use a real one.
|
|
637
|
+
- **`exportContainer()` / `importContainer()`** on `ContainerProvider`. Both require an unlocked workspace and throw `WORKSPACE_LOCKED` otherwise — a precondition that was not stated when they shipped. Sealed records move verbatim — never decrypted, so no key is materialized and nothing is re-keyed. Because every backend shares one framing, a container round-trips IndexedDB ⇄ filesystem and opens with the same password. Import refuses a non-empty target without `{ overwrite: true }` and rejects entries outside the container layout.
|
|
638
|
+
- **`wative-core/node` subpath** — `HybridProvider`, `FileSink` and the default-workspace-path resolver.
|
|
639
|
+
- **`STORAGE_NOT_DURABLE`** error code.
|
|
24
640
|
|
|
25
641
|
### Changed
|
|
26
|
-
- `HybridProvider` and `FileSink`
|
|
642
|
+
- **The root entry is condition-split.** A browser bundler resolves the universal core; Node resolves an entry that still re-exports `HybridProvider` and `FileSink`, so `import { Workspace, HybridProvider } from "wative-core"` compiles and runs exactly as before. Those two re-exports are **deprecated** in favour of `wative-core/node` and will be removed in a future major. Condition order is significant — `browser` is declared before `node`, since resolvers take the first match.
|
|
643
|
+
- `tsconfig` `lib` gains `DOM`; the browser is now a real target.
|
|
644
|
+
|
|
645
|
+
### Browser notes
|
|
646
|
+
- Creating a **new** workspace in evictable storage is **refused** with `STORAGE_NOT_DURABLE` unless `{ acknowledgeEvictionRisk: true }` is passed. Opening an existing workspace is never blocked. Browser storage is evictable and this library holds private keys: Chrome and Firefox treat IndexedDB as best-effort unless `navigator.storage.persist()` is granted, Safari caps script-writable storage at roughly 7 days without user interaction, and private-mode windows may have no IndexedDB at all. Request persistence from a user gesture, and use `exportContainer()` for a durable backup.
|
|
647
|
+
- A **`Buffer` polyfill is still required** — `@solana/web3.js` and `@coral-xyz/anchor` read the global. Standard for any Solana dapp.
|
|
27
648
|
|
|
28
|
-
###
|
|
29
|
-
-
|
|
30
|
-
-
|
|
649
|
+
### Internal
|
|
650
|
+
- `scripts/browser-e2e.mjs` (`pnpm test:browser`) bundles the real dist through the `browser` condition, loads it into headless Chromium and drives a full lifecycle against the browser's own IndexedDB, asserting the same published BIP-44 vectors the Node suite pins. Resolving is not the same as running — a build can resolve cleanly and still fail at runtime, as a 2.1.0 regression showed. Wired into `prepublishOnly`.
|
|
651
|
+
- The published bundle is exercised under each platform to prove the export map lands where intended; asserting its shape is not the same as asserting a resolver's behaviour.
|
|
31
652
|
|
|
32
653
|
## [2.1.0] — 2026-07-28
|
|
33
654
|
|
|
@@ -114,7 +735,7 @@ Runs in the browser now. Existing Node code keeps working unchanged.
|
|
|
114
735
|
### Security
|
|
115
736
|
- **Envelope v2 — Argon2id + AAD-bound on-disk records.** New seals use Argon2id (RFC 9106; t=3, m=64 MiB, p=1) and bind every record's GCM tag to its on-disk identity. HybridProvider records bind to `wative:v2:record:<recordType>:<slug>`; per-account mnemonic ciphertexts bind to `wative:v2:account:<slug>:mnemonic`; per-address private-key ciphertexts bind to `wative:v2:account:<slug>:wallet:<id>:<vm>:pk`; recovery envelopes bind to `wative:v2:recovery:account:<slug>`. Closes the blob-swap class of attack where an attacker with workspace-dir write access (but not the password) could substitute one record's ciphertext for another's. Legacy v1 records (PBKDF2-SHA256, no AAD) continue to read; new writes are always v2.
|
|
116
737
|
- **Slug-collision suffix now CSPRNG.** Was `Math.random()`; now `crypto.randomInt`. Prevents predictable filenames in `accounts/<slug>-<suffix>.db`.
|
|
117
|
-
-
|
|
738
|
+
- **The derived seed is zeroed on lock.** `Account.lock()` now overwrites the BIP-39 seed bytes before releasing them. JS string immutability still prevents perfect zeroization of mnemonic + plaintext private keys (documented on `Workspace.lock()`).
|
|
118
739
|
- **`useTestKdfIterations` Jest auto-detect removed.** The `typeof globalThis.expect !== "undefined"` opt-in arm fired in any downstream consumer using Jest, not just our own test process. Now requires explicit `WATIVE_ALLOW_TEST_KDF=1` (or `NODE_ENV=test`). Internal-only function; not on the package surface.
|
|
119
740
|
|
|
120
741
|
## [2.0.1] — 2026-04-29
|