wative-core 2.4.1 → 2.4.3

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.
Files changed (53) hide show
  1. package/CHANGELOG.md +20 -0
  2. package/README.md +59 -55
  3. package/dist/artifacts/evm/index.cjs +1 -1
  4. package/dist/artifacts/evm/index.d.cts +2 -2
  5. package/dist/artifacts/evm/index.d.ts +2 -2
  6. package/dist/artifacts/evm/index.js +1 -1
  7. package/dist/artifacts/svm/index.cjs +1 -1
  8. package/dist/artifacts/svm/index.d.cts +3 -3
  9. package/dist/artifacts/svm/index.d.ts +3 -3
  10. package/dist/artifacts/svm/index.js +1 -1
  11. package/dist/{container-provider--npoeR2N.d.ts → container-provider-B8M0ylrK.d.ts} +1 -1
  12. package/dist/{container-provider-Ca_5z1uQ.d.cts → container-provider-YxyMpkhG.d.cts} +1 -1
  13. package/dist/{evm-EHpaW_sK.d.ts → evm-Bo3Dy492.d.ts} +1 -1
  14. package/dist/{evm-hSJJOVG3.d.cts → evm-DA6Sh6jv.d.cts} +1 -1
  15. package/dist/{file-sink-Bk5zYHw-.d.ts → file-sink-Be5iLbuT.d.ts} +2 -2
  16. package/dist/{file-sink-7ZAutH45.d.cts → file-sink-KCCpKHRj.d.cts} +2 -2
  17. package/dist/index.cjs +1 -1
  18. package/dist/index.d.cts +6 -6
  19. package/dist/index.d.ts +6 -6
  20. package/dist/index.js +1 -1
  21. package/dist/index.node.cjs +1 -1
  22. package/dist/index.node.d.cts +5 -5
  23. package/dist/index.node.d.ts +5 -5
  24. package/dist/index.node.js +1 -1
  25. package/dist/node/index.cjs +1 -1
  26. package/dist/node/index.d.cts +3 -3
  27. package/dist/node/index.d.ts +3 -3
  28. package/dist/node/index.js +1 -1
  29. package/dist/{svm-B5q8YaV9.d.ts → svm-B51NevS8.d.ts} +1 -1
  30. package/dist/{svm-CXFwmeMo.d.cts → svm-DswX6XRv.d.cts} +1 -1
  31. package/dist/{workspace-kq3QyAgm.d.cts → workspace-CBxKR80D.d.cts} +7 -43
  32. package/dist/{workspace-kq3QyAgm.d.ts → workspace-CBxKR80D.d.ts} +7 -43
  33. package/examples/01-quick-start.test.cjs +1 -2
  34. package/examples/02-hd-account.test.cjs +1 -2
  35. package/examples/03-pk-account.test.cjs +39 -1
  36. package/examples/04-network-management.test.cjs +1 -2
  37. package/examples/05-asset-management.test.cjs +1 -2
  38. package/examples/06-address-signing.test.cjs +1 -2
  39. package/examples/07-custom-provider.test.cjs +2 -8
  40. package/examples/08-persistence.test.cjs +1 -2
  41. package/examples/09-workspace-filter.test.cjs +1 -2
  42. package/examples/10-default-network.test.cjs +1 -3
  43. package/examples/11-subpath-imports.test.cjs +2 -6
  44. package/examples/12-logger.test.cjs +1 -3
  45. package/examples/13-workspace-config.test.cjs +1 -5
  46. package/examples/14-esm-runtime.test.mjs +0 -9
  47. package/examples/15-package-resolution.test.cjs +5 -83
  48. package/examples/16-known-answer-vectors.test.cjs +4 -22
  49. package/examples/17-locked-state.test.cjs +2 -23
  50. package/examples/18-envelope-v3.test.cjs +2 -21
  51. package/examples/19-simple-provider.test.cjs +1 -19
  52. package/examples/README.md +1 -1
  53. package/package.json +1 -1
package/CHANGELOG.md CHANGED
@@ -2,6 +2,26 @@
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.3] — 2026-08-11
6
+
7
+ Documentation only. No API or behaviour changes from 2.4.2.
8
+
9
+ ### Changed
10
+ - The README now leads with what a `Record` is and where records can live, and states plainly that one imported key controls both addresses in a PK wallet.
11
+
12
+ ## [2.4.2] — 2026-08-11
13
+
14
+ Importing a private key is easier, and one import now reaches both chains.
15
+
16
+ ### Changed
17
+ - **BREAKING.** A PK wallet now holds two addresses — the chain you imported for, plus one on the other chain derived from the same secret, so one key controls both. `addresses[0]` is unchanged; `addresses.length` is now 2. Existing stored wallets keep their single address.
18
+
19
+ ### Added
20
+ - `importPrivateKey(pk)` no longer needs a `vm` — the chain is read from the key — and accepts the JSON byte array `solana-keygen` writes.
21
+
22
+ ### Fixed
23
+ - An EVM private key outside the valid range is refused as `INVALID_PRIVATE_KEY` instead of surfacing as an untyped error.
24
+
5
25
  ## [2.4.1] — 2026-08-11
