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.
Files changed (39) hide show
  1. package/dist/cli.js +1 -1
  2. package/dist/commands/fork/fund.js +3 -1
  3. package/dist/commands/fork/serve.js +10 -5
  4. package/dist/commands/test-move.js +6 -3
  5. package/dist/core/AccountManager.d.ts +121 -139
  6. package/dist/core/AccountManager.js +217 -158
  7. package/dist/fork/api.d.ts +1 -1
  8. package/dist/fork/api.js +9 -4
  9. package/dist/fork/manager.d.ts +2 -2
  10. package/dist/fork/manager.js +4 -8
  11. package/dist/fork/storage.js +5 -4
  12. package/dist/fork/test.d.ts +1 -1
  13. package/dist/fork/validation.d.ts +9 -0
  14. package/dist/fork/validation.js +88 -0
  15. package/dist/harness/Harness.d.ts +35 -6
  16. package/dist/harness/Harness.js +36 -5
  17. package/dist/helpers/index.d.ts +1 -0
  18. package/dist/helpers/index.js +1 -0
  19. package/dist/helpers/move-tests.js +8 -5
  20. package/dist/helpers/setup.js +6 -3
  21. package/dist/helpers/setupLocalTesting.d.ts +2 -2
  22. package/dist/helpers/setupLocalTesting.js +55 -24
  23. package/dist/helpers/testFixtures.d.ts +5 -4
  24. package/dist/helpers/testFixtures.js +4 -5
  25. package/dist/helpers/version-check.js +4 -2
  26. package/dist/index.d.ts +3 -1
  27. package/dist/node/MoveliteManager.d.ts +18 -0
  28. package/dist/node/MoveliteManager.js +152 -0
  29. package/dist/node/NodeProvider.d.ts +9 -0
  30. package/dist/node/NodeProvider.js +1 -0
  31. package/dist/runtime.d.ts +13 -0
  32. package/dist/runtime.js +8 -4
  33. package/dist/templates/.mocharc.json +1 -0
  34. package/dist/templates/tests/Counter.test.ts +12 -14
  35. package/dist/templates/tests/setup.ts +34 -0
  36. package/dist/types/config.d.ts +2 -0
  37. package/dist/types/fork.d.ts +24 -0
  38. package/dist/types/runtime.d.ts +2 -0
  39. package/package.json +4 -1
