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
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
// 07 — Custom storage backend. Extend the abstract `Provider` class to plug
|
|
2
|
+
// `Workspace.open()` into any storage system. This example uses an in-memory
|
|
3
|
+
// key-value store with a trivial XOR cipher; real custom Providers should use
|
|
4
|
+
// an authenticated cipher (AES-GCM, ChaCha20-Poly1305).
|
|
5
|
+
|
|
6
|
+
const test = require("node:test");
|
|
7
|
+
const assert = require("node:assert");
|
|
8
|
+
|
|
9
|
+
const { Workspace, Provider, Record, WativeError } = require("wative-core");
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* In-memory `Provider` implementation. Stores records in a Map under
|
|
13
|
+
* filesystem-style paths, encrypted with a single-byte-header XOR scheme.
|
|
14
|
+
*/
|
|
15
|
+
class InMemoryProvider extends Provider {
|
|
16
|
+
#store = new Map();
|
|
17
|
+
#dirs = new Set();
|
|
18
|
+
#password = null;
|
|
19
|
+
|
|
20
|
+
constructor(label) {
|
|
21
|
+
super(label);
|
|
22
|
+
this.#dirs.add("/");
|
|
23
|
+
this.#dirs.add("/accounts");
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
_exist(path) { return this.#store.has(path) || this.#dirs.has(path); }
|
|
27
|
+
_read(path) {
|
|
28
|
+
const v = this.#store.get(path);
|
|
29
|
+
if (!v) throw new WativeError("RECORD_NOT_FOUND", `no entry at ${path}`);
|
|
30
|
+
return v;
|
|
31
|
+
}
|
|
32
|
+
_write(path, data) { this.#store.set(path, data); }
|
|
33
|
+
_remove(path) { this.#store.delete(path); }
|
|
34
|
+
_ensureDir(path) { this.#dirs.add(path); }
|
|
35
|
+
_listItems(path) {
|
|
36
|
+
const prefix = path.endsWith("/") ? path : path + "/";
|
|
37
|
+
const out = new Set();
|
|
38
|
+
for (const k of this.#store.keys()) {
|
|
39
|
+
if (k.startsWith(prefix)) {
|
|
40
|
+
const head = k.slice(prefix.length).split("/")[0];
|
|
41
|
+
if (head) out.add(head);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
return Array.from(out);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
async unlockContainer(password) {
|
|
48
|
+
if (typeof password !== "string" || password.length === 0) return false;
|
|
49
|
+
const path = this.#pathFor("CONFIG", "config");
|
|
50
|
+
if (this.#store.has(path)) {
|
|
51
|
+
try { this.#decrypt(this.#store.get(path), password); }
|
|
52
|
+
catch { return false; }
|
|
53
|
+
}
|
|
54
|
+
this.#password = password;
|
|
55
|
+
return true;
|
|
56
|
+
}
|
|
57
|
+
async lockContainer() { this.#password = null; }
|
|
58
|
+
isContainerUnlocked() { return this.#password !== null; }
|
|
59
|
+
|
|
60
|
+
async loadRecord(type, slug) {
|
|
61
|
+
if (this.#password === null) throw new WativeError("PROVIDER_IO", "container locked");
|
|
62
|
+
const eff = slug ?? this.#defaultSlug(type);
|
|
63
|
+
const path = this.#pathFor(type, eff);
|
|
64
|
+
if (!this.#store.has(path)) throw new WativeError("RECORD_NOT_FOUND", path);
|
|
65
|
+
const encrypted = this.#store.get(path);
|
|
66
|
+
const value = JSON.parse(new TextDecoder().decode(this.#decrypt(encrypted, this.#password)));
|
|
67
|
+
return new Record(eff, path, {
|
|
68
|
+
provider: this,
|
|
69
|
+
recordType: type,
|
|
70
|
+
encrypted,
|
|
71
|
+
value,
|
|
72
|
+
password: this.#password,
|
|
73
|
+
decrypt: (ct, pwd) => this.#decrypt(ct, pwd),
|
|
74
|
+
persist: async (val, pwd) => {
|
|
75
|
+
this.#store.set(path, this.#encrypt(JSON.stringify(val), pwd));
|
|
76
|
+
},
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
async loadRecords(type) {
|
|
80
|
+
if (type !== "ACCOUNTS") {
|
|
81
|
+
const r = await this.loadRecord(type).catch((e) => {
|
|
82
|
+
if (e instanceof WativeError && e.code === "RECORD_NOT_FOUND") return null;
|
|
83
|
+
throw e;
|
|
84
|
+
});
|
|
85
|
+
return r ? [r] : [];
|
|
86
|
+
}
|
|
87
|
+
const items = this._listItems("/accounts").filter((n) => n.endsWith(".kv"));
|
|
88
|
+
return Promise.all(items.map((n) => this.loadRecord("ACCOUNTS", n.slice(0, -3))));
|
|
89
|
+
}
|
|
90
|
+
async writeRecord(type, slug, value) {
|
|
91
|
+
if (this.#password === null) throw new WativeError("PROVIDER_IO", "container locked");
|
|
92
|
+
this.#store.set(this.#pathFor(type, slug), this.#encrypt(JSON.stringify(value), this.#password));
|
|
93
|
+
}
|
|
94
|
+
async dropRecord(type, slug) { this.#store.delete(this.#pathFor(type, slug)); }
|
|
95
|
+
async close() { this.#password = null; }
|
|
96
|
+
|
|
97
|
+
#defaultSlug(type) {
|
|
98
|
+
return ({ CONFIG: "config", NETWORK: "network", ASSET: "asset", LOG: "log", ACCOUNTS: "" })[type] || "";
|
|
99
|
+
}
|
|
100
|
+
#pathFor(type, slug) {
|
|
101
|
+
if (type === "ACCOUNTS") return `/accounts/${slug}.kv`;
|
|
102
|
+
return `/${this.#defaultSlug(type)}`;
|
|
103
|
+
}
|
|
104
|
+
#encrypt(plaintext, password) {
|
|
105
|
+
const data = new TextEncoder().encode(plaintext);
|
|
106
|
+
const stream = this.#streamFor(password, data.length);
|
|
107
|
+
const out = new Uint8Array(data.length + 1);
|
|
108
|
+
out[0] = 0x42;
|
|
109
|
+
for (let i = 0; i < data.length; i++) out[i + 1] = data[i] ^ stream[i];
|
|
110
|
+
return out;
|
|
111
|
+
}
|
|
112
|
+
#decrypt(blob, password) {
|
|
113
|
+
if (blob.length < 1 || blob[0] !== 0x42) throw new WativeError("DECRYPT_FAILED", "bad header");
|
|
114
|
+
const ct = blob.subarray(1);
|
|
115
|
+
const stream = this.#streamFor(password, ct.length);
|
|
116
|
+
const out = new Uint8Array(ct.length);
|
|
117
|
+
for (let i = 0; i < ct.length; i++) out[i] = ct[i] ^ stream[i];
|
|
118
|
+
try { JSON.parse(new TextDecoder().decode(out)); }
|
|
119
|
+
catch (e) { throw new WativeError("BAD_PASSWORD", "non-JSON after decrypt", { cause: e }); }
|
|
120
|
+
return out;
|
|
121
|
+
}
|
|
122
|
+
#streamFor(password, length) {
|
|
123
|
+
const seed = new TextEncoder().encode("salt|" + password);
|
|
124
|
+
const out = new Uint8Array(length);
|
|
125
|
+
for (let i = 0; i < length; i++) out[i] = seed[i % seed.length] ^ ((i * 7) & 0xff);
|
|
126
|
+
return out;
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
test("custom provider — full lifecycle against an in-memory backend", async () => {
|
|
131
|
+
const provider = new InMemoryProvider("memory://desk-1");
|
|
132
|
+
|
|
133
|
+
let ws = await Workspace.open({ provider, password: "wsp-pwd" });
|
|
134
|
+
assert.strictEqual(ws.locked, false);
|
|
135
|
+
|
|
136
|
+
const slugs = ws.networks.map((n) => n.slug).sort();
|
|
137
|
+
assert.strictEqual(slugs.length, 10);
|
|
138
|
+
assert.ok(slugs.includes("ethereum"));
|
|
139
|
+
assert.ok(slugs.includes("arbitrum-sepolia"));
|
|
140
|
+
|
|
141
|
+
const acc = await ws.accounts.create(
|
|
142
|
+
"Trader",
|
|
143
|
+
"wsp-pwd",
|
|
144
|
+
"abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about",
|
|
145
|
+
);
|
|
146
|
+
await acc.deriveWallets(2);
|
|
147
|
+
assert.strictEqual(acc.wallets.length, 3);
|
|
148
|
+
|
|
149
|
+
await ws.lock();
|
|
150
|
+
ws = await Workspace.open({ provider, password: "wsp-pwd" });
|
|
151
|
+
const reopened = ws.accounts.bySlug(acc.slug);
|
|
152
|
+
assert.ok(reopened);
|
|
153
|
+
assert.strictEqual(reopened.wallets.length, 3);
|
|
154
|
+
|
|
155
|
+
await ws.lock();
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
test("custom provider — bad password is rejected", async () => {
|
|
159
|
+
const provider = new InMemoryProvider("memory://desk-2");
|
|
160
|
+
const ws = await Workspace.open({ provider, password: "right-pwd" });
|
|
161
|
+
await ws.lock();
|
|
162
|
+
|
|
163
|
+
await assert.rejects(Workspace.open({ provider, password: "wrong-pwd" }), (err) => err.code === "BAD_PASSWORD");
|
|
164
|
+
});
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
// 08 — Persistence. Verify that accounts, networks, custom assets, and the
|
|
2
|
+
// logger configuration all round-trip correctly across `lock()` + reopen.
|
|
3
|
+
|
|
4
|
+
const test = require("node:test");
|
|
5
|
+
const assert = require("node:assert");
|
|
6
|
+
const { mkdtempSync } = require("node:fs");
|
|
7
|
+
const { tmpdir } = require("node:os");
|
|
8
|
+
const { join } = require("node:path");
|
|
9
|
+
|
|
10
|
+
const { Workspace, Network, Asset } = require("wative-core");
|
|
11
|
+
|
|
12
|
+
const MNEMONIC =
|
|
13
|
+
"abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about";
|
|
14
|
+
|
|
15
|
+
test("persistence — multiple HD + PK accounts round-trip", async () => {
|
|
16
|
+
const root = mkdtempSync(join(tmpdir(), "wative-persist-acc-"));
|
|
17
|
+
let ws = await Workspace.open({ path: root, password: "wsp-pwd" });
|
|
18
|
+
|
|
19
|
+
const hd = await ws.accounts.create("Desk One", "wsp-pwd", MNEMONIC);
|
|
20
|
+
await hd.deriveWallets(3);
|
|
21
|
+
|
|
22
|
+
const pk = await ws.accounts.create(
|
|
23
|
+
"Hot Wallet",
|
|
24
|
+
"wsp-pwd",
|
|
25
|
+
"0x4f3edf983ac636a65a842ce7c78d9aa706d3b113bce9c46f30d7d21715b23b1d",
|
|
26
|
+
undefined,
|
|
27
|
+
{ kind: "PK" },
|
|
28
|
+
);
|
|
29
|
+
|
|
30
|
+
await ws.lock();
|
|
31
|
+
ws = await Workspace.open({ path: root, password: "wsp-pwd" });
|
|
32
|
+
assert.strictEqual(ws.accounts.length, 2);
|
|
33
|
+
const reopenHd = ws.accounts.bySlug(hd.slug);
|
|
34
|
+
const reopenPk = ws.accounts.bySlug(pk.slug);
|
|
35
|
+
assert.ok(reopenHd && reopenPk);
|
|
36
|
+
assert.strictEqual(reopenHd.organizationType, "HD");
|
|
37
|
+
assert.strictEqual(reopenPk.organizationType, "PK");
|
|
38
|
+
assert.strictEqual(reopenHd.wallets.length, 4);
|
|
39
|
+
assert.strictEqual(reopenPk.wallets.length, 1);
|
|
40
|
+
|
|
41
|
+
await ws.lock();
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
test("persistence — added user network round-trips with all fields", async () => {
|
|
45
|
+
const root = mkdtempSync(join(tmpdir(), "wative-persist-net-"));
|
|
46
|
+
let ws = await Workspace.open({ path: root, password: "wsp-pwd" });
|
|
47
|
+
|
|
48
|
+
await ws.networks.add(new Network({
|
|
49
|
+
slug: "monad-testnet",
|
|
50
|
+
name: "Monad Testnet",
|
|
51
|
+
chainId: 10143,
|
|
52
|
+
rpcUrl: "https://testnet-rpc.monad.xyz",
|
|
53
|
+
nativeCurrency: { name: "Monad", symbol: "MON", decimals: 18 },
|
|
54
|
+
color: "#7e22ce",
|
|
55
|
+
vm: "evm",
|
|
56
|
+
}));
|
|
57
|
+
|
|
58
|
+
await ws.lock();
|
|
59
|
+
ws = await Workspace.open({ path: root, password: "wsp-pwd" });
|
|
60
|
+
const monad = ws.networks.bySlug("monad-testnet");
|
|
61
|
+
assert.ok(monad);
|
|
62
|
+
assert.strictEqual(monad.chainId, 10143);
|
|
63
|
+
assert.strictEqual(monad.rpcUrl, "https://testnet-rpc.monad.xyz");
|
|
64
|
+
assert.strictEqual(monad.nativeCurrency.symbol, "MON");
|
|
65
|
+
assert.strictEqual(monad.color, "#7e22ce");
|
|
66
|
+
|
|
67
|
+
await ws.lock();
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
test("persistence — custom user asset round-trips", async () => {
|
|
71
|
+
const root = mkdtempSync(join(tmpdir(), "wative-persist-asset-"));
|
|
72
|
+
let ws = await Workspace.open({ path: root, password: "wsp-pwd" });
|
|
73
|
+
|
|
74
|
+
await ws.addAsset(new Asset({
|
|
75
|
+
id: 250,
|
|
76
|
+
symbol: "PEPE",
|
|
77
|
+
name: "Pepe",
|
|
78
|
+
decimals: 18,
|
|
79
|
+
contractAddress: "0x6982508145454Ce325dDbE47a25d4ec3d2311933",
|
|
80
|
+
network: Network.Ethereum,
|
|
81
|
+
}));
|
|
82
|
+
|
|
83
|
+
await ws.lock();
|
|
84
|
+
ws = await Workspace.open({ path: root, password: "wsp-pwd" });
|
|
85
|
+
const ethAssets = await ws.assets(Network.Ethereum);
|
|
86
|
+
const pepe = ethAssets.find((a) => a.id === 250);
|
|
87
|
+
assert.ok(pepe);
|
|
88
|
+
assert.strictEqual(pepe.symbol, "PEPE");
|
|
89
|
+
assert.strictEqual(pepe.decimals, 18);
|
|
90
|
+
|
|
91
|
+
await ws.lock();
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
test("persistence — custom logger config round-trips", async () => {
|
|
95
|
+
const root = mkdtempSync(join(tmpdir(), "wative-persist-log-"));
|
|
96
|
+
let ws = await Workspace.open({ path: root, password: "wsp-pwd" });
|
|
97
|
+
|
|
98
|
+
await ws.logger.setLevel("debug");
|
|
99
|
+
|
|
100
|
+
await ws.lock();
|
|
101
|
+
ws = await Workspace.open({ path: root, password: "wsp-pwd" });
|
|
102
|
+
assert.strictEqual(ws.logger.config.minLevel, "debug");
|
|
103
|
+
|
|
104
|
+
await ws.lock();
|
|
105
|
+
});
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
// 09 — Workspace search. `filter(query, objective)` returns the live object
|
|
2
|
+
// matching `query` for the given objective: Wallet, Address, Account, or Asset.
|
|
3
|
+
|
|
4
|
+
const test = require("node:test");
|
|
5
|
+
const assert = require("node:assert");
|
|
6
|
+
const { mkdtempSync } = require("node:fs");
|
|
7
|
+
const { tmpdir } = require("node:os");
|
|
8
|
+
const { join } = require("node:path");
|
|
9
|
+
|
|
10
|
+
const { Workspace } = require("wative-core");
|
|
11
|
+
|
|
12
|
+
const MNEMONIC =
|
|
13
|
+
"abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about";
|
|
14
|
+
|
|
15
|
+
test("filter — find an Account by displayName or slug", async () => {
|
|
16
|
+
const root = mkdtempSync(join(tmpdir(), "wative-filter-acc-"));
|
|
17
|
+
const ws = await Workspace.open({ path: root, password: "wsp-pwd" });
|
|
18
|
+
|
|
19
|
+
const acc = await ws.accounts.create("Trading Desk", "wsp-pwd", MNEMONIC);
|
|
20
|
+
|
|
21
|
+
const byName = await ws.filter("Trading Desk", "Account");
|
|
22
|
+
const bySlug = await ws.filter(acc.slug, "Account");
|
|
23
|
+
assert.ok(byName && byName.slug === acc.slug);
|
|
24
|
+
assert.ok(bySlug && bySlug.slug === acc.slug);
|
|
25
|
+
|
|
26
|
+
const missing = await ws.filter("nonexistent", "Account");
|
|
27
|
+
assert.strictEqual(missing, null);
|
|
28
|
+
|
|
29
|
+
await ws.lock();
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
test("filter — find a Wallet by id or by tag", async () => {
|
|
33
|
+
const root = mkdtempSync(join(tmpdir(), "wative-filter-wallet-"));
|
|
34
|
+
const ws = await Workspace.open({ path: root, password: "wsp-pwd" });
|
|
35
|
+
|
|
36
|
+
const acc = await ws.accounts.create("Desk", "wsp-pwd", MNEMONIC);
|
|
37
|
+
await acc.deriveWallets(2);
|
|
38
|
+
|
|
39
|
+
await acc.wallets[1].addTag("alpha-strategy");
|
|
40
|
+
|
|
41
|
+
const byId = await ws.filter("1", "Wallet");
|
|
42
|
+
assert.ok(byId);
|
|
43
|
+
assert.strictEqual(byId.id, 1);
|
|
44
|
+
|
|
45
|
+
const byTag = await ws.filter("alpha-strategy", "Wallet");
|
|
46
|
+
assert.ok(byTag);
|
|
47
|
+
assert.strictEqual(byTag.id, 1);
|
|
48
|
+
|
|
49
|
+
await ws.lock();
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
test("filter — find an Address by its public key (case-insensitive for EVM)", async () => {
|
|
53
|
+
const root = mkdtempSync(join(tmpdir(), "wative-filter-addr-"));
|
|
54
|
+
const ws = await Workspace.open({ path: root, password: "wsp-pwd" });
|
|
55
|
+
|
|
56
|
+
const acc = await ws.accounts.create("Desk", "wsp-pwd", MNEMONIC);
|
|
57
|
+
const evm = acc.wallets[0].addresses.find((a) => a.vm === "evm");
|
|
58
|
+
|
|
59
|
+
const found = await ws.filter(evm.publicKey, "Address");
|
|
60
|
+
assert.ok(found);
|
|
61
|
+
assert.strictEqual(found.publicKey, evm.publicKey);
|
|
62
|
+
|
|
63
|
+
const foundLower = await ws.filter(evm.publicKey.toLowerCase(), "Address");
|
|
64
|
+
assert.ok(foundLower);
|
|
65
|
+
assert.strictEqual(foundLower.publicKey, evm.publicKey);
|
|
66
|
+
|
|
67
|
+
await ws.lock();
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
test("filter — find an Asset; returns null for unknown query", async () => {
|
|
71
|
+
const root = mkdtempSync(join(tmpdir(), "wative-filter-asset-"));
|
|
72
|
+
const ws = await Workspace.open({ path: root, password: "wsp-pwd" });
|
|
73
|
+
|
|
74
|
+
const usdc = await ws.filter("USDC", "Asset");
|
|
75
|
+
assert.ok(usdc && usdc.symbol === "USDC");
|
|
76
|
+
|
|
77
|
+
const usdt = await ws.filter("0xdAC17F958D2ee523a2206206994597C13D831ec7", "Asset");
|
|
78
|
+
assert.ok(usdt && usdt.symbol === "USDT");
|
|
79
|
+
|
|
80
|
+
const missing = await ws.filter("DOES-NOT-EXIST", "Asset");
|
|
81
|
+
assert.strictEqual(missing, null);
|
|
82
|
+
|
|
83
|
+
await ws.lock();
|
|
84
|
+
});
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
// 10 — Account default network. `account.setDefaultNetwork(value)` accepts
|
|
2
|
+
// a slug, a chain id, a hex chain id, or a Network instance — and the choice
|
|
3
|
+
// persists across reopen.
|
|
4
|
+
|
|
5
|
+
const test = require("node:test");
|
|
6
|
+
const assert = require("node:assert");
|
|
7
|
+
const { mkdtempSync } = require("node:fs");
|
|
8
|
+
const { tmpdir } = require("node:os");
|
|
9
|
+
const { join } = require("node:path");
|
|
10
|
+
|
|
11
|
+
const { Workspace, Network } = require("wative-core");
|
|
12
|
+
|
|
13
|
+
const MNEMONIC =
|
|
14
|
+
"abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about";
|
|
15
|
+
|
|
16
|
+
test("setDefaultNetwork — accepts slug, chainId, hex chainId, and Network instance", async () => {
|
|
17
|
+
const root = mkdtempSync(join(tmpdir(), "wative-default-net-forms-"));
|
|
18
|
+
const ws = await Workspace.open({ path: root, password: "wsp-pwd" });
|
|
19
|
+
|
|
20
|
+
const acc = await ws.accounts.create("Desk", "wsp-pwd", MNEMONIC);
|
|
21
|
+
|
|
22
|
+
await acc.setDefaultNetwork("base");
|
|
23
|
+
assert.strictEqual(acc.defaultNetwork, "base");
|
|
24
|
+
|
|
25
|
+
await acc.setDefaultNetwork(8453);
|
|
26
|
+
assert.strictEqual(acc.defaultNetwork, "base");
|
|
27
|
+
|
|
28
|
+
await acc.setDefaultNetwork("0xa");
|
|
29
|
+
assert.strictEqual(acc.defaultNetwork, "optimism");
|
|
30
|
+
|
|
31
|
+
await acc.setDefaultNetwork(Network.Arbitrum);
|
|
32
|
+
assert.strictEqual(acc.defaultNetwork, "arbitrum");
|
|
33
|
+
|
|
34
|
+
await ws.lock();
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
test("setDefaultNetwork — rejects an unknown network", async () => {
|
|
38
|
+
const root = mkdtempSync(join(tmpdir(), "wative-default-net-bad-"));
|
|
39
|
+
const ws = await Workspace.open({ path: root, password: "wsp-pwd" });
|
|
40
|
+
|
|
41
|
+
const acc = await ws.accounts.create("Desk", "wsp-pwd", MNEMONIC);
|
|
42
|
+
|
|
43
|
+
await assert.rejects(
|
|
44
|
+
acc.setDefaultNetwork("does-not-exist"),
|
|
45
|
+
(err) => err.code === "UNSUPPORTED_NETWORK",
|
|
46
|
+
);
|
|
47
|
+
await assert.rejects(
|
|
48
|
+
acc.setDefaultNetwork(99999999),
|
|
49
|
+
(err) => err.code === "UNSUPPORTED_NETWORK",
|
|
50
|
+
);
|
|
51
|
+
|
|
52
|
+
await ws.lock();
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
test("setDefaultNetwork — choice persists across lock + reopen", async () => {
|
|
56
|
+
const root = mkdtempSync(join(tmpdir(), "wative-default-net-persist-"));
|
|
57
|
+
let ws = await Workspace.open({ path: root, password: "wsp-pwd" });
|
|
58
|
+
|
|
59
|
+
const acc = await ws.accounts.create("Desk", "wsp-pwd", MNEMONIC);
|
|
60
|
+
await acc.setDefaultNetwork(Network.Solana);
|
|
61
|
+
assert.strictEqual(acc.defaultNetwork, "solana");
|
|
62
|
+
|
|
63
|
+
await ws.lock();
|
|
64
|
+
ws = await Workspace.open({ path: root, password: "wsp-pwd" });
|
|
65
|
+
const reopened = ws.accounts.bySlug(acc.slug);
|
|
66
|
+
assert.ok(reopened);
|
|
67
|
+
assert.strictEqual(reopened.defaultNetwork, "solana");
|
|
68
|
+
|
|
69
|
+
await ws.lock();
|
|
70
|
+
});
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
// 11 — Subpath imports. Heavier chain helpers ship under `wative-core/artifacts/evm`
|
|
2
|
+
// and `wative-core/artifacts/svm` so they're only pulled in when used. In your
|
|
3
|
+
// own project: require("wative-core/artifacts/evm") / require("wative-core/artifacts/svm").
|
|
4
|
+
|
|
5
|
+
const test = require("node:test");
|
|
6
|
+
const assert = require("node:assert");
|
|
7
|
+
|
|
8
|
+
const evmArtifacts = require("../dist/artifacts/evm/index.cjs");
|
|
9
|
+
const svmArtifacts = require("../dist/artifacts/svm/index.cjs");
|
|
10
|
+
|
|
11
|
+
test("subpath import — evm artifacts expose a usable ERC20 contract", () => {
|
|
12
|
+
assert.ok(evmArtifacts.ERC20);
|
|
13
|
+
assert.strictEqual(typeof evmArtifacts.ERC20.encode, "function");
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
test("subpath import — svm artifacts expose TokenProgram and Token2022Program", () => {
|
|
17
|
+
assert.ok(svmArtifacts.TokenProgram);
|
|
18
|
+
assert.ok(svmArtifacts.Token2022Program);
|
|
19
|
+
assert.strictEqual(typeof svmArtifacts.TokenProgram.encodeInstruction, "function");
|
|
20
|
+
assert.strictEqual(typeof svmArtifacts.Token2022Program.encodeInstruction, "function");
|
|
21
|
+
});
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
// 12 — Logger. Workspace exposes `ws.logger` after unlock. Write structured
|
|
2
|
+
// logs at any level, change the level via `ws.logger.setLevel(...)`, and the
|
|
3
|
+
// configuration persists across reopen.
|
|
4
|
+
|
|
5
|
+
const test = require("node:test");
|
|
6
|
+
const assert = require("node:assert");
|
|
7
|
+
const { mkdtempSync } = require("node:fs");
|
|
8
|
+
const { tmpdir } = require("node:os");
|
|
9
|
+
const { join } = require("node:path");
|
|
10
|
+
|
|
11
|
+
const { Workspace } = require("wative-core");
|
|
12
|
+
|
|
13
|
+
test("logger — write at every level + change minLevel at runtime", async () => {
|
|
14
|
+
const root = mkdtempSync(join(tmpdir(), "wative-logger-write-"));
|
|
15
|
+
const ws = await Workspace.open({ path: root, password: "wsp-pwd" });
|
|
16
|
+
|
|
17
|
+
ws.logger.trace("trace event", { phase: "init" });
|
|
18
|
+
ws.logger.debug("debug event", { phase: "init" });
|
|
19
|
+
ws.logger.info("info event", { phase: "init" });
|
|
20
|
+
ws.logger.warn("warn event", { phase: "init" });
|
|
21
|
+
ws.logger.error("error event", { phase: "init" });
|
|
22
|
+
|
|
23
|
+
await ws.logger.setLevel("warn");
|
|
24
|
+
assert.strictEqual(ws.logger.config.minLevel, "warn");
|
|
25
|
+
|
|
26
|
+
ws.logger.warn("still works");
|
|
27
|
+
ws.logger.info("filtered out below minLevel");
|
|
28
|
+
|
|
29
|
+
await ws.lock();
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
test("logger — config persists across reopen", async () => {
|
|
33
|
+
const root = mkdtempSync(join(tmpdir(), "wative-logger-persist-"));
|
|
34
|
+
let ws = await Workspace.open({ path: root, password: "wsp-pwd" });
|
|
35
|
+
|
|
36
|
+
await ws.logger.setLevel("error");
|
|
37
|
+
assert.strictEqual(ws.logger.config.minLevel, "error");
|
|
38
|
+
|
|
39
|
+
await ws.lock();
|
|
40
|
+
ws = await Workspace.open({ path: root, password: "wsp-pwd" });
|
|
41
|
+
assert.strictEqual(ws.logger.config.minLevel, "error");
|
|
42
|
+
|
|
43
|
+
await ws.lock();
|
|
44
|
+
});
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
// 13 — Workspace config public API. Consumers should never need Provider
|
|
2
|
+
// record access to update business-wide settings.
|
|
3
|
+
|
|
4
|
+
const test = require("node:test");
|
|
5
|
+
const assert = require("node:assert");
|
|
6
|
+
const { mkdtempSync } = require("node:fs");
|
|
7
|
+
const { tmpdir } = require("node:os");
|
|
8
|
+
const { join } = require("node:path");
|
|
9
|
+
|
|
10
|
+
const { Workspace } = require("wative-core");
|
|
11
|
+
|
|
12
|
+
test("workspace config — setBusinessTimezone/getConfig work from release dist", async () => {
|
|
13
|
+
const root = mkdtempSync(join(tmpdir(), "wative-config-"));
|
|
14
|
+
const ws = await Workspace.open({ path: root, password: "wsp-pwd" });
|
|
15
|
+
|
|
16
|
+
await ws.setBusinessTimezone("UTC");
|
|
17
|
+
assert.deepStrictEqual(await ws.getConfig(), { businessTimezone: "UTC" });
|
|
18
|
+
|
|
19
|
+
await ws.setBusinessTimezone("Etc/UTC");
|
|
20
|
+
assert.deepStrictEqual(await ws.getConfig(), { businessTimezone: "Etc/UTC" });
|
|
21
|
+
|
|
22
|
+
await assert.rejects(ws.setBusinessTimezone("Not/AZone"), (err) => (
|
|
23
|
+
err.code === "PARAMETER_ERROR"
|
|
24
|
+
&& /INVALID_BUSINESS_TIMEZONE/.test(err.message)
|
|
25
|
+
&& !err.message.includes("Not/AZone")
|
|
26
|
+
));
|
|
27
|
+
|
|
28
|
+
// `config` hands back a frozen snapshot, not the live object. Assigning to it
|
|
29
|
+
// does not change the workspace — read config through `config` / `getConfig()`
|
|
30
|
+
// and change it through the setters.
|
|
31
|
+
const snapshot = ws.config;
|
|
32
|
+
assert.strictEqual(Object.isFrozen(snapshot), true);
|
|
33
|
+
snapshot.settlementWindow = "ny-close";
|
|
34
|
+
assert.strictEqual(snapshot.settlementWindow, undefined);
|
|
35
|
+
|
|
36
|
+
await ws.setBusinessTimezone("America/New_York");
|
|
37
|
+
assert.deepStrictEqual(await ws.getConfig(), { businessTimezone: "America/New_York" });
|
|
38
|
+
|
|
39
|
+
assert.strictEqual(Object.prototype.hasOwnProperty.call(ws, "_provider"), false);
|
|
40
|
+
assert.strictEqual("_provider" in ws, false);
|
|
41
|
+
|
|
42
|
+
await ws.lock();
|
|
43
|
+
});
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import assert from "node:assert/strict";
|
|
2
|
+
import { mkdtemp } from "node:fs/promises";
|
|
3
|
+
import { tmpdir } from "node:os";
|
|
4
|
+
import path from "node:path";
|
|
5
|
+
import test from "node:test";
|
|
6
|
+
import { fileURLToPath } from "node:url";
|
|
7
|
+
|
|
8
|
+
const here = path.dirname(fileURLToPath(import.meta.url));
|
|
9
|
+
|
|
10
|
+
async function importDist() {
|
|
11
|
+
// Import by PACKAGE SPECIFIER, not by file path. The root entry is
|
|
12
|
+
// condition-split: a raw path to dist/index.js bypasses the exports map and
|
|
13
|
+
// lands on the browser build, which deliberately has no filesystem backend.
|
|
14
|
+
// "wative-core" resolves through the node condition, which is what an ESM
|
|
15
|
+
// consumer actually gets — and still loads real ESM, so this keeps testing
|
|
16
|
+
// that the dist needs no CJS require shims.
|
|
17
|
+
return import("wative-core");
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const PASSWORD = "wsp-pwd";
|
|
21
|
+
const MNEMONIC = "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about";
|
|
22
|
+
const EVM_PK = "0x4f3edf983ac636a65a842ce7c78d9aa706d3b113bce9c46f30d7d21715b23b1d";
|
|
23
|
+
|
|
24
|
+
test("ESM dist consumer creates HD and PK accounts without global require shims", async () => {
|
|
25
|
+
const { Workspace } = await importDist();
|
|
26
|
+
assert.equal(globalThis.require, undefined);
|
|
27
|
+
const root = await mkdtemp(path.join(tmpdir(), "wative-core-esm-runtime-"));
|
|
28
|
+
const ws = await Workspace.open({ path: root, password: PASSWORD });
|
|
29
|
+
|
|
30
|
+
const hd = await ws.accounts.create("ESM HD", PASSWORD, MNEMONIC, undefined, { kind: "HD" });
|
|
31
|
+
const pk = await ws.accounts.create("ESM PK", PASSWORD, EVM_PK, undefined, { kind: "PK" });
|
|
32
|
+
|
|
33
|
+
assert.equal(hd.organizationType, "HD");
|
|
34
|
+
assert.equal(pk.organizationType, "PK");
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
test("ESM dist consumer signs EVM transactions without global require shims", async () => {
|
|
38
|
+
const { Workspace } = await importDist();
|
|
39
|
+
assert.equal(globalThis.require, undefined);
|
|
40
|
+
const root = await mkdtemp(path.join(tmpdir(), "wative-core-esm-tx-sign-"));
|
|
41
|
+
const ws = await Workspace.open({ path: root, password: PASSWORD });
|
|
42
|
+
const account = await ws.accounts.create("ESM TX PK", PASSWORD, EVM_PK, undefined, { kind: "PK" });
|
|
43
|
+
await account.tryUnlock(PASSWORD);
|
|
44
|
+
const address = account.wallets[0].addresses.find((candidate) => candidate.vm === "evm");
|
|
45
|
+
|
|
46
|
+
const tx = address.buildTransaction({
|
|
47
|
+
to: "0x000000000000000000000000000000000000dEaD",
|
|
48
|
+
value: "0",
|
|
49
|
+
nonce: 0,
|
|
50
|
+
chainId: 1,
|
|
51
|
+
gasLimit: "21000",
|
|
52
|
+
maxFeePerGas: "2000000000",
|
|
53
|
+
maxPriorityFeePerGas: "1000000000",
|
|
54
|
+
});
|
|
55
|
+
await tx.sign();
|
|
56
|
+
|
|
57
|
+
assert.match(tx.rawTransaction, /^0x[0-9a-fA-F]+$/);
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
test("ESM dist consumer signs SVM transactions without global require shims", async () => {
|
|
61
|
+
// Regression: the SVM signing path used require() for @solana/web3.js, bs58
|
|
62
|
+
// and tweetnacl, which throws "Dynamic require ... is not supported" under
|
|
63
|
+
// native ESM. These are now static imports like web3 on the EVM side.
|
|
64
|
+
const { Workspace } = await importDist();
|
|
65
|
+
assert.equal(globalThis.require, undefined);
|
|
66
|
+
const root = await mkdtemp(path.join(tmpdir(), "wative-core-esm-svm-sign-"));
|
|
67
|
+
const ws = await Workspace.open({ path: root, password: PASSWORD });
|
|
68
|
+
const account = await ws.accounts.create("ESM SVM HD", PASSWORD, MNEMONIC, undefined, { kind: "HD" });
|
|
69
|
+
await account.tryUnlock(PASSWORD);
|
|
70
|
+
const svm = account.wallets[0].addresses.find((candidate) => candidate.vm === "svm");
|
|
71
|
+
assert.ok(svm, "wallet exposes an SVM address");
|
|
72
|
+
|
|
73
|
+
const tx = svm.buildTransaction({
|
|
74
|
+
recipient: "11111111111111111111111111111111",
|
|
75
|
+
amount: "1000000",
|
|
76
|
+
recentBlockhash: "11111111111111111111111111111111",
|
|
77
|
+
});
|
|
78
|
+
await tx.sign();
|
|
79
|
+
assert.equal(tx.status, "signed");
|
|
80
|
+
assert.ok(typeof tx.hash === "string" && tx.hash.length > 0);
|
|
81
|
+
});
|