6
26
 
7
27
  One breaking item, and it is the kind worth reading before upgrading: a
package/README.md CHANGED
@@ -7,8 +7,9 @@
7
7
 
8
8
  Hyperscale crypto wallet management library — multi-workspace, multi-chain, originally designed for on-chain market makers.
9
9
 
10
- Runs in Node and in the browser. Wallets live in one encrypted container: on disk
11
- under Node, in IndexedDB in a browser, or in any storage you plug in yourself.
10
+ Runs in Node and in the browser environments. Everything a workspace persists — wallets, accounts, networks, assets — is encrypted `Record` layer, sealed by the library exclusively seen by creators.
11
+
12
+ Where those records live is yours to choose: files, IndexedDB, Docker, NSA, databases, CDN, RAM, etc. Any encryption algorithm, any format, any storage media.
12
13
 
13
14
  ## How it fits together
14
15
 
@@ -44,28 +45,55 @@ Account "Trading Desk" (HD) one mnemonic
44
45
  └── … account.deriveWallets(n)
45
46
  ```
46
47
 
47
- **PK** — imported keys. A `Wallet` is one key you handed in, on the one chain
48
- you imported it for. Wallets have no relationship to each other:
48
+ **PK** — imported keys. Each import allocates its own `Wallet`, and that wallet
49
+ gets an address on **both** chains: the key signs on its own curve, and the same
50
+ 32 secret bytes seed a keypair on the other one. The chain you imported for
51
+ comes first.
49
52
 
50
53
  ```
51
54
  Account "Cold Storage" (PK) no mnemonic
52
55
  ├── Wallet 0 the key passed to accounts.create()
53
- │ └── Address vm: "evm" 0x90F8bf6A…
54
- ├── Wallet 1 account.importPrivateKey(pk, "evm")
55
- │ └── Address vm: "evm" 0xFFcf8FDE…
56
- └── Wallet 2 account.importPrivateKey(pk, "svm")
57
- └── Address vm: "svm" 7WktogJEd… one chain per wallet
56
+ │ ├── Address vm: "evm" 0x90F8bf6A… the imported key
57
+ │ └── Address vm: "svm" HaWmh8svNQ… same secret, other curve
58
+ ├── Wallet 1 account.importPrivateKey(evmKey)
59
+ │ ├── Address vm: "evm" 0xFFcf8FDE…
60
+ │ └── Address vm: "svm" Fgdy5QRxtP…
61
+ └── Wallet 2 account.importPrivateKey(svmKey)
62
+ ├── Address vm: "svm" 3QVq8D876h… the imported key
63
+ └── Address vm: "evm" 0x1a740984… same secret, other curve
64
+ ```
65
+
66
+ A PK account can hold as many keys as you like, on any mix of chains — but it
67
+ refuses the same identity twice, on either chain.
68
+
69
+ > **`wallet.addresses` is a list.** It holds one address per `(vm, network)`
70
+ > pair and is free to carry several. Read it rather than assuming a position:
71
+ >
72
+ > ```ts
73
+ > const evm = wallet.addresses.find((a) => a.vm === "evm");
74
+ > ```
75
+
76
+ **Which chain a key is for is read off the key** — `vm` is optional:
77
+
78
+ ```ts
79
+ await acc.importPrivateKey(evmKey); // 64 hex chars -> evm
80
+ await acc.importPrivateKey(solanaKey); // base58 -> svm
81
+ await acc.importPrivateKey(readFileSync("id.json")); // solana-keygen -> svm
82
+ await acc.importPrivateKey(key, "evm"); // or say so explicitly
58
83
  ```
59
84
 
60
- A PK account can hold as many keys as you like, and they can be on different
61
- chains — but it refuses the same identity twice.
85
+ The formats do not overlap, so this is a decision rather than a guess. A value
86
+ that is neither is refused with `INVALID_PRIVATE_KEY` — including 32 bytes of
87
+ hex that is not a valid secp256k1 scalar, and a bare 32-byte base58 value,
88
+ which is indistinguishable from a Solana *public* key.
62
89
 
63
90
  | | HD | PK |
64
91
  | ---------------------- | ----------------------------- | ------------------------------- |
65
92
  | Created from | a BIP-39 mnemonic | one private key |
66
93
  | Grows with | `deriveWallets(n)` | `importPrivateKey(pk, vm)` |
67
94
  | A `Wallet` is | a derivation slot | one imported key |
68
- | Addresses per `Wallet` | **2** — one evm, one svm | **1** — the chain you imported |
95
+ | Addresses per `Wallet` | **2** — one evm, one svm | **2** — imported chain first |
96
+ | Those two addresses | independent keys, two paths | **one key, both curves** |
69
97
  | `dumpMnemonic()` | ✔ | throws `UNSUPPORTED_OP` |
70
98
  | `sliceWallets(n)` | ✔ | throws `UNSUPPORTED_OP` |
71
99
  | `importPrivateKey()` | throws `UNSUPPORTED_OP` | ✔ |
@@ -77,10 +105,10 @@ await hd.deriveWallets(5); // slots 1..5, alongside slot 0
77
105
  hd.wallets[0].addresses.find((a) => a.vm === "evm");
78
106
  hd.wallets[0].addresses.find((a) => a.vm === "svm");
79
107
 
80
- // PK — each import is its own wallet, on one chain
108
+ // PK — each import is its own wallet, with an address on both chains
81
109
  const pk = await ws.accounts.create("Cold Storage", password, privateKey);
82
- await pk.importPrivateKey(anotherEvmKey, "evm"); // -> a new Wallet
83
- await pk.importPrivateKey(aSolanaKey, "svm"); // -> another new Wallet
110
+ await pk.importPrivateKey(anotherEvmKey); // -> a new Wallet, vm inferred
111
+ await pk.importPrivateKey(aSolanaKey); // -> another new Wallet
84
112
  ```