@@ -0,0 +1,152 @@
1
+ import { execSync, spawn } from "child_process";
2
+ import { existsSync } from "fs";
3
+ import { join } from "path";
4
+ import { createRequire } from "module";
5
+ import { logger } from "../ui/index.js";
6
+ export class MoveliteManager {
7
+ process = null;
8
+ port;
9
+ killed = false;
10
+ binaryPath;
11
+ constructor(binaryPath, port = 8090) {
12
+ this.binaryPath = binaryPath;
13
+ this.port = port;
14
+ }
15
+ async start() {
16
+ const binary = this.binaryPath;
17
+ if (!binary) {
18
+ throw new Error("movelite binary not found");
19
+ }
20
+ if (await this.isPortInUse(this.port)) {
21
+ this.port = this.port + 1;
22
+ if (await this.isPortInUse(this.port)) {
23
+ throw new Error(`Ports ${this.port - 1} and ${this.port} are in use`);
24
+ }
25
+ }
26
+ logger.step("Starting movelite...");
27
+ this.process = spawn(binary, ["start", "--port", String(this.port), "--no-auth"], {
28
+ stdio: "pipe",
29
+ detached: false,
30
+ });
31
+ this.process.on("exit", () => {
32
+ this.process = null;
33
+ });
34
+ await this.waitForReady();
35
+ logger.success(`movelite ready on port ${this.port}`);
36
+ return this.getNodeInfo();
37
+ }
38
+ async waitForReady() {
39
+ const url = `http://127.0.0.1:${this.port}/v1`;
40
+ const timeout = 15_000;
41
+ const start = Date.now();
42
+ while (Date.now() - start < timeout) {
43
+ try {
44
+ const res = await fetch(url);
45
+ if (res.ok)
46
+ return;
47
+ }
48
+ catch {
49
+ // not ready yet
50
+ }
51
+ await new Promise((r) => setTimeout(r, 200));
52
+ }
53
+ throw new Error(`movelite did not become ready within ${timeout}ms`);
54
+ }
55
+ async fundAccount(address, amount) {
56
+ const res = await fetch(`http://127.0.0.1:${this.port}/mint?address=${address}&amount=${amount}`, { method: "POST" });
57
+ if (!res.ok) {
58
+ throw new Error(`Failed to fund account: ${res.status}`);
59
+ }
60
+ }
61
+ async fundAccounts(accounts, balance) {
62
+ for (const account of accounts) {
63
+ await this.fundAccount(account.accountAddress.toString(), balance);
64
+ }
65
+ }
66
+ async stop() {
67
+ if (!this.process || this.killed)
68
+ return;
69
+ this.killed = true;
70
+ this.process.kill("SIGTERM");
71
+ await new Promise((resolve) => {
72
+ const timer = setTimeout(() => {
73
+ if (this.process)
74
+ this.process.kill("SIGKILL");
75
+ resolve();
76
+ }, 5_000);
77
+ if (this.process) {
78
+ this.process.on("exit", () => {
79
+ clearTimeout(timer);
80
+ resolve();
81
+ });
82
+ }
83
+ else {
84
+ clearTimeout(timer);
85
+ resolve();
86
+ }
87
+ });
88
+ this.process = null;
89
+ }
90
+ async isPortInUse(port) {
91
+ try {
92
+ const res = await fetch(`http://127.0.0.1:${port}/v1`);
93
+ return res.ok;
94
+ }
95
+ catch {
96
+ return false;
97
+ }
98
+ }
99
+ isRunning() {
100
+ return this.process !== null && !this.killed;
101
+ }
102
+ getNodeInfo() {
103
+ return {
104
+ rpcUrl: `http://127.0.0.1:${this.port}`,
105
+ faucetUrl: `http://127.0.0.1:${this.port}`,
106
+ readyUrl: `http://127.0.0.1:${this.port}/v1`,
107
+ testDir: "",
108
+ };
109
+ }
110
+ }
111
+ export function findMoveliteBinary() {
112
+ if (process.env.MOVELITE_PATH) {
113
+ return existsSync(process.env.MOVELITE_PATH)
114
+ ? process.env.MOVELITE_PATH
115
+ : null;
116
+ }
117
+ const platforms = {
118
+ "darwin-arm64": "movelite-darwin-arm64",
119
+ "darwin-x64": "movelite-darwin-x64",
120
+ "linux-x64": "movelite-linux-x64",
121
+ "linux-arm64": "movelite-linux-arm64",
122
+ };
123
+ const key = `${process.platform}-${process.arch}`;
124
+ const pkg = platforms[key];
125
+ if (pkg) {
126
+ try {
127
+ // Resolve through the `movelite` shim (movehat's direct dependency),
128
+ // then resolve the platform package from movelite's own context. The
129
+ // platform package is movelite's dependency, not movehat's, so a direct
130
+ // resolve from here fails under pnpm/yarn strict layouts.
131
+ const req = createRequire(import.meta.url);
132
+ const movelitePkg = req.resolve("movelite/package.json");
133
+ const moveliteReq = createRequire(movelitePkg);
134
+ const pkgPath = moveliteReq.resolve(`${pkg}/package.json`);
135
+ const binPath = join(pkgPath, "..", "bin", "movelite");
136
+ if (existsSync(binPath))
137
+ return binPath;
138
+ }
139
+ catch {
140
+ // package not installed
141
+ }
142
+ }
143
+ try {
144
+ const found = execSync("which movelite", { encoding: "utf-8" }).trim();
145
+ if (found && existsSync(found))
146
+ return found;
147
+ }
148
+ catch {
149
+ // not in PATH
150
+ }
151
+ return null;
152
+ }
@@ -0,0 +1,9 @@
1
+ import type { Account } from "@aptos-labs/ts-sdk";
2
+ import type { LocalNodeInfo } from "./LocalNodeManager.js";
3
+ export interface NodeProvider {
4
+ start(): Promise<LocalNodeInfo>;
5
+ stop(): Promise<void>;
6
+ isRunning(): boolean;
7
+ getNodeInfo(): LocalNodeInfo;
8
+ fundAccounts(accounts: Account[], balance: number): Promise<void>;
9
+ }
@@ -0,0 +1 @@
1
+ export {};
package/dist/runtime.d.ts CHANGED
@@ -1,9 +1,22 @@
1
1
  import { MovehatRuntime } from "./types/runtime.js";
2
2
  import { MovehatUserConfig } from "./types/config.js";
