wative-core 1.2.2 → 2.2.1

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 (62) hide show
  1. package/CHANGELOG.md +147 -0
  2. package/LICENSE +55 -0
  3. package/README.md +577 -0
  4. package/dist/artifacts/evm/index.cjs +1 -0
  5. package/dist/artifacts/evm/index.d.cts +6 -0
  6. package/dist/artifacts/evm/index.d.ts +6 -0
  7. package/dist/artifacts/evm/index.js +1 -0
  8. package/dist/artifacts/svm/index.cjs +1 -0
  9. package/dist/artifacts/svm/index.d.cts +9 -0
  10. package/dist/artifacts/svm/index.d.ts +9 -0
  11. package/dist/artifacts/svm/index.js +1 -0
  12. package/dist/container-provider-C59yqd5Z.d.ts +48 -0
  13. package/dist/container-provider-i614LDE1.d.cts +48 -0
  14. package/dist/evm-BLml33LX.d.cts +34 -0
  15. package/dist/evm-DTzqGlL6.d.ts +34 -0
  16. package/dist/file-sink-B73MuigK.d.cts +44 -0
  17. package/dist/file-sink-LS4hyXXi.d.ts +44 -0
  18. package/dist/index.cjs +1 -0
  19. package/dist/index.d.cts +194 -0
  20. package/dist/index.d.ts +194 -0
  21. package/dist/index.js +1 -0
  22. package/dist/index.node.cjs +1 -0
  23. package/dist/index.node.d.cts +6 -0
  24. package/dist/index.node.d.ts +6 -0
  25. package/dist/index.node.js +1 -0
  26. package/dist/node/index.cjs +1 -0
  27. package/dist/node/index.d.cts +7 -0
  28. package/dist/node/index.d.ts +7 -0
  29. package/dist/node/index.js +1 -0
  30. package/dist/svm-CCzqNAWM.d.cts +59 -0
  31. package/dist/svm-DieFbh9k.d.ts +59 -0
  32. package/dist/workspace-CamTHxso.d.cts +737 -0
  33. package/dist/workspace-CamTHxso.d.ts +737 -0
  34. package/examples/01-quick-start.test.cjs +37 -0
  35. package/examples/02-hd-account.test.cjs +71 -0
  36. package/examples/03-pk-account.test.cjs +59 -0
  37. package/examples/04-network-management.test.cjs +94 -0
  38. package/examples/05-asset-management.test.cjs +117 -0
  39. package/examples/06-address-signing.test.cjs +55 -0
  40. package/examples/07-custom-provider.test.cjs +164 -0
  41. package/examples/08-persistence.test.cjs +105 -0
  42. package/examples/09-workspace-filter.test.cjs +84 -0
  43. package/examples/10-default-network.test.cjs +70 -0
  44. package/examples/11-subpath-imports.test.cjs +24 -0
  45. package/examples/12-logger.test.cjs +44 -0
  46. package/examples/13-workspace-config.test.cjs +43 -0
  47. package/examples/14-esm-runtime.test.mjs +81 -0
  48. package/examples/15-package-resolution.test.cjs +76 -0
  49. package/examples/16-known-answer-vectors.test.cjs +141 -0
  50. package/examples/17-locked-state.test.cjs +162 -0
  51. package/examples/README.md +72 -0
  52. package/package.json +85 -39
  53. package/lib/account.d.ts +0 -69
  54. package/lib/index.d.ts +0 -6
  55. package/lib/index.esm.js +0 -1
  56. package/lib/index.umd.js +0 -1
  57. package/lib/mnemonic.d.ts +0 -3
  58. package/lib/utils.d.ts +0 -21
  59. package/src/account.ts +0 -749
  60. package/src/index.ts +0 -11
  61. package/src/mnemonic.ts +0 -2084
  62. package/src/utils.ts +0 -132