85
113
 
86
114
  ## Install
@@ -150,7 +178,6 @@ await ws.lock();
150
178
  | Storage | encrypted files on disk | IndexedDB |
151
179
  | Where it lives | `Workspace.open({ path })`, or default | `IdbProvider.create(name)` |
152
180
  | Extra setup | none | `Buffer` polyfill |
153
- | Keys can vanish| no | yes — storage is evictable |
154
181
 
155
182
  `HybridProvider`, `HybridProviderV3` and `FileSink` are re-exported from the
156
183
  package root in Node.
@@ -233,17 +260,6 @@ produces the same keys.
233
260
  Nothing to install: it runs on a bundled WebAssembly build by default, with no
234
261
  native binary and no build step.
235
262
 
236
- ### Browser: grant `'wasm-unsafe-eval'`
237
-
238
- ```
239
- Content-Security-Policy: script-src 'self' 'wasm-unsafe-eval'
240
- ```
241
-
242
- Optional. Without it nothing breaks and nothing throws — key derivation falls
243
- back to a slower implementation that produces identical results, so unlocking a
244
- wallet with a dozen addresses takes tens of seconds instead of about one. Grant
245
- it if that matters to you. `argon2BackendInfo()` reports which one ran.
246
-
247
263
  ### One derivation per container: `HybridProviderV3` (Node only)
248
264
 
249
265
  ```ts
@@ -262,24 +278,13 @@ It is the Node default where `@node-rs/argon2` is installed, and Node-only: that
262
278
  package has no browser build. Construct it yourself only to pick a location, or
263
279
  to be sure you get it rather than the fallback.
264
280
 
265
- Two things to plan for:
266
-
267
- - **An existing workspace does not get faster on its own.** Secrets written
268
- before the switch keep their own keys, so they keep costing one derivation
269
- each. `account.resetPassword(old, new)` rewrites them.
270
- - ⚠️ **Older versions of this library cannot open a V3 workspace**, and there is
271
- no way back. Once a workspace has been opened with V3, keep using a version
272
- that supports it.
273
-
274
281
  ### Checking which implementation ran
275
282
 
276
283
  ```ts
277
284
  import { argon2BackendInfo } from "wative-core";
278
285
 
279
286
  argon2BackendInfo();
