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