package/README.md ADDED
@@ -0,0 +1,577 @@
1
+ # Wative Core 2
2
+
3
+ [![npm](https://img.shields.io/npm/v/wative-core?color=cb3837&logo=npm)](https://www.npmjs.com/package/wative-core)
4
+ [![CI](https://github.com/braady/wative-core/actions/workflows/ci.yml/badge.svg)](https://github.com/braady/wative-core/actions/workflows/ci.yml)
5
+ [![node](https://img.shields.io/node/v/wative-core)](https://nodejs.org)
6
+ [![license](https://img.shields.io/badge/license-Modified%20MIT-blue)](./LICENSE)
7
+
8
+ Hyperscale crypto wallet management library — multi-workspace, multi-chain, originally designed for on-chain market makers.
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.
12
+
13
+ ## How it fits together
14
+
15
+ A workspace is the container. Everything else nests inside it:
16
+
17
+ ```
18
+ Workspace one encrypted container, one password
19
+ │
20
+ ├── Account an identity — HD (from a mnemonic) or PK (imported keys)
21
+ │ └── Wallet one slot in that account
22
+ │ └── Address one key on one chain — this is what signs
23
+ │
24
+ ├── Network the chains you can reach (RPC, chain id)
25
+ └── Asset the tokens tracked on each network
26
+ ```
27
+
28
+ ### Two kinds of Account
29
+
30
+ The nesting is the same for both, but a `Wallet` means something different in
31
+ each — and that is the part most people get wrong first.
32
+
33
+ **HD** — one mnemonic, many slots. A `Wallet` is a derivation slot, and it holds
34
+ the EVM *and* Solana keys derived from that same slot:
35
+
36
+ ```
37
+ Account "Trading Desk" (HD) one mnemonic
38
+ ├── Wallet 0 m/44'/60'/0'/0/0
39
+ │ ├── Address vm: "evm" 0x9858EfFD…
40
+ │ └── Address vm: "svm" HAgk14JpMQ… same slot, both chains
41
+ ├── Wallet 1 m/44'/60'/0'/0/1
42
+ │ ├── Address vm: "evm" 0x6Fac4D18…
43
+ │ └── Address vm: "svm" Hh8QwFUA6…
44
+ └── … account.deriveWallets(n)
45
+ ```
46
+
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:
49
+
50
+ ```
51
+ Account "Cold Storage" (PK) no mnemonic
52
+ ├── 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
58
+ ```
59
+
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.
62
+
63
+ | | HD | PK |
64
+ | ---------------------- | ----------------------------- | ------------------------------- |
65
+ | Created from | a BIP-39 mnemonic | one private key |
66
+ | Grows with | `deriveWallets(n)` | `importPrivateKey(pk, vm)` |
67
+ | A `Wallet` is | a derivation slot | one imported key |
68
+ | Addresses per `Wallet` | **2** — one evm, one svm | **1** — the chain you imported |
69
+ | `dumpMnemonic()` | ✔ | throws `UNSUPPORTED_OP` |
70
+ | `sliceWallets(n)` | ✔ | throws `UNSUPPORTED_OP` |
71
+ | `importPrivateKey()` | throws `UNSUPPORTED_OP` | ✔ |
72
+
73
+ ```ts
74
+ // HD — one slot gives you the matching keys on both chains
75
+ const hd = await ws.accounts.create("Trading Desk", password, mnemonic);
76
+ await hd.deriveWallets(5); // slots 1..5, alongside slot 0
77
+ hd.wallets[0].addresses.find((a) => a.vm === "evm");
78
+ hd.wallets[0].addresses.find((a) => a.vm === "svm");
79
+
80
+ // PK — each import is its own wallet, on one chain
81
+ 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
84
+ ```
85
+
86
+ ## Install
87
+
88
+ ```bash
89
+ pnpm add wative-core
90
+ # or
91
+ npm i wative-core
92
+ ```
93
+
94
+ Node.js 22.12+ for the filesystem backend. The browser build has no Node requirement.
95
+
96
+ <img width="1280" height="720" alt="wative-hierarchy" src="https://github.com/user-attachments/assets/eea4e0de-e19f-43ff-8a2b-ea5193998166" />
97
+
98
+ ## Quick start
99
+
100
+ The API is the same in both environments. Only storage differs: Node writes
101
+ encrypted files to disk, the browser writes to IndexedDB.
102
+
103
+ ### Node
104
+
105
+ ```ts
106
+ import { Workspace } from "wative-core";
107
+ import "wative-core/node"; // installs the filesystem backend
108
+
109
+ const ws = await Workspace.open({ password: "your-workspace-password" });
110
+
111
+ const account = await ws.accounts.create(
112
+ "Trading Desk",
113
+ "your-workspace-password",
114
+ "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about",
115
+ );
116
+
117
+ await account.deriveWallets(5);
118
+
119
+ const wallet = account.wallets[0];
120
+ const evmAddress = wallet.addresses.find((a) => a.vm === "evm")!;
121
+ const svmAddress = wallet.addresses.find((a) => a.vm === "svm")!;
122
+
123
+ await ws.lock();
124
+ ```
125
+
126
+ ### Browser
127
+
128
+ ```ts
129
+ import { Workspace, IdbProvider } from "wative-core";
130
+
131
+ const provider = await IdbProvider.create("my-dapp");
132
+ const ws = await Workspace.open({ provider, password: "your-workspace-password" });
133
+
134
+ // everything from here is identical to the Node example
135
+ const account = await ws.accounts.create(
136
+ "Trading Desk",
137
+ "your-workspace-password",
138
+ "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about",
139
+ );
140
+
141
+ await account.deriveWallets(5);
142
+ await ws.lock();
143
+ ```
144
+
145
+ ### What differs
146
+
147
+ | | Node | Browser |
148
+ | -------------- | -------------------------------------- | -------------------------------- |
149
+ | Extra import | `import "wative-core/node"` | none |
150
+ | Storage | encrypted files on disk | IndexedDB |
151
+ | Where it lives | `Workspace.open({ path })`, or default | `IdbProvider.create(name)` |
152
+ | Extra setup | none | `Buffer` polyfill |
153
+ | Keys can vanish| no | yes — storage is evictable |
154
+
155
+ `HybridProvider` and `FileSink` are also re-exported from the package root in
156
+ Node, so existing imports keep working. New code should take them from
157
+ `wative-core/node`.
158
+
159
+ ### Browser: the Buffer polyfill
160
+
161
+ The Solana libraries read a global `Buffer`, which browsers do not provide.
162
+
163
+ ```bash
164
+ pnpm add buffer
165
+ ```
166
+
167
+ ```ts
168
+ // once, before importing wative-core
169
+ import { Buffer } from "buffer";
170
+ globalThis.Buffer = Buffer;
171
+ ```
172
+
173
+ ### Browser: keeping keys from disappearing
174
+
175
+ Browser storage is not permanent. A browser may clear IndexedDB when disk runs
176
+ low, and Safari clears script-writable storage after about seven days without
177
+ user interaction. Losing it means losing the keys.
178
+
179
+ `IdbProvider.create()` asks the browser for persistent storage and reports the
180
+ answer:
181
+
182
+ ```ts
183
+ const provider = await IdbProvider.create("my-dapp");
184
+ provider.durability; // "persistent" | "best-effort"
185
+ ```
186
+
187
+ Creating a **new** workspace in non-persistent storage is refused, because keys
188
+ written there can disappear without warning:
189
+
190
+ ```ts
191
+ // throws STORAGE_NOT_DURABLE when the browser refused persistence
192
+ await Workspace.open({ provider, password });
193
+
194
+ // proceed anyway — only if the keys are backed up elsewhere
195
+ await IdbProvider.create("my-dapp", { acknowledgeEvictionRisk: true });
196
+ ```
197
+
198
+ Opening a workspace that already exists is never blocked.
199
+
200
+ Browsers are far more likely to grant persistence when the request comes from a
201
+ user action, so call `IdbProvider.create()` from a click rather than on page
202
+ load. Whatever the answer, give users a backup — `exportContainer()` returns the
203
+ encrypted records, and they import into a Node workspace unchanged:
204
+
205
+ ```ts
206
+ const backup = await provider.exportContainer(); // still encrypted
207
+ await otherProvider.importContainer(backup); // same password opens it
208
+ ```
209
+
210
+ ## Runnable examples
211
+
212
+ An `examples/` folder ships with the package — runnable JavaScript files demonstrating every domain's use cases. Read them top-to-bottom as walkthroughs, or run them as integration tests via Node's built-in test runner.
213
+
214
+ Copy them out of `node_modules` first, then run the whole set:
215
+
216
+ ```bash
217
+ cp -R node_modules/wative-core/tests wative-examples
218
+ node --test wative-examples/
219
+ ```
220
+
221
+ The copy is required: Node's test runner skips anything under `node_modules`, so pointing `--test` there finds no files. To run a single example without copying, execute it directly — a `node:test` file runs itself:
222
+
223
+ ```bash
224
+ node node_modules/wative-core/examples/01-quick-start.test.cjs
225
+ ```
226
+
227
+ Files cover: quick start, HD vs PK accounts, network management, asset management, address signing, custom storage backends, persistence, workspace search, default-network selection, subpath imports, the workspace logger, workspace config, the ESM entry, and package resolution. See [examples/README.md](./examples/README.md) for the index.
228
+
229
+ ## What you can do
230
+
231
+ The library is organized around seven things you'll work with:
232
+
233
+ - **`Workspace`** — your top-level container. Holds the password, your accounts, your network and asset list, and a built-in logger.
234
+ - **`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.
235
+ - **`Wallet`** — a unit inside an account. HD wallets carry one EVM and one Solana address; PK wallets carry one address.
236
+ - **`Address`** — an on-chain identity. Sign messages, build transactions, send them.
237
+ - **`Network`** — chain metadata. 10 networks ship pre-loaded.
238
+ - **`Asset`** — token metadata. 25 tokens ship pre-loaded.
239
+ - **`Transaction`** — `EvmTransaction` or `SvmTransaction`. Subscribe to lifecycle events (`change` / `confirmed` / `failed`) or await terminal states (`whenSubmitted` / `whenMined` / `whenConfirmed` / `whenFinalized`).
240
+
241
+ Everything is encrypted on disk under your workspace password.
242
+
243
+ ### Pre-loaded networks and tokens
244
+
245
+ | Networks (10) | Tokens (25) |
246
+ |---|---|
247
+ | ethereum, base, bnbchain, arbitrum, optimism | native gas + USDC + USDT on each EVM mainnet (BSC versions are 18-decimal Binance-Peg); plus WETH on ethereum |
248
+ | sepolia, arbitrum-sepolia | native ETH on both testnets; plus USDC on arbitrum-sepolia |
249
+ | solana, solana-testnet, solana-devnet | native SOL + USDC + USDT + WSOL on solana mainnet; native-only on testnets |
250
+
251
+ ## Examples by domain
252
+
253
+ ### Workspace — open / unlock / lock
254
+
255
+ ```ts
256
+ import { Workspace } from "wative-core";
257
+
258
+ const ws = await Workspace.open({ password: "wsp-pwd" });
259
+
260
+ const ws2 = await Workspace.open({ path: "/var/lib/wative-prod", password: "wsp-pwd" });
261
+
262
+ const locked = await Workspace.open({ path: "/var/lib/wative-prod" });
263
+ console.log(locked.locked);
264
+ await locked.unlock("wsp-pwd");
265
+
266
+ ws.logger.info("trading session opened", { desk: "alpha" });
267
+ await ws.logger.setLevel("debug");
268
+
269
+ await ws.lock();
270
+ ```
271
+
272
+ #### Creating vs. opening — it's automatic
273
+
274
+ You don't tell `open()` whether to create or open. It decides from what's already at the location:
275
+
276
+ - **Nothing there (or an empty folder)** → a new workspace is created.
277
+ - **A wative workspace already there** → it's opened, and your password is checked.
278
+ - **Something else there** (a folder with unrelated files) → the call is **refused** with a `PARAMETER_ERROR`, so a mistyped path never turns one of your other folders into a workspace.
279
+
280
+ Pass the password to get an unlocked workspace, or omit it to get a locked one you unlock later:
281
+
282
+ ```ts
283
+ const ws = await Workspace.open({ path: "/var/lib/wative-prod", password: "wsp-pwd" }); // unlocked
284
+ const later = await Workspace.open({ path: "/var/lib/wative-prod" }); // locked
285
+ await later.unlock("wsp-pwd");
286
+ ```
287
+
288
+ `open()` also accepts the older positional form — `Workspace.open(pathOrProvider, password)` — which works exactly the same way.
289
+
290
+ #### Where the workspace lives on disk
291
+
292
+ Most apps pass an explicit path as the first argument — that path is used verbatim (with `~` expansion). When you omit the path, the library resolves it through a **3-tier strategy**, in order:
293
+
294
+ 1. **`WATIVE_WORKSPACE_PATH` environment variable** — if set and non-empty, this wins. Use it for ops/CI overrides without touching code:
295
+
296
+ ```bash
297
+ WATIVE_WORKSPACE_PATH=/var/lib/wative-prod node app.js
298
+ WATIVE_WORKSPACE_PATH=~/.wative2 node app.js # ~ is expanded
299
+ ```
300
+
301
+ 2. **`<process.cwd()>/.wative2`** — used when the env var is unset *and* this directory already exists on disk. Lets a project pin its workspace by simply having a `.wative2/` folder at the repo root.
302
+
303
+ 3. **`<os.homedir()>/.wative2`** — last-resort fallback. Where `Workspace.open({ password })` lands on a fresh machine when no env var is set and there's no project-local `.wative2/`. Acts as the user-wide default workspace.
304
+
305
+ So a typical lifecycle looks like:
306
+
307
+ ```ts
308
+ // First run on a fresh machine — env unset, no <cwd>/.wative2 → creates ~/.wative2
309
+ await Workspace.open({ password: "wsp-pwd" });
310
+
311
+ // Later, opt the project into a local workspace
312
+ // $ mkdir .wative2
313
+ // Now <cwd>/.wative2 exists → tier 2 wins
314
+ await Workspace.open({ password: "wsp-pwd" });
315
+
316
+ // Or override via env in CI/staging
317
+ // $ WATIVE_WORKSPACE_PATH=/srv/wative-staging node app.js
318
+ await Workspace.open({ password: "wsp-pwd" });
319
+ ```
320
+
321
+ If you'd rather not rely on the resolver, just pass the path explicitly — it short-circuits all three tiers.
322
+
323
+ ### Account — HD vs PK, derive wallets, import keys
324
+
325
+ ```ts
326
+ const hd = await ws.accounts.create(
327
+ "Trading Desk",
328
+ "wsp-pwd",
329
+ "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about",
330
+ );
331
+ await hd.deriveWallets(10);
332
+ await hd.sliceWallets(6);
333
+
334
+ const pk = await ws.accounts.create(
335
+ "External Hot Wallet",
336
+ "wsp-pwd",
337
+ "0x4f3edf983ac636a65a842ce7c78d9aa706d3b113bce9c46f30d7d21715b23b1d",
338
+ undefined,
339
+ { kind: "PK" },
340
+ );
341
+ await pk.importPrivateKey(
342
+ "xtGWcHvQsr5ue8zG2F5fm31bW8vyn597Y92gUWaZZg3S1Z6FeJMATL8KU3xJMGbLfALnokcct9y5wYtCRrwNXZW",
343
+ "svm",
344
+ );
345
+
346
+ const sub = await ws.accounts.create(
347
+ "Sub Account",
348
+ "sub-pwd",
349
+ "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about",
350
+ undefined,
351
+ { hasOwnPassword: true },
352
+ );
353
+ await sub.tryUnlock("sub-pwd");
354
+ await sub.resetPassword("sub-pwd", "new-sub-pwd");
355
+
356
+ console.log(hd.dumpMnemonic());
357
+
358
+ hd.lock();
359
+ await pk.drop();
360
+ ```
361
+
362
+ ### Wallet — derivation slot, tags, drop
363
+
364
+ ```ts
365
+ const wallet = hd.wallets[2];
366
+ console.log(wallet.id);
367
+ console.log(wallet.addresses.length);
368
+
369
+ await wallet.addTag("hot");
370
+ await wallet.addTag("alpha-strategy");
371
+
372
+ await hd.wallets.drop(hd.wallets[10]);
373
+ ```
374
+
375
+ ### Address — sign messages, build transactions
376
+
377
+ ```ts
378
+ const evmAddr = hd.wallets[0].addresses.find((a) => a.vm === "evm")!;
379
+ const svmAddr = hd.wallets[0].addresses.find((a) => a.vm === "svm")!;
380
+
381
+ const sig = evmAddr.signMessage("hello hedgue");
382
+
383
+ import { Network } from "wative-core";
384
+ const tx = await evmAddr.buildTransaction({
385
+ to: "0x1234567890123456789012345678901234567890",
386
+ value: 1_000_000_000_000_000n,
387
+ network: Network.Ethereum,
388
+ });
389
+ await evmAddr.signTransaction(tx);
390
+ const tracker = await evmAddr.sendTransaction(tx);
391
+
392
+ tracker.on("change", (state) => console.log("state →", state.status));
393
+ tracker.on("confirmed", (receipt) => console.log("confirmed", receipt));
394
+ tracker.on("failed", (err) => console.error("failed", err));
395
+
396
+ const hash = await tracker.whenSubmitted();
397
+ const receipt = await tracker.whenConfirmed(3);
398
+
399
+ const sim = await evmAddr.simulateTransaction(tx);
400
+ console.log(sim.gasUsed);
401
+ ```
402
+
403
+ ### Network — pre-loaded networks + your own
404
+
405
+ ```ts
406
+ import { Network, type ChainId, type Slug } from "wative-core";
407
+
408
+ console.log(Network.Ethereum.chainId);
409
+ console.log(Network.Solana.vm);
410
+
411
+ const eth = ws.networks.bySlug("ethereum" as Slug)!;
412
+ const arb = ws.networks.byChainId(42161 as ChainId)!;
413
+
414
+ const monad = new Network({
415
+ slug: "monad-testnet" as Slug,
416
+ name: "Monad Testnet",
417
+ chainId: 10143 as ChainId,
418
+ rpcUrl: "https://testnet-rpc.monad.xyz",
419
+ nativeCurrency: { name: "Monad", symbol: "MON", decimals: 18 },
420
+ vm: "evm",
421
+ });
422
+ await ws.networks.add(monad);
423
+
424
+ const customEth = new Network({
425
+ ...eth,
426
+ rpcUrl: "https://eth-mainnet.g.alchemy.com/v2/YOUR_KEY",
427
+ });
428
+ await ws.networks.update(customEth);
429
+
430
+ await ws.networks.drop(monad);
431
+
432
+ await hd.setDefaultNetwork("base");
433
+ await hd.setDefaultNetwork(8453 as ChainId);
434
+ await hd.setDefaultNetwork("0x2105");
435
+ await hd.setDefaultNetwork(Network.Base);
436
+ ```
437
+
438
+ ### Asset — pre-loaded tokens + custom tokens
439
+
440
+ ```ts
441
+ import { Asset, Network, type AssetId } from "wative-core";
442
+
443
+ const ethAssets = await ws.assets(Network.Ethereum);
444
+ console.log(ethAssets.map((a) => a.symbol));
445
+
446
+ const myToken = new Asset({
447
+ id: 200 as AssetId,
448
+ symbol: "PEPE",
449
+ name: "Pepe",
450
+ decimals: 18,
451
+ contractAddress: "0x6982508145454Ce325dDbE47a25d4ec3d2311933",
452
+ network: Network.Ethereum,
453
+ });
454
+ await ws.addAsset(myToken);
455
+
456
+ await ws.dropAsset(200 as AssetId);
457
+
458
+ const found = await ws.filter("USDC", "Asset");
459
+ const byAddr = await ws.filter("0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48", "Asset");
460
+ ```
461
+
462
+ ### Transaction — build, sign, simulate, send
463
+
464
+ ```ts
465
+ import { EvmTransaction, Network, type EvmAddress } from "wative-core";
466
+
467
+ const evmTx = new EvmTransaction({
468
+ from: evmAddr.publicKey as EvmAddress,
469
+ to: "0x1234567890123456789012345678901234567890" as EvmAddress,
470
+ value: 10_000_000n,
471
+ network: Network.Base,
472
+ gasLimit: 21000n,
473
+ maxFeePerGas: 50_000_000_000n,
474
+ maxPriorityFeePerGas: 1_000_000_000n,
475
+ });
476
+ await evmAddr.signTransaction(evmTx);
477
+ const evmTracker = await evmAddr.sendTransaction(evmTx);
478
+
479
+ const receipt = await evmTracker.whenConfirmed(3);
480
+ ```
481
+
482
+ For Solana transactions, build instructions with `@solana/web3.js` (or your preferred Solana toolkit) and pass them to `new SvmTransaction({ from, instructions, network })`. The signing, sending, and tracker lifecycle then mirror the EVM flow.
483
+
484
+ #### Hand off to an external signer / RPC client
485
+
486
+ Both `EvmTransaction` and `SvmTransaction` expose `toRawTx()` for routing the transaction through any chain-native tool you already use:
487
+
488
+ ```ts
489
+ // EVM — synchronous; returns a plain web3.js / ethers / viem TransactionRequest
490
+ const evmTx = evmAddr.buildTransaction({
491
+ to: "0x1234567890123456789012345678901234567890" as EvmAddress,
492
+ value: 10_000_000n,
493
+ gasLimit: 21000n,
494
+ });
495
+ const rawEvm = evmTx.toRawTx();
496
+ // { from, to, value, data, type, chainId, gasLimit, ... }
497
+ // pass `rawEvm` to web3.eth.sendTransaction, ethers Signer, viem walletClient, etc.
498
+ ```
499
+
500
+ ```ts
501
+ // Solana — async; returns the underlying @solana/web3.js Transaction
502
+ const svmTx = svmAddr.buildTransaction({
503
+ recipient: "11111111111111111111111111111112",
504
+ amount: 1_000_000n,
505
+ });
506
+ const rawSvm = await svmTx.toRawTx();
507
+ // rawSvm.add(extraIx);
508
+ // rawSvm.sign(theirKeypair);
509
+ // connection.sendRawTransaction(rawSvm.serialize());
510
+ ```
511
+
512
+ For SVM, if you supply `recentBlockhash` at construction time, `toRawTx()` builds entirely offline — no Address binding required.
513
+
514
+ ### Workspace search
515
+
516
+ ```ts
517
+ const account = await ws.filter("Trading Desk", "Account");
518
+ const wallet = await ws.filter("alpha-strategy", "Wallet");
519
+ const address = await ws.filter("0xab12345678…cdef", "Address");
520
+ const asset = await ws.filter("USDC", "Asset");
521
+ ```
522
+
523
+ ## Custom storage backends
524
+
525
+ By default, `Workspace.open(...)` stores everything as encrypted files under the path you pass it. If you want to store the workspace somewhere else — a database, an object store, a browser's IndexedDB — extend the `Provider` base class and pass an instance to `Workspace.open()`:
526
+
527
+ ```ts
528
+ import { Workspace, Provider, Record, type RecordInit, type RecordType } from "wative-core";
529
+ import type { Slug } from "wative-core";
530
+
531
+ class MyDatabaseProvider extends Provider {
532
+ constructor(connectionString: string) {
533
+ super(connectionString);
534
+ }
535
+
536
+ protected async _exist(path: string) { return false; }
537
+ protected async _read(path: string) { return new Uint8Array(); }
538
+ protected async _write(path: string, b: Uint8Array) {}
539
+ protected async _remove(path: string) {}
540
+ protected async _ensureDir(path: string) {}
541
+ protected async _listItems(path: string) { return []; }
542
+
543
+ async unlockContainer(password: string): Promise<boolean> { return true; }
544
+ async lockContainer(): Promise<void> {}
545
+ isContainerUnlocked(): boolean { return true; }
546
+
547
+ async loadRecord<T>(type: RecordType, slug?: Slug): Promise<Record<T>> { throw new Error(); }
548
+ async loadRecords<T>(type: RecordType): Promise<readonly Record<T>[]> { return []; }
549
+ async writeRecord<T>(type: RecordType, slug: Slug, value: T): Promise<void> {}
550
+ async dropRecord(type: RecordType, slug: Slug): Promise<void> {}
551
+
552
+ async close(): Promise<void> {}
553
+ }
554
+
555
+ const ws = await Workspace.open({ provider: new MyDatabaseProvider("postgres://..."), password: "wsp-pwd" });
556
+ ```
557
+
558
+ `Workspace.open()` accepts any `Provider` subclass — the rest of the library is identical regardless of where state lives.
559
+
560
+ ## Imports from sub-paths
561
+
562
+ Heavier chain helpers are kept on separate import paths so you only pull them in if you need them:
563
+
564
+ ```ts
565
+ import { ERC20 } from "wative-core/artifacts/evm";
566
+ import { TokenProgram, Token2022Program } from "wative-core/artifacts/svm";
567
+ ```
568
+
569
+ ## Compatibility notes
570
+
571
+ - **Runtime**: Node.js 22.12+. Deno / Bun / browser support is partial. Some chain libraries that ship as compiled dependencies do not have universal builds yet.
572
+ - **Platform builds**: a few compiled dependencies lack pre-built binaries for Windows-ARM64 and Alpine-musl. Most installs on macOS / Linux x64 / Linux ARM64 won't notice.
573
+ - **One process at a time**: opening the same workspace from two Node processes simultaneously isn't supported.
574
+
575
+ ## License
576
+
577
+ [BUSL-1.1](./LICENSE) — Business Source License 1.1.