280
- // { backend: "wasm", wasm: true, overrides: [] }
281
- // { backend: "noble", wasm: false, reason: "...", overrides: [] } ← degraded
282
- // { backend: "unresolved", wasm: false, ..., overrides: ["node-rs"] }
287
+ // -> { backend: "wasm" | "noble" | "unresolved", wasm, reason?, overrides }
283
288
  ```
284
289
 
285
290
  `backend` is the process-wide default. A provider may carry its own — a
@@ -312,7 +317,7 @@ The library is organized around seven things you'll work with:
312
317
 
313
318
  - **`Workspace`** — your top-level container. Holds the password, your accounts, your network and asset list, and a built-in logger.
314
319
  - **`Account`** — either an HD account (one BIP-39 mnemonic, can derive many wallets) or a PK account (raw private keys you import one at a time). Each account can have its own password or share the workspace password.
315
- - **`Wallet`** — a unit inside an account. HD wallets carry one EVM and one Solana address; PK wallets carry one address.
320
+ - **`Wallet`** — a unit inside an account, holding one address per `(vm, network)`. Both kinds give you an EVM and a Solana address: an HD slot derives one per chain from the mnemonic, an import derives its sibling from the imported key. Treat `wallet.addresses` as the list it is.
316
321
  - **`Address`** — an on-chain identity. Sign messages, build transactions, send them.
317
322
  - **`Network`** — chain metadata. 10 networks ship pre-loaded.
318
323
  - **`Asset`** — token metadata. 25 tokens ship pre-loaded.
@@ -388,13 +393,10 @@ So a typical lifecycle looks like:
388
393
  // First run on a fresh machine — env unset, no <cwd>/.wative2 → creates ~/.wative2
389
394
  await Workspace.open({ password: "wsp-pwd" });
390
395
 
391
- // Later, opt the project into a local workspace
392
- // $ mkdir .wative2
393
- // Now <cwd>/.wative2 exists → tier 2 wins
396
+ // After `mkdir .wative2`, <cwd>/.wative2 exists and tier 2 wins
394
397
  await Workspace.open({ password: "wsp-pwd" });
395
398
 
396
- // Or override via env in CI/staging
397
- // $ WATIVE_WORKSPACE_PATH=/srv/wative-staging node app.js
399
+ // Or set WATIVE_WORKSPACE_PATH=/srv/wative-staging to override
398
400
  await Workspace.open({ password: "wsp-pwd" });
399
401
  ```
400
402
 
@@ -454,6 +456,15 @@ await hd.wallets.drop(hd.wallets[10]);
454
456
 
455
457
  ### Address — sign messages, build transactions
456
458
 
459
+ > ⚠️ **One secret controls both addresses in a PK wallet.** There is only one
460
+ > key to import, so both addresses come from it. Whoever learns that key holds
461
+ > both chains.
462
+ >
463
+ > This is not how an HD account works. There, a slot's two addresses come from
464
+ > two different derivation paths — the mnemonic is shared, the private keys are
465
+ > independent. If you want independent keys per chain, use an HD account, or
466
+ > import a separate key for each and read `addresses[0]` of each wallet.
467
+
457
468
  ```ts
458
469
  const evmAddr = hd.wallets[0].addresses.find((a) => a.vm === "evm")!;
459
470
  const svmAddr = hd.wallets[0].addresses.find((a) => a.vm === "svm")!;
@@ -547,11 +558,7 @@ const evmTx = new EvmTransaction({
547
558
  from: evmAddr.publicKey as EvmAddress,
548
559
  to: "0x1234567890123456789012345678901234567890" as EvmAddress,
549
560
  value: 10_000_000n,
550
- // The chain is set HERE, by id. `chainId` is required and has no default,
551
- // and there is no `network` field on a transaction — both are refused
552
- // rather than guessed, because a signed transaction for the wrong chain is
553
- // still a valid transaction on that chain.
554
- chainId: 8453 as ChainId, // Base
561
+ chainId: 8453 as ChainId, // required, no default; there is no `network` field
555
562
  gasLimit: 21000n,
556
563
  maxFeePerGas: 50_000_000_000n,
557
564
  maxPriorityFeePerGas: 1_000_000_000n,
@@ -576,8 +583,7 @@ const evmTx = evmAddr.buildTransaction({
576
583
  gasLimit: 21000n,
577
584
  });
578
585
  const rawEvm = evmTx.toRawTx();
579
- // { from, to, value, data, type, chainId, gasLimit, ... }
580
- // pass `rawEvm` to web3.eth.sendTransaction, ethers Signer, viem walletClient, etc.
586
+ // { from, to, value, data, type, chainId, gasLimit, ... } — hand to web3/ethers/viem
581
587
  ```
582
588
 
583
589
  ```ts
@@ -587,9 +593,7 @@ const svmTx = svmAddr.buildTransaction({
587
593
  amount: 1_000_000n,
588
594
  });
589
595
  const rawSvm = await svmTx.toRawTx();
590
- // rawSvm.add(extraIx);
591
- // rawSvm.sign(theirKeypair);
592
- // connection.sendRawTransaction(rawSvm.serialize());
596
+ // then rawSvm.add(...) / .sign(...) / connection.sendRawTransaction(rawSvm.serialize())
593
597
  ```
594
598
 
595
599
  For SVM, if you supply `recentBlockhash` at construction time, `toRawTx()` builds entirely offline — no Address binding required.