movehat 0.2.6 → 0.2.8
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/dist/cli.js +1 -1
- package/dist/commands/fork/fund.js +3 -1
- package/dist/commands/fork/serve.js +10 -5
- package/dist/commands/test-move.js +6 -3
- package/dist/core/AccountManager.d.ts +121 -139
- package/dist/core/AccountManager.js +217 -158
- package/dist/fork/api.d.ts +1 -1
- package/dist/fork/api.js +9 -4
- package/dist/fork/manager.d.ts +2 -2
- package/dist/fork/manager.js +4 -8
- package/dist/fork/storage.js +5 -4
- package/dist/fork/test.d.ts +1 -1
- package/dist/fork/validation.d.ts +9 -0
- package/dist/fork/validation.js +88 -0
- package/dist/harness/Harness.d.ts +35 -6
- package/dist/harness/Harness.js +36 -5
- package/dist/helpers/index.d.ts +1 -0
- package/dist/helpers/index.js +1 -0
- package/dist/helpers/move-tests.js +8 -5
- package/dist/helpers/setup.js +6 -3
- package/dist/helpers/setupLocalTesting.d.ts +2 -2
- package/dist/helpers/setupLocalTesting.js +55 -24
- package/dist/helpers/testFixtures.d.ts +5 -4
- package/dist/helpers/testFixtures.js +4 -5
- package/dist/helpers/version-check.js +4 -2
- package/dist/index.d.ts +3 -1
- package/dist/node/MoveliteManager.d.ts +18 -0
- package/dist/node/MoveliteManager.js +152 -0
- package/dist/node/NodeProvider.d.ts +9 -0
- package/dist/node/NodeProvider.js +1 -0
- package/dist/runtime.d.ts +13 -0
- package/dist/runtime.js +8 -4
- package/dist/templates/.mocharc.json +1 -0
- package/dist/templates/tests/Counter.test.ts +12 -14
- package/dist/templates/tests/setup.ts +34 -0
- package/dist/types/config.d.ts +2 -0
- package/dist/types/fork.d.ts +24 -0
- package/dist/types/runtime.d.ts +2 -0
- package/package.json +4 -1
|
@@ -1,59 +1,82 @@
|
|
|
1
1
|
import { Account, Ed25519PrivateKey, PrivateKey, PrivateKeyVariants, } from "@aptos-labs/ts-sdk";
|
|
2
2
|
import { chmodSync, existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
|
|
3
3
|
import { join } from "path";
|
|
4
|
+
import { logger } from "../ui/index.js";
|
|
4
5
|
/**
|
|
5
6
|
* Centralized Account Manager for movehat
|
|
6
7
|
*
|
|
7
8
|
* Manages all account creation, loading, and lifecycle operations.
|
|
8
|
-
* Provides a pool of reusable test accounts with labels for better test
|
|
9
|
+
* Provides a pool of reusable test accounts with labels for better test
|
|
10
|
+
* readability.
|
|
11
|
+
*
|
|
12
|
+
* Two API surfaces are exposed:
|
|
13
|
+
*
|
|
14
|
+
* **Instance API (recommended; M9.1+)** — `new AccountManager(options?)`
|
|
15
|
+
* gives a fully isolated pool. Two instances in the same process have
|
|
16
|
+
* independent labelMaps, private-key maps, and account pools. Each
|
|
17
|
+
* `Harness` constructed in 0.3.0+ will own one of these; user code
|
|
18
|
+
* accesses labeled accounts via `harness.accounts.<label>` (Harness
|
|
19
|
+
* snapshots them at construction time).
|
|
20
|
+
*
|
|
21
|
+
* **Static facade (deprecated; will be removed in 0.3.0)** — every
|
|
22
|
+
* former static method (`AccountManager.createAccount(...)`, etc.) still
|
|
23
|
+
* works and forwards to a single process-wide singleton. This preserves
|
|
24
|
+
* the existing label-sharing behavior across calls during the
|
|
25
|
+
* deprecation window. Migration target is the instance API; see M9 meta
|
|
26
|
+
* (#270) and migration guide at `/docs/upgrading/0.3.0` (published in
|
|
27
|
+
* M9.4).
|
|
9
28
|
*/
|
|
10
29
|
export class AccountManager {
|
|
11
|
-
//
|
|
12
|
-
//
|
|
13
|
-
//
|
|
14
|
-
//
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
30
|
+
// ── Instance state ──────────────────────────────────────────────
|
|
31
|
+
// All former class-static fields are now instance fields. Two
|
|
32
|
+
// `new AccountManager()` calls produce fully isolated state. The
|
|
33
|
+
// static facade below operates on a single module-level singleton,
|
|
34
|
+
// so existing callers see no behavior change.
|
|
35
|
+
pool = new Map(); // address → Account
|
|
36
|
+
privateKeys = new Map(); // address → privateKey hex
|
|
37
|
+
labelMap = new Map(); // label → address
|
|
38
|
+
generatedAccountAddresses = new Set();
|
|
39
|
+
poolLoaded = false;
|
|
40
|
+
poolPathOverride;
|
|
41
|
+
constructor(options = {}) {
|
|
42
|
+
this.poolPathOverride = options.poolPath;
|
|
43
|
+
}
|
|
25
44
|
/**
|
|
26
|
-
*
|
|
27
|
-
*
|
|
45
|
+
* Resolved pool directory. When the caller supplied an explicit
|
|
46
|
+
* `poolPath` to the constructor, that value is returned verbatim.
|
|
47
|
+
* Otherwise the path is computed LAZILY from the current
|
|
48
|
+
* `process.cwd()` on each access — so `process.chdir()` performed
|
|
49
|
+
* between construction and pool I/O takes effect, unlike the legacy
|
|
50
|
+
* static-only API which captured cwd at module import time
|
|
51
|
+
* (audit finding F8(b)).
|
|
52
|
+
*/
|
|
53
|
+
get poolPath() {
|
|
54
|
+
return this.poolPathOverride ?? join(process.cwd(), ".movehat", "accounts");
|
|
55
|
+
}
|
|
56
|
+
// ── Instance methods (real implementations) ─────────────────────
|
|
57
|
+
/**
|
|
58
|
+
* Get a test account from the pool. If label is provided and exists,
|
|
59
|
+
* returns that account. Otherwise creates a new account with the
|
|
60
|
+
* optional label.
|
|
28
61
|
*
|
|
29
|
-
* @param label Optional label for the account (e.g., "alice", "bob"
|
|
62
|
+
* @param label Optional label for the account (e.g., "alice", "bob")
|
|
30
63
|
* @returns An Account instance
|
|
31
|
-
*
|
|
32
|
-
* @example
|
|
33
|
-
* const alice = AccountManager.getTestAccount("alice");
|
|
34
|
-
* const randomAccount = AccountManager.getTestAccount();
|
|
35
64
|
*/
|
|
36
|
-
|
|
65
|
+
getTestAccount(label) {
|
|
37
66
|
if (label) {
|
|
38
|
-
const
|
|
39
|
-
if (
|
|
40
|
-
return
|
|
67
|
+
const existing = this.getAccountByLabel(label);
|
|
68
|
+
if (existing) {
|
|
69
|
+
return existing;
|
|
41
70
|
}
|
|
42
71
|
}
|
|
43
72
|
return this.createAccount(label, false);
|
|
44
73
|
}
|
|
45
74
|
/**
|
|
46
|
-
* Create a new account and optionally add it to the pool with a
|
|
47
|
-
*
|
|
48
|
-
*
|
|
49
|
-
* @param fund Whether to fund the account (handled externally)
|
|
50
|
-
* @returns A new Account instance
|
|
51
|
-
*
|
|
52
|
-
* @example
|
|
53
|
-
* const deployer = AccountManager.createAccount("deployer");
|
|
54
|
-
* const bob = AccountManager.createAccount("bob", true);
|
|
75
|
+
* Create a new account and optionally add it to the pool with a
|
|
76
|
+
* label. The `fund` parameter is accepted for API symmetry; actual
|
|
77
|
+
* funding is the caller's responsibility.
|
|
55
78
|
*/
|
|
56
|
-
|
|
79
|
+
createAccount(label, _fund = false) {
|
|
57
80
|
const account = Account.generate();
|
|
58
81
|
const address = account.accountAddress.toString();
|
|
59
82
|
this.pool.set(address, account);
|
|
@@ -65,18 +88,9 @@ export class AccountManager {
|
|
|
65
88
|
return account;
|
|
66
89
|
}
|
|
67
90
|
/**
|
|
68
|
-
* Get an account by its label
|
|
69
|
-
*
|
|
70
|
-
* @param label The label to look up
|
|
71
|
-
* @returns The Account if found, undefined otherwise
|
|
72
|
-
*
|
|
73
|
-
* @example
|
|
74
|
-
* const alice = AccountManager.getAccountByLabel("alice");
|
|
75
|
-
* if (alice) {
|
|
76
|
-
* console.log(`Alice's address: ${alice.accountAddress.toString()}`);
|
|
77
|
-
* }
|
|
91
|
+
* Get an account by its label, or undefined if not present.
|
|
78
92
|
*/
|
|
79
|
-
|
|
93
|
+
getAccountByLabel(label) {
|
|
80
94
|
const address = this.labelMap.get(label);
|
|
81
95
|
if (!address) {
|
|
82
96
|
return undefined;
|
|
@@ -84,16 +98,11 @@ export class AccountManager {
|
|
|
84
98
|
return this.pool.get(address);
|
|
85
99
|
}
|
|
86
100
|
/**
|
|
87
|
-
* Load account from environment variable
|
|
88
|
-
*
|
|
89
|
-
* @param envVar The environment variable name (defaults to "PRIVATE_KEY")
|
|
90
|
-
* @returns Account instance
|
|
91
|
-
* @throws Error if environment variable is not set
|
|
101
|
+
* Load account from environment variable.
|
|
92
102
|
*
|
|
93
|
-
* @
|
|
94
|
-
* const account = AccountManager.loadAccountFromEnv("MH_PRIVATE_KEY");
|
|
103
|
+
* @throws Error if the environment variable is not set
|
|
95
104
|
*/
|
|
96
|
-
|
|
105
|
+
loadAccountFromEnv(envVar = "PRIVATE_KEY") {
|
|
97
106
|
const privateKeyHex = process.env[envVar];
|
|
98
107
|
if (!privateKeyHex) {
|
|
99
108
|
throw new Error(`Environment variable ${envVar} not found. ` +
|
|
@@ -102,15 +111,9 @@ export class AccountManager {
|
|
|
102
111
|
return this.loadAccountFromPrivateKey(privateKeyHex);
|
|
103
112
|
}
|
|
104
113
|
/**
|
|
105
|
-
* Load account from a private key hex string
|
|
106
|
-
*
|
|
107
|
-
* @param privateKeyHex The private key as a hex string (with or without 0x prefix)
|
|
108
|
-
* @returns Account instance
|
|
109
|
-
*
|
|
110
|
-
* @example
|
|
111
|
-
* const account = AccountManager.loadAccountFromPrivateKey("0xabc123...");
|
|
114
|
+
* Load account from a private key hex string (with or without 0x prefix).
|
|
112
115
|
*/
|
|
113
|
-
|
|
116
|
+
loadAccountFromPrivateKey(privateKeyHex) {
|
|
114
117
|
// Format into AIP-80 shape (`ed25519-priv-0x…`) before constructing
|
|
115
118
|
// the SDK type. Without this, raw-hex inputs trigger a noisy
|
|
116
119
|
// deprecation warning from `@aptos-labs/ts-sdk` on every call.
|
|
@@ -119,16 +122,9 @@ export class AccountManager {
|
|
|
119
122
|
return account;
|
|
120
123
|
}
|
|
121
124
|
/**
|
|
122
|
-
* Load all accounts from movehat config
|
|
123
|
-
*
|
|
124
|
-
* @param config The resolved MovehatConfig
|
|
125
|
-
* @returns Array of Account instances
|
|
126
|
-
*
|
|
127
|
-
* @example
|
|
128
|
-
* const config = await resolveNetworkConfig(userConfig);
|
|
129
|
-
* const accounts = AccountManager.loadAccountsFromConfig(config);
|
|
125
|
+
* Load all accounts from movehat config.
|
|
130
126
|
*/
|
|
131
|
-
|
|
127
|
+
loadAccountsFromConfig(config) {
|
|
132
128
|
const accounts = [];
|
|
133
129
|
for (const privateKeyHex of config.allAccounts) {
|
|
134
130
|
try {
|
|
@@ -137,21 +133,15 @@ export class AccountManager {
|
|
|
137
133
|
}
|
|
138
134
|
catch (error) {
|
|
139
135
|
const msg = error instanceof Error ? error.message : String(error);
|
|
140
|
-
|
|
136
|
+
logger.warning(`Failed to load account from config: ${msg}`);
|
|
141
137
|
}
|
|
142
138
|
}
|
|
143
139
|
return accounts;
|
|
144
140
|
}
|
|
145
141
|
/**
|
|
146
|
-
* Get all labeled accounts in the pool
|
|
147
|
-
*
|
|
148
|
-
* @returns Record of label to Account mappings
|
|
149
|
-
*
|
|
150
|
-
* @example
|
|
151
|
-
* const labeled = AccountManager.getLabeledAccounts();
|
|
152
|
-
* console.log(`Deployer: ${labeled.deployer?.accountAddress.toString()}`);
|
|
142
|
+
* Get all labeled accounts in the pool.
|
|
153
143
|
*/
|
|
154
|
-
|
|
144
|
+
getLabeledAccounts() {
|
|
155
145
|
const result = {};
|
|
156
146
|
for (const [label, address] of this.labelMap.entries()) {
|
|
157
147
|
const account = this.pool.get(address);
|
|
@@ -161,25 +151,25 @@ export class AccountManager {
|
|
|
161
151
|
}
|
|
162
152
|
return result;
|
|
163
153
|
}
|
|
164
|
-
|
|
165
|
-
const
|
|
154
|
+
saveAccountPool(poolPathOrOptions, options = {}) {
|
|
155
|
+
const explicitPath = typeof poolPathOrOptions === "string" ? poolPathOrOptions : undefined;
|
|
166
156
|
const effectiveOptions = typeof poolPathOrOptions === "object" && poolPathOrOptions !== null
|
|
167
157
|
? poolPathOrOptions
|
|
168
158
|
: options;
|
|
169
159
|
const includeImported = effectiveOptions.includeImported ?? false;
|
|
170
|
-
const basePath =
|
|
160
|
+
const basePath = explicitPath || this.poolPath;
|
|
171
161
|
// Ensure directory exists with restrictive perms (the pool file holds
|
|
172
162
|
// plaintext private keys, so the directory must not be world-readable).
|
|
173
|
-
//
|
|
174
|
-
//
|
|
163
|
+
// mkdirSync's mode is masked by the process umask; chmod explicitly
|
|
164
|
+
// afterwards to guarantee 0o700.
|
|
175
165
|
if (!existsSync(basePath)) {
|
|
176
166
|
mkdirSync(basePath, { recursive: true, mode: 0o700 });
|
|
177
167
|
}
|
|
178
168
|
chmodSync(basePath, 0o700);
|
|
179
|
-
// Build stored accounts array
|
|
180
169
|
const storedAccounts = [];
|
|
181
170
|
const labelMapObject = {};
|
|
182
171
|
for (const [address, account] of this.pool.entries()) {
|
|
172
|
+
void account;
|
|
183
173
|
if (!includeImported && !this.generatedAccountAddresses.has(address)) {
|
|
184
174
|
continue;
|
|
185
175
|
}
|
|
@@ -194,7 +184,7 @@ export class AccountManager {
|
|
|
194
184
|
}
|
|
195
185
|
const privateKey = this.privateKeys.get(address);
|
|
196
186
|
if (!privateKey) {
|
|
197
|
-
|
|
187
|
+
logger.warning(`No private key found for account ${address}, skipping`);
|
|
198
188
|
continue;
|
|
199
189
|
}
|
|
200
190
|
storedAccounts.push({
|
|
@@ -208,10 +198,6 @@ export class AccountManager {
|
|
|
208
198
|
accounts: storedAccounts,
|
|
209
199
|
labelMap: labelMapObject,
|
|
210
200
|
};
|
|
211
|
-
// Write to file with owner-only permissions — file contains plaintext
|
|
212
|
-
// private keys for test accounts. writeFileSync's mode is masked by
|
|
213
|
-
// the process umask, so we chmod explicitly afterwards to guarantee
|
|
214
|
-
// 0o600 regardless of umask.
|
|
215
201
|
const poolFilePath = join(basePath, "test-pool.json");
|
|
216
202
|
writeFileSync(poolFilePath, JSON.stringify(poolData, null, 2), {
|
|
217
203
|
encoding: "utf-8",
|
|
@@ -220,31 +206,22 @@ export class AccountManager {
|
|
|
220
206
|
chmodSync(poolFilePath, 0o600);
|
|
221
207
|
}
|
|
222
208
|
/**
|
|
223
|
-
* Load account pool from disk
|
|
224
|
-
*
|
|
225
|
-
* @param poolPath Optional custom path (defaults to .movehat/accounts)
|
|
226
|
-
* @returns true if pool was loaded, false if file doesn't exist
|
|
227
|
-
*
|
|
228
|
-
* @example
|
|
229
|
-
* if (AccountManager.loadAccountPool()) {
|
|
230
|
-
* console.log("Pool loaded successfully");
|
|
231
|
-
* }
|
|
209
|
+
* Load account pool from disk. Returns true if a pool file was found
|
|
210
|
+
* and parsed, false if the file does not exist or parsing failed.
|
|
232
211
|
*/
|
|
233
|
-
|
|
212
|
+
loadAccountPool(poolPath) {
|
|
234
213
|
if (this.poolLoaded) {
|
|
235
|
-
return true;
|
|
214
|
+
return true;
|
|
236
215
|
}
|
|
237
|
-
const basePath = poolPath || this.
|
|
216
|
+
const basePath = poolPath || this.poolPath;
|
|
238
217
|
const poolFilePath = join(basePath, "test-pool.json");
|
|
239
218
|
if (!existsSync(poolFilePath)) {
|
|
240
219
|
return false;
|
|
241
220
|
}
|
|
242
221
|
try {
|
|
243
222
|
const poolData = JSON.parse(readFileSync(poolFilePath, "utf-8"));
|
|
244
|
-
// Clear existing pool
|
|
245
223
|
this.pool.clear();
|
|
246
224
|
this.labelMap.clear();
|
|
247
|
-
// Restore accounts
|
|
248
225
|
for (const stored of poolData.accounts) {
|
|
249
226
|
const account = this.accountFromPrivateKey(stored.privateKey);
|
|
250
227
|
this.trackAccount(account, stored.privateKey, "generated");
|
|
@@ -263,20 +240,14 @@ export class AccountManager {
|
|
|
263
240
|
}
|
|
264
241
|
catch (error) {
|
|
265
242
|
const msg = error instanceof Error ? error.message : String(error);
|
|
266
|
-
|
|
243
|
+
logger.warning(`Failed to load account pool: ${msg}`);
|
|
267
244
|
return false;
|
|
268
245
|
}
|
|
269
246
|
}
|
|
270
247
|
/**
|
|
271
|
-
* Clear the entire account pool
|
|
272
|
-
* Useful for test isolation
|
|
273
|
-
*
|
|
274
|
-
* @example
|
|
275
|
-
* after(() => {
|
|
276
|
-
* AccountManager.clearPool();
|
|
277
|
-
* });
|
|
248
|
+
* Clear the entire account pool. Useful for test isolation.
|
|
278
249
|
*/
|
|
279
|
-
|
|
250
|
+
clearPool() {
|
|
280
251
|
this.pool.clear();
|
|
281
252
|
this.privateKeys.clear();
|
|
282
253
|
this.labelMap.clear();
|
|
@@ -284,43 +255,27 @@ export class AccountManager {
|
|
|
284
255
|
this.poolLoaded = false;
|
|
285
256
|
}
|
|
286
257
|
/**
|
|
287
|
-
*
|
|
288
|
-
*
|
|
289
|
-
* @returns Number of accounts in the pool
|
|
258
|
+
* Number of accounts in the pool.
|
|
290
259
|
*/
|
|
291
|
-
|
|
260
|
+
getPoolSize() {
|
|
292
261
|
return this.pool.size;
|
|
293
262
|
}
|
|
294
263
|
/**
|
|
295
|
-
*
|
|
296
|
-
*
|
|
297
|
-
* @returns Array of all Account instances
|
|
264
|
+
* All accounts in the pool.
|
|
298
265
|
*/
|
|
299
|
-
|
|
266
|
+
getAllAccounts() {
|
|
300
267
|
return Array.from(this.pool.values());
|
|
301
268
|
}
|
|
302
269
|
/**
|
|
303
|
-
*
|
|
304
|
-
*
|
|
305
|
-
* @param label The label to check
|
|
306
|
-
* @returns true if label exists, false otherwise
|
|
270
|
+
* Whether a label is currently bound to an account in this pool.
|
|
307
271
|
*/
|
|
308
|
-
|
|
272
|
+
hasLabel(label) {
|
|
309
273
|
return this.labelMap.has(label);
|
|
310
274
|
}
|
|
311
275
|
/**
|
|
312
|
-
* Get or create an account with a specific label
|
|
313
|
-
* If the label exists, returns the existing account
|
|
314
|
-
* Otherwise creates a new account with that label
|
|
315
|
-
*
|
|
316
|
-
* @param label The label for the account
|
|
317
|
-
* @returns Account instance
|
|
318
|
-
*
|
|
319
|
-
* @example
|
|
320
|
-
* const alice = AccountManager.getOrCreateLabeled("alice");
|
|
321
|
-
* const aliceAgain = AccountManager.getOrCreateLabeled("alice"); // Same account
|
|
276
|
+
* Get or create an account with a specific label.
|
|
322
277
|
*/
|
|
323
|
-
|
|
278
|
+
getOrCreateLabeled(label) {
|
|
324
279
|
const existing = this.getAccountByLabel(label);
|
|
325
280
|
if (existing) {
|
|
326
281
|
return existing;
|
|
@@ -328,16 +283,9 @@ export class AccountManager {
|
|
|
328
283
|
return this.createAccount(label, false);
|
|
329
284
|
}
|
|
330
285
|
/**
|
|
331
|
-
* Batch create multiple labeled accounts
|
|
332
|
-
*
|
|
333
|
-
* @param labels Array of labels to create
|
|
334
|
-
* @returns Record of label to Account mappings
|
|
335
|
-
*
|
|
336
|
-
* @example
|
|
337
|
-
* const accounts = AccountManager.createBatch(["alice", "bob", "charlie"]);
|
|
338
|
-
* console.log(accounts.alice.accountAddress.toString());
|
|
286
|
+
* Batch create multiple labeled accounts.
|
|
339
287
|
*/
|
|
340
|
-
|
|
288
|
+
createBatch(labels) {
|
|
341
289
|
const result = {};
|
|
342
290
|
for (const label of labels) {
|
|
343
291
|
result[label] = this.getOrCreateLabeled(label);
|
|
@@ -345,16 +293,12 @@ export class AccountManager {
|
|
|
345
293
|
return result;
|
|
346
294
|
}
|
|
347
295
|
/**
|
|
348
|
-
* Export account private keys for backup/sharing
|
|
349
|
-
* WARNING: Only use this for test accounts, never production keys
|
|
350
|
-
*
|
|
351
|
-
* @param labels Optional array of labels to export (exports all if not provided)
|
|
352
|
-
* @returns Record of label/address to private key hex string
|
|
296
|
+
* Export account private keys for backup/sharing.
|
|
297
|
+
* WARNING: Only use this for test accounts, never production keys.
|
|
353
298
|
*/
|
|
354
|
-
|
|
299
|
+
exportPrivateKeys(labels) {
|
|
355
300
|
const result = {};
|
|
356
301
|
if (labels) {
|
|
357
|
-
// Export specific labels
|
|
358
302
|
for (const label of labels) {
|
|
359
303
|
const address = this.labelMap.get(label);
|
|
360
304
|
if (address) {
|
|
@@ -366,7 +310,6 @@ export class AccountManager {
|
|
|
366
310
|
}
|
|
367
311
|
}
|
|
368
312
|
else {
|
|
369
|
-
// Export all labeled accounts
|
|
370
313
|
for (const [label, address] of this.labelMap.entries()) {
|
|
371
314
|
const privateKey = this.privateKeys.get(address);
|
|
372
315
|
if (privateKey) {
|
|
@@ -376,12 +319,12 @@ export class AccountManager {
|
|
|
376
319
|
}
|
|
377
320
|
return result;
|
|
378
321
|
}
|
|
379
|
-
|
|
322
|
+
accountFromPrivateKey(privateKeyHex) {
|
|
380
323
|
const formatted = PrivateKey.formatPrivateKey(privateKeyHex, PrivateKeyVariants.Ed25519);
|
|
381
324
|
const privateKey = new Ed25519PrivateKey(formatted);
|
|
382
325
|
return Account.fromPrivateKey({ privateKey });
|
|
383
326
|
}
|
|
384
|
-
|
|
327
|
+
trackAccount(account, privateKeyHex, source) {
|
|
385
328
|
const address = account.accountAddress.toString();
|
|
386
329
|
this.pool.set(address, account);
|
|
387
330
|
this.privateKeys.set(address, privateKeyHex);
|
|
@@ -392,4 +335,120 @@ export class AccountManager {
|
|
|
392
335
|
this.generatedAccountAddresses.delete(address);
|
|
393
336
|
}
|
|
394
337
|
}
|
|
338
|
+
// ── Static facade (deprecated; removed in 0.3.0) ────────────────
|
|
339
|
+
//
|
|
340
|
+
// Every former static method below forwards to `__defaultManager`,
|
|
341
|
+
// a module-level singleton constructed once at import time. This
|
|
342
|
+
// preserves the legacy "process-wide pool shared across consumers"
|
|
343
|
+
// behavior so existing callers see no change during the 0.2.7
|
|
344
|
+
// deprecation window. The singleton is constructed with its
|
|
345
|
+
// `poolPath` eagerly captured from the import-time `process.cwd()`
|
|
346
|
+
// — preserving F8(b) for the static API specifically.
|
|
347
|
+
//
|
|
348
|
+
// Removal happens in M9.4 (0.3.0). Until then the runtime warning
|
|
349
|
+
// added in M9.3 nudges users toward `harness.accounts.<label>` or
|
|
350
|
+
// `harness.runtime.accountManager.*`.
|
|
351
|
+
static getTestAccount(label) {
|
|
352
|
+
__warnDeprecated("getTestAccount");
|
|
353
|
+
return __defaultManager.getTestAccount(label);
|
|
354
|
+
}
|
|
355
|
+
static createAccount(label, fund = false) {
|
|
356
|
+
__warnDeprecated("createAccount");
|
|
357
|
+
return __defaultManager.createAccount(label, fund);
|
|
358
|
+
}
|
|
359
|
+
static getAccountByLabel(label) {
|
|
360
|
+
__warnDeprecated("getAccountByLabel");
|
|
361
|
+
return __defaultManager.getAccountByLabel(label);
|
|
362
|
+
}
|
|
363
|
+
static loadAccountFromEnv(envVar = "PRIVATE_KEY") {
|
|
364
|
+
__warnDeprecated("loadAccountFromEnv");
|
|
365
|
+
return __defaultManager.loadAccountFromEnv(envVar);
|
|
366
|
+
}
|
|
367
|
+
static loadAccountFromPrivateKey(privateKeyHex) {
|
|
368
|
+
__warnDeprecated("loadAccountFromPrivateKey");
|
|
369
|
+
return __defaultManager.loadAccountFromPrivateKey(privateKeyHex);
|
|
370
|
+
}
|
|
371
|
+
static loadAccountsFromConfig(config) {
|
|
372
|
+
__warnDeprecated("loadAccountsFromConfig");
|
|
373
|
+
return __defaultManager.loadAccountsFromConfig(config);
|
|
374
|
+
}
|
|
375
|
+
static getLabeledAccounts() {
|
|
376
|
+
__warnDeprecated("getLabeledAccounts");
|
|
377
|
+
return __defaultManager.getLabeledAccounts();
|
|
378
|
+
}
|
|
379
|
+
static saveAccountPool(poolPathOrOptions, options = {}) {
|
|
380
|
+
__warnDeprecated("saveAccountPool");
|
|
381
|
+
// Re-dispatch via the instance overloads, preserving caller intent.
|
|
382
|
+
if (poolPathOrOptions === undefined) {
|
|
383
|
+
__defaultManager.saveAccountPool();
|
|
384
|
+
}
|
|
385
|
+
else if (typeof poolPathOrOptions === "string") {
|
|
386
|
+
__defaultManager.saveAccountPool(poolPathOrOptions, options);
|
|
387
|
+
}
|
|
388
|
+
else {
|
|
389
|
+
__defaultManager.saveAccountPool(poolPathOrOptions);
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
static loadAccountPool(poolPath) {
|
|
393
|
+
__warnDeprecated("loadAccountPool");
|
|
394
|
+
return __defaultManager.loadAccountPool(poolPath);
|
|
395
|
+
}
|
|
396
|
+
static clearPool() {
|
|
397
|
+
__warnDeprecated("clearPool");
|
|
398
|
+
__defaultManager.clearPool();
|
|
399
|
+
}
|
|
400
|
+
static getPoolSize() {
|
|
401
|
+
__warnDeprecated("getPoolSize");
|
|
402
|
+
return __defaultManager.getPoolSize();
|
|
403
|
+
}
|
|
404
|
+
static getAllAccounts() {
|
|
405
|
+
__warnDeprecated("getAllAccounts");
|
|
406
|
+
return __defaultManager.getAllAccounts();
|
|
407
|
+
}
|
|
408
|
+
static hasLabel(label) {
|
|
409
|
+
__warnDeprecated("hasLabel");
|
|
410
|
+
return __defaultManager.hasLabel(label);
|
|
411
|
+
}
|
|
412
|
+
static getOrCreateLabeled(label) {
|
|
413
|
+
__warnDeprecated("getOrCreateLabeled");
|
|
414
|
+
return __defaultManager.getOrCreateLabeled(label);
|
|
415
|
+
}
|
|
416
|
+
static createBatch(labels) {
|
|
417
|
+
__warnDeprecated("createBatch");
|
|
418
|
+
return __defaultManager.createBatch(labels);
|
|
419
|
+
}
|
|
420
|
+
static exportPrivateKeys(labels) {
|
|
421
|
+
__warnDeprecated("exportPrivateKeys");
|
|
422
|
+
return __defaultManager.exportPrivateKeys(labels);
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
// Module-level singleton backing the static facade. The eager poolPath
|
|
426
|
+
// capture mirrors the pre-M9 behavior — `process.chdir` after import
|
|
427
|
+
// does NOT redirect static-facade pool I/O. F8(b) is preserved for
|
|
428
|
+
// the static API and only flips for callers using the instance API.
|
|
429
|
+
const __defaultManager = new AccountManager({
|
|
430
|
+
poolPath: join(process.cwd(), ".movehat", "accounts"),
|
|
431
|
+
});
|
|
432
|
+
// Once-per-method-per-process deprecation tracking for the static
|
|
433
|
+
// facade. The warning fires on the FIRST call to each static method
|
|
434
|
+
// per process, then stays silent for subsequent calls. A user with
|
|
435
|
+
// 50 tests calling `AccountManager.getLabeledAccounts()` sees the
|
|
436
|
+
// warning once, not 50 times.
|
|
437
|
+
//
|
|
438
|
+
// Exported as `_resetDeprecationWarnings` for tests that need to
|
|
439
|
+
// verify warning behavior across multiple `it` blocks (vitest workers
|
|
440
|
+
// share module state within a single test file).
|
|
441
|
+
const __warnedMethods = new Set();
|
|
442
|
+
/** @internal — test-only. Resets the once-per-method dedup Set. */
|
|
443
|
+
export function _resetDeprecationWarnings() {
|
|
444
|
+
__warnedMethods.clear();
|
|
445
|
+
}
|
|
446
|
+
function __warnDeprecated(method) {
|
|
447
|
+
if (__warnedMethods.has(method))
|
|
448
|
+
return;
|
|
449
|
+
__warnedMethods.add(method);
|
|
450
|
+
logger.warning(`AccountManager.${method} is deprecated and will be removed in 0.3.0. ` +
|
|
451
|
+
`Use 'harness.accounts.<label>' for labeled-account access, or ` +
|
|
452
|
+
`'harness.runtime.accountManager.${method}' for direct manager calls. ` +
|
|
453
|
+
`See migration tracker: https://github.com/gilbertsahumada/movehat/issues/270`);
|
|
395
454
|
}
|
package/dist/fork/api.d.ts
CHANGED
|
@@ -55,7 +55,7 @@ export declare class MovementApiClient {
|
|
|
55
55
|
/**
|
|
56
56
|
* Get a specific account resource
|
|
57
57
|
*/
|
|
58
|
-
getAccountResource(address: string, resourceType: string): Promise<
|
|
58
|
+
getAccountResource(address: string, resourceType: string): Promise<AccountResource>;
|
|
59
59
|
/**
|
|
60
60
|
* Get all resources for an account
|
|
61
61
|
*/
|
package/dist/fork/api.js
CHANGED
|
@@ -2,6 +2,7 @@ import https from 'https';
|
|
|
2
2
|
import http from 'http';
|
|
3
3
|
import { URL } from 'url';
|
|
4
4
|
import { normalizeAddressShort } from '../utils/address.js';
|
|
5
|
+
import { assertLedgerInfo, assertAccountData, assertAccountResource, assertAccountResourceArray, } from './validation.js';
|
|
5
6
|
const DEFAULT_TIMEOUT_MS = 30_000;
|
|
6
7
|
const DEFAULT_MAX_BYTES = 16 * 1024 * 1024;
|
|
7
8
|
/**
|
|
@@ -191,14 +192,16 @@ export class MovementApiClient {
|
|
|
191
192
|
* Get ledger information
|
|
192
193
|
*/
|
|
193
194
|
async getLedgerInfo() {
|
|
194
|
-
|
|
195
|
+
const raw = await this.get(this.apiPath('/'));
|
|
196
|
+
return assertLedgerInfo(raw);
|
|
195
197
|
}
|
|
196
198
|
/**
|
|
197
199
|
* Get account information
|
|
198
200
|
*/
|
|
199
201
|
async getAccount(address) {
|
|
200
202
|
const normalizedAddress = normalizeAddressShort(address);
|
|
201
|
-
|
|
203
|
+
const raw = await this.get(this.apiPath(`/accounts/${normalizedAddress}`));
|
|
204
|
+
return assertAccountData(raw);
|
|
202
205
|
}
|
|
203
206
|
/**
|
|
204
207
|
* Get a specific account resource
|
|
@@ -207,14 +210,16 @@ export class MovementApiClient {
|
|
|
207
210
|
const normalizedAddress = normalizeAddressShort(address);
|
|
208
211
|
// URL encode the resource type
|
|
209
212
|
const encodedType = encodeURIComponent(resourceType);
|
|
210
|
-
|
|
213
|
+
const raw = await this.get(this.apiPath(`/accounts/${normalizedAddress}/resource/${encodedType}`));
|
|
214
|
+
return assertAccountResource(raw);
|
|
211
215
|
}
|
|
212
216
|
/**
|
|
213
217
|
* Get all resources for an account
|
|
214
218
|
*/
|
|
215
219
|
async getAccountResources(address) {
|
|
216
220
|
const normalizedAddress = normalizeAddressShort(address);
|
|
217
|
-
|
|
221
|
+
const raw = await this.get(this.apiPath(`/accounts/${normalizedAddress}/resources`));
|
|
222
|
+
return assertAccountResourceArray(raw);
|
|
218
223
|
}
|
|
219
224
|
/**
|
|
220
225
|
* Execute a Move view function via the upstream node's POST /v1/view.
|
package/dist/fork/manager.d.ts
CHANGED
|
@@ -36,8 +36,8 @@ export declare class ForkManager {
|
|
|
36
36
|
load(): void;
|
|
37
37
|
getMetadata(): ForkMetadata;
|
|
38
38
|
getAccount(address: string): Promise<AccountState>;
|
|
39
|
-
getResource(address: string, resourceType: string): Promise<
|
|
40
|
-
getAllResources(address: string): Promise<Record<string,
|
|
39
|
+
getResource(address: string, resourceType: string): Promise<unknown>;
|
|
40
|
+
getAllResources(address: string): Promise<Record<string, unknown>>;
|
|
41
41
|
/**
|
|
42
42
|
* Stateless passthrough of `POST /v1/view` to the upstream RPC.
|
|
43
43
|
*
|
package/dist/fork/manager.js
CHANGED
|
@@ -3,6 +3,7 @@ import { MovementApiClient } from './api.js';
|
|
|
3
3
|
import { ForkStorage } from './storage.js';
|
|
4
4
|
import { normalizeAddress } from '../utils/address.js';
|
|
5
5
|
import { logger } from '../ui/index.js';
|
|
6
|
+
import { assertCoinStore } from './validation.js';
|
|
6
7
|
/**
|
|
7
8
|
* Derive a deterministic 32-byte hex placeholder for the `authentication_key`
|
|
8
9
|
* of a fork-funded account. The real auth_key is `sha3_256(public_key || 0x00)`
|
|
@@ -182,17 +183,12 @@ export class ForkManager {
|
|
|
182
183
|
async fundAccount(address, amount, coinType = '0x1::aptos_coin::AptosCoin') {
|
|
183
184
|
const normalizedAddress = normalizeAddress(address);
|
|
184
185
|
const resourceType = `0x1::coin::CoinStore<${coinType}>`;
|
|
185
|
-
// Try to get existing coin store. The coin store is a CoinStore<T>
|
|
186
|
-
// resource whose `data` is Movement-side untyped JSON; we shape it
|
|
187
|
-
// locally as a structural object with `coin.value: string`.
|
|
188
|
-
// any: full CoinStore schema lives at the Movement REST boundary —
|
|
189
|
-
// proper validation deferred to the boundary-validation follow-up of #57.
|
|
190
186
|
let coinStore;
|
|
191
187
|
try {
|
|
192
|
-
|
|
188
|
+
const raw = await this.getResource(normalizedAddress, resourceType);
|
|
189
|
+
coinStore = assertCoinStore(raw);
|
|
193
190
|
}
|
|
194
191
|
catch (error) {
|
|
195
|
-
// Only catch "not found" errors, rethrow others (network, API, etc.)
|
|
196
192
|
const msg = error instanceof Error ? error.message : String(error);
|
|
197
193
|
if (!msg.includes('not found')) {
|
|
198
194
|
throw error;
|
|
@@ -220,7 +216,7 @@ export class ForkManager {
|
|
|
220
216
|
frozen: false,
|
|
221
217
|
};
|
|
222
218
|
}
|
|
223
|
-
const currentBalance = BigInt(coinStore.coin.value
|
|
219
|
+
const currentBalance = BigInt(coinStore.coin.value);
|
|
224
220
|
const newBalance = currentBalance + BigInt(amount);
|
|
225
221
|
coinStore.coin.value = newBalance.toString();
|
|
226
222
|
await this.setResource(normalizedAddress, resourceType, coinStore);
|