3
+ import { AccountManager } from "./core/AccountManager.js";
3
4
  export interface InitRuntimeOptions {
4
5
  network?: string;
5
6
  accountIndex?: number;
6
7
  configOverride?: Partial<MovehatUserConfig>;
8
+ /**
9
+ * Optional pre-constructed `AccountManager` instance. When supplied,
10
+ * the returned runtime's `accountManager` field is THIS instance —
11
+ * so labeled accounts created BEFORE `initRuntime` are visible on
12
+ * the runtime. When omitted, `initRuntime` constructs a fresh
13
+ * `new AccountManager()` per call.
14
+ *
15
+ * `setupLocalTesting` uses this option to thread one manager through
16
+ * `createBatch` → `exportPrivateKeys` → `initRuntime` so the deployer
17
+ * key it extracts ends up on the same manager the runtime exposes.
18
+ */
19
+ accountManager?: AccountManager;
7
20
  }
8
21
  /**
9
22
  * Initialize the Movehat Runtime Environment.
package/dist/runtime.js CHANGED
@@ -35,9 +35,12 @@ export async function initRuntime(options = {}) {
35
35
  fullnode: config.rpc,
36
36
  });
37
37
  const aptos = new Aptos(aptosConfig);
38
- // Setup accounts using AccountManager
38
+ // Setup accounts using AccountManager. Use the caller-supplied
39
+ // instance when threaded in (preserves labels created before
40
+ // initRuntime); otherwise construct a fresh one.
41
+ const accountManager = options.accountManager ?? new AccountManager();
39
42
  const accountIndex = options.accountIndex || 0;
40
- const accounts = AccountManager.loadAccountsFromConfig(config);
43
+ const accounts = accountManager.loadAccountsFromConfig(config);
41
44
  // Primary account (accounts[0] or selected index)
42
45
  const account = accounts[accountIndex];
43
46
  if (!account) {
@@ -73,10 +76,10 @@ export async function initRuntime(options = {}) {
73
76
  return getDeployedAddress(config.network, moduleName);
74
77
  };
75
78
  const createAccount = () => {
76
- return AccountManager.createAccount();
79
+ return accountManager.createAccount();
77
80
  };
78
81
  const getAccountHelper = (privateKeyHex) => {
79
- return AccountManager.loadAccountFromPrivateKey(privateKeyHex);
82
+ return accountManager.loadAccountFromPrivateKey(privateKeyHex);
80
83
  };
81
84
  const getAccountByIndex = (index) => {
82
85
  const acc = accounts[index];
@@ -95,6 +98,7 @@ export async function initRuntime(options = {}) {
95
98
  aptos,
96
99
  account,
97
100
  accounts,
101
+ accountManager,
98
102
  getContract: getContractHelper,
99
103
  deployContract,
100
104
  getDeployment,
@@ -2,6 +2,7 @@
2
2
  "node-option": ["import=tsx"],
3
3
  "extensions": ["ts"],
4
4
  "spec": ["tests/**/*.test.ts"],
5
+ "require": ["tests/setup.ts"],
5
6
  "timeout": 30000,
6
7
  "color": true,
7
8
  "reporter": "spec"
@@ -1,7 +1,8 @@
1
1
  // @ts-nocheck - This is a template file, dependencies are installed in user projects
2
2
  import { describe, it, before, after } from "mocha";
3
3
  import { expect } from "chai";
4
- import { Harness, AccountManager } from "movehat";
4
+ import { Harness } from "movehat";
5
+ import { getSharedNode } from "./setup.js";
5
6
 
6
7
  describe("Counter Contract", () => {
7
8
  let harness;
@@ -9,24 +10,21 @@ describe("Counter Contract", () => {
9
10
  let deployer, alice, bob;
10
11
 
11
12
  before(async function () {
12
- this.timeout(60000); // Allow time for local node startup + deployment
13
-
14
- // Hardhat-style Harness the primary public API.
15
- // createLocal spins up a Movement local node, funds the labeled
16
- // accounts from the local faucet, and (via autoDeploy) builds +
17
- // publishes the named modules so they're ready to use.
18
- //
19
- // The setupTestFixture helper still works if you prefer the older
20
- // pattern; see `import { setupTestFixture } from "movehat/helpers"`.
13
+ this.timeout(60000);
14
+
15
+ // Reuses the shared Movement node started by root hooks (tests/setup.ts).
16
+ // Each spec still gets its own accounts + deployments for isolation.
21
17
  harness = await Harness.createLocal({
18
+ localNode: getSharedNode(),
22
19
  accountLabels: ["deployer", "alice", "bob"],
23
20
  autoDeploy: ["counter"],
24
21
  });
25
22
 
26
- const labeled = AccountManager.getLabeledAccounts();
27
- deployer = labeled.deployer;
28
- alice = labeled.alice;
29
- bob = labeled.bob;
23
+ // harness.accounts is a snapshot of the labeled accounts created
24
+ // during Harness construction. Two `Harness.createLocal({ accountLabels:
25
+ // ["alice"] })` calls in the same process now produce DIFFERENT
26
+ // alice accounts (per-Harness isolation introduced in 0.2.7).
27
+ ({ deployer, alice, bob } = harness.accounts);
30
28
 
31
29
  const counterAddr = harness.runtime.getDeploymentAddress("counter");
32
30
  counter = harness.runtime.getContract(counterAddr, "counter");
@@ -0,0 +1,34 @@
1
+ // @ts-nocheck - This is a template file, dependencies are installed in user projects
2
+ import { LocalNodeManager } from "movehat/helpers";
3
+
4
+ let sharedNode;
5
+
6
+ /**
7
+ * Returns the shared local Movement node started by root hooks.
8
+ * Pass the result as `{ localNode: getSharedNode() }` to
9
+ * Harness.createLocal() or setupTestFixture().
10
+ */
11
+ export function getSharedNode() {
12
+ if (!sharedNode || !sharedNode.isRunning()) {
13
+ throw new Error(
14
+ "Shared node not available. " +
15
+ 'Ensure tests/setup.ts is listed in .mocharc.json under "require".'
16
+ );
17
+ }
18
+ return sharedNode;
19
+ }
20
+
21
+ export const mochaHooks = {
22
+ async beforeAll() {
23
+ this.timeout(60000);
24
+ sharedNode = new LocalNodeManager({ forceRestart: true });
25
+ await sharedNode.start();
26
+ },
27
+
28
+ async afterAll() {
29
+ if (sharedNode) {
30
+ await sharedNode.stop();
31
+ sharedNode = undefined;
32
+ }
33
+ },
34
+ };
@@ -41,6 +41,8 @@ export type LocalTestingMode = 'local-node' | 'fork';
41
41
  */
42
42
  export interface LocalTestOptions {
43
43
  mode?: LocalTestingMode;
44
+ localNode?: import("../node/LocalNodeManager.js").LocalNodeManager;
45
+ useMovelite?: boolean;
44
46
  nodeTestDir?: string;
45
47
  nodeForceRestart?: boolean;
46
48
  nodeFaucetPort?: number;
@@ -40,3 +40,27 @@ export interface AccountResource {
40
40
  */
41
41
  data: unknown;
42
42
  }
43
+ export interface CoinStore {
44
+ coin: {
45
+ value: string;
46
+ };
47
+ deposit_events: {
48
+ counter: string;
49
+ guid: {
50
+ id: {
51
+ addr: string;
52
+ creation_num: string;
53
+ };
54
+ };
55
+ };
56
+ withdraw_events: {
57
+ counter: string;
58
+ guid: {
59
+ id: {
60
+ addr: string;
61
+ creation_num: string;
62
+ };
63
+ };
64
+ };
65
+ frozen: boolean;
66
+ }
@@ -2,6 +2,7 @@ import { Aptos, Account } from "@aptos-labs/ts-sdk";
2
2
  import { MovehatConfig } from "./config.js";
3
3
  import { MoveContract } from "../core/contract.js";
4
4
  import { DeploymentInfo } from "../core/deployments.js";
5
+ import type { AccountManager } from "../core/AccountManager.js";
5
6
  import type { ChildProcessAdapter } from "../utils/childProcessAdapter.js";
6
7
  export interface NetworkInfo {
7
8
  name: string;
@@ -14,6 +15,7 @@ export interface MovehatRuntime {
14
15
  aptos: Aptos;
15
16
  account: Account;
16
17
  accounts: Account[];
18
+ accountManager: AccountManager;
17
19
  getContract: (address: string, moduleName: string) => MoveContract;
18
20
  deployContract: (moduleName: string, options?: {
19
21
  packageDir?: string;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "movehat",
3
- "version": "0.2.6",
3
+ "version": "0.2.8",
4
4
  "type": "module",
5
5
  "description": "Hardhat-like development framework for Movement L1 smart contracts",
6
6
  "bin": {
@@ -70,6 +70,9 @@
70
70
  "prompts": "^2.4.2",
71
71
  "tsx": "^4.7.0"
72
72
  },
73
+ "optionalDependencies": {
74
+ "movelite": "^0.1.0"
75
+ },
73
76
  "devDependencies": {
74
77
  "@types/js-yaml": "^4.0.9",
75
78
  "@types/node": "^24.10.1",