movehat 0.2.7 → 0.2.9
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/Publisher.d.ts +20 -1
- package/dist/core/Publisher.js +167 -89
- 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 +3 -2
- package/dist/harness/Harness.js +4 -1
- 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/setupLocalTesting.d.ts +2 -2
- package/dist/helpers/setupLocalTesting.js +58 -21
- package/dist/helpers/version-check.js +4 -2
- 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.js +2 -0
- package/dist/templates/.mocharc.json +1 -0
- package/dist/templates/tests/Counter.test.ts +6 -9
- 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 +6 -0
- package/package.json +4 -1
package/dist/cli.js
CHANGED
|
@@ -66,7 +66,7 @@ program
|
|
|
66
66
|
});
|
|
67
67
|
program
|
|
68
68
|
.command('compile')
|
|
69
|
-
.description('Compile Move smart contracts
|
|
69
|
+
.description('Compile Move smart contracts (auto-detects named addresses and updates Move.toml)')
|
|
70
70
|
.action(compileCommand);
|
|
71
71
|
program
|
|
72
72
|
.command('run <script>')
|
|
@@ -34,9 +34,11 @@ export default async function forkFundCommand(options) {
|
|
|
34
34
|
// Verify
|
|
35
35
|
const resourceType = `0x1::coin::CoinStore<${coinType}>`;
|
|
36
36
|
const coinStore = await forkManager.getResource(options.account, resourceType);
|
|
37
|
+
const { assertCoinStore } = await import('../../fork/validation.js');
|
|
38
|
+
const validated = assertCoinStore(coinStore);
|
|
37
39
|
logger.newline();
|
|
38
40
|
logger.success("Account funded successfully!");
|
|
39
|
-
logger.plain(` New balance: ${
|
|
41
|
+
logger.plain(` New balance: ${validated.coin.value}`);
|
|
40
42
|
logger.newline();
|
|
41
43
|
}
|
|
42
44
|
catch (error) {
|
|
@@ -2,6 +2,7 @@ import { join } from 'path';
|
|
|
2
2
|
import { existsSync } from 'fs';
|
|
3
3
|
import { loadUserConfig } from '../../core/config.js';
|
|
4
4
|
import { ForkServer } from '../../fork/server.js';
|
|
5
|
+
import { logger } from '../../ui/index.js';
|
|
5
6
|
/**
|
|
6
7
|
* Fork serve command: Start a local RPC server serving the fork
|
|
7
8
|
*/
|
|
@@ -26,9 +27,11 @@ export default async function forkServeCommand(options) {
|
|
|
26
27
|
}
|
|
27
28
|
// Verify fork exists
|
|
28
29
|
if (!existsSync(join(forkPath, 'metadata.json'))) {
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
30
|
+
logger.newline();
|
|
31
|
+
logger.error(`Fork not found at ${forkPath}`);
|
|
32
|
+
logger.newline();
|
|
33
|
+
logger.error("Create a fork first with:");
|
|
34
|
+
logger.error(" movehat fork create --network <network> --name <name>");
|
|
32
35
|
process.exit(1);
|
|
33
36
|
}
|
|
34
37
|
// Get port (already validated by Commander's parsePort in cli.ts)
|
|
@@ -38,7 +41,8 @@ export default async function forkServeCommand(options) {
|
|
|
38
41
|
const server = new ForkServer(forkPath, port, host);
|
|
39
42
|
// Handle graceful shutdown (use 'once' to prevent duplicate shutdowns)
|
|
40
43
|
const shutdown = async () => {
|
|
41
|
-
|
|
44
|
+
logger.newline();
|
|
45
|
+
logger.step("Shutting down...");
|
|
42
46
|
await server.stop();
|
|
43
47
|
process.exit(0);
|
|
44
48
|
};
|
|
@@ -59,7 +63,8 @@ export default async function forkServeCommand(options) {
|
|
|
59
63
|
}
|
|
60
64
|
catch (error) {
|
|
61
65
|
const msg = error instanceof Error ? error.message : String(error);
|
|
62
|
-
|
|
66
|
+
logger.newline();
|
|
67
|
+
logger.error(`Error starting fork server: ${msg}`);
|
|
63
68
|
process.exit(1);
|
|
64
69
|
}
|
|
65
70
|
}
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import { runMoveTests } from "../helpers/move-tests.js";
|
|
2
|
+
import { logger } from "../ui/index.js";
|
|
2
3
|
export default async function testMoveCommand(options = {}) {
|
|
3
4
|
try {
|
|
4
|
-
|
|
5
|
+
logger.step("Running Move unit tests...");
|
|
6
|
+
logger.newline();
|
|
5
7
|
await runMoveTests({
|
|
6
8
|
filter: options.filter,
|
|
7
9
|
ignoreWarnings: options.ignoreWarnings,
|
|
@@ -10,10 +12,11 @@ export default async function testMoveCommand(options = {}) {
|
|
|
10
12
|
process.exit(0);
|
|
11
13
|
}
|
|
12
14
|
catch (err) {
|
|
13
|
-
|
|
15
|
+
logger.newline();
|
|
16
|
+
logger.error("Move tests failed");
|
|
14
17
|
const msg = err instanceof Error ? err.message : String(err);
|
|
15
18
|
if (msg) {
|
|
16
|
-
|
|
19
|
+
logger.error(` ${msg}`);
|
|
17
20
|
}
|
|
18
21
|
process.exit(1);
|
|
19
22
|
}
|
package/dist/core/Publisher.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Account } from "@aptos-labs/ts-sdk";
|
|
1
|
+
import { Account, Aptos } from "@aptos-labs/ts-sdk";
|
|
2
2
|
import { MovehatConfig } from "../types/config.js";
|
|
3
3
|
import { DeploymentInfo } from "./deployments.js";
|
|
4
4
|
import type { ChildProcessAdapter } from "../utils/childProcessAdapter.js";
|
|
@@ -12,6 +12,13 @@ export interface PublishInput {
|
|
|
12
12
|
config: MovehatConfig;
|
|
13
13
|
account: Account;
|
|
14
14
|
packageDir?: string | undefined;
|
|
15
|
+
/**
|
|
16
|
+
* Publish via the TypeScript SDK instead of the Movement CLI. Set when the
|
|
17
|
+
* backend is movelite, whose REST responses the Movement CLI cannot parse.
|
|
18
|
+
* Requires `aptos`.
|
|
19
|
+
*/
|
|
20
|
+
sdkPublish?: boolean | undefined;
|
|
21
|
+
aptos?: Aptos | undefined;
|
|
15
22
|
}
|
|
16
23
|
/**
|
|
17
24
|
* Publishes a Move module via the Movement CLI.
|
|
@@ -22,4 +29,16 @@ export declare class Publisher {
|
|
|
22
29
|
private readonly deps;
|
|
23
30
|
constructor(deps?: PublisherDeps);
|
|
24
31
|
deploy(input: PublishInput): Promise<DeploymentInfo>;
|
|
32
|
+
/**
|
|
33
|
+
* Publish via the Movement CLI (`movement move publish`). The default path
|
|
34
|
+
* for real Movement nodes, forks, and testnet. Returns the parsed tx hash.
|
|
35
|
+
*/
|
|
36
|
+
private publishViaCli;
|
|
37
|
+
/**
|
|
38
|
+
* Publish the already-built package via the TypeScript SDK. Used when the
|
|
39
|
+
* backend is movelite. Reads the compiled artifacts the CLI build produced
|
|
40
|
+
* under `<dir>/build/<pkg>/`, submits a `0x1::code::publish_package_txn`,
|
|
41
|
+
* and waits for it. Returns the on-chain tx hash.
|
|
42
|
+
*/
|
|
43
|
+
private publishViaSdk;
|
|
25
44
|
}
|
package/dist/core/Publisher.js
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import { PrivateKey, PrivateKeyVariants } from "@aptos-labs/ts-sdk";
|
|
2
|
+
import { existsSync, readFileSync, readdirSync } from "node:fs";
|
|
3
|
+
import { join } from "node:path";
|
|
2
4
|
import { extractNamedAddresses } from "../commands/compile.js";
|
|
3
5
|
import { saveDeployment, loadDeployment, validateSafeName, } from "./deployments.js";
|
|
4
6
|
import { validatePathSafety } from "./shell.js";
|
|
@@ -74,103 +76,34 @@ export class Publisher {
|
|
|
74
76
|
.join(","),
|
|
75
77
|
]
|
|
76
78
|
: [];
|
|
79
|
+
// The SDK publish path reads package-metadata.bcs from the build
|
|
80
|
+
// output; `--save-metadata` makes the build emit it. The CLI path
|
|
81
|
+
// doesn't need it (`move publish` rebuilds metadata internally).
|
|
82
|
+
const saveMetadataArgs = input.sdkPublish ? ["--save-metadata"] : [];
|
|
77
83
|
// Build first with named addresses
|
|
78
84
|
const buildResult = await withSpinner("Building package", () => runCli({
|
|
79
85
|
command: "movement",
|
|
80
|
-
args: [
|
|
86
|
+
args: [
|
|
87
|
+
"move",
|
|
88
|
+
"build",
|
|
89
|
+
"--package-dir",
|
|
90
|
+
safeDir,
|
|
91
|
+
...namedAddrArgs,
|
|
92
|
+
...saveMetadataArgs,
|
|
93
|
+
],
|
|
81
94
|
timeoutMs: 120000, // 2 minutes for git dependency downloads
|
|
82
95
|
}, { adapter: this.deps.adapter }));
|
|
83
96
|
if (isVerbose() && buildResult.stdout) {
|
|
84
97
|
logger.info(buildResult.stdout.trim(), 2);
|
|
85
98
|
}
|
|
86
|
-
// Publish
|
|
87
|
-
//
|
|
88
|
-
//
|
|
89
|
-
// is
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
// leaving the user's file mutated if the process died before the
|
|
95
|
-
// restore step.
|
|
96
|
-
let publishOut = "";
|
|
97
|
-
let publishErr = "";
|
|
98
|
-
// Pass the private key to Movement CLI via a 0o600 temp file
|
|
99
|
-
// (`--private-key-file <path>`) and the on-chain address via
|
|
100
|
-
// `--sender-account <addr>`. This avoids the CLI's profile-yaml
|
|
101
|
-
// lookup chain entirely — no CWD / HOME / .aptos / .movement
|
|
102
|
-
// dance, no CLI-variant dependency.
|
|
103
|
-
const keyFilePath = writeTempKeyFile(formattedPrivateKey);
|
|
104
|
-
// Register a sync cleanup hook BEFORE invoking the CLI. If the
|
|
105
|
-
// user Ctrl+C's (or the process is SIGTERM'd) between the file
|
|
106
|
-
// write and our finally, the SIGINT handler iterates every
|
|
107
|
-
// registered callback and unlinks this deploy's key file
|
|
108
|
-
// synchronously so the private key never persists on disk after
|
|
109
|
-
// an abnormal exit. The signal-handler path uses the
|
|
110
|
-
// best-effort variant because the event loop is dead and we
|
|
111
|
-
// cannot logger.warning.
|
|
112
|
-
ensureSignalHandler();
|
|
113
|
-
const syncCleanup = () => removeKeyFileSyncBestEffort(keyFilePath);
|
|
114
|
-
cleanupCallbacks.add(syncCleanup);
|
|
115
|
-
try {
|
|
116
|
-
// Execute publish command. Private key reaches the CLI via the
|
|
117
|
-
// temp key file path (--private-key-file) — never on the
|
|
118
|
-
// command line — so it can't leak through `ps aux`. runCli's
|
|
119
|
-
// stdout/stderr redaction still applies as defense in depth
|
|
120
|
-
// for any `ed25519-priv-…` substring that surfaces in CLI
|
|
121
|
-
// output (Movement CLI sometimes echoes the key on error).
|
|
122
|
-
const publishResult = await withSpinner("Publishing to blockchain", () => runCli({
|
|
123
|
-
command: "movement",
|
|
124
|
-
args: [
|
|
125
|
-
"move",
|
|
126
|
-
"publish",
|
|
127
|
-
"--package-dir",
|
|
128
|
-
safeDir,
|
|
129
|
-
"--url",
|
|
130
|
-
config.rpc,
|
|
131
|
-
"--private-key-file",
|
|
132
|
-
keyFilePath,
|
|
133
|
-
"--sender-account",
|
|
134
|
-
deployerAddress,
|
|
135
|
-
"--assume-yes",
|
|
136
|
-
...namedAddrArgs,
|
|
137
|
-
],
|
|
138
|
-
timeoutMs: 120000, // 2 minutes for blockchain transactions
|
|
139
|
-
}, { adapter: this.deps.adapter }));
|
|
140
|
-
publishOut = publishResult.stdout;
|
|
141
|
-
publishErr = publishResult.stderr;
|
|
142
|
-
// Both stdout and stderr from the publish subprocess are gated
|
|
143
|
-
// behind isVerbose() — Movement CLI emits progress to both
|
|
144
|
-
// streams ("Compiling, may take a little while..."), so a
|
|
145
|
-
// visible stderr line is not by itself a failure signal. The
|
|
146
|
-
// surrounding withSpinner converts the runCli throw on real
|
|
147
|
-
// failure into the visible spinner.fail() output instead.
|
|
148
|
-
if (isVerbose() && publishOut)
|
|
149
|
-
logger.info(publishOut.trim(), 2);
|
|
150
|
-
if (isVerbose() && publishErr)
|
|
151
|
-
logger.info(publishErr.trim(), 2);
|
|
152
|
-
}
|
|
153
|
-
finally {
|
|
154
|
-
// Unlink the temp key file via the observable cleanup helper.
|
|
155
|
-
// ENOENT and other already-gone outcomes are benign (null).
|
|
156
|
-
// A non-null Error means the unlink failed AND the file still
|
|
157
|
-
// exists on disk — the private key would persist silently
|
|
158
|
-
// otherwise, so we emit a warning with the manual-cleanup
|
|
159
|
-
// hint. The SIGINT signal handler's sync callback below also
|
|
160
|
-
// tries to remove the same file; if SIGINT fires before this
|
|
161
|
-
// finally runs the file is gone and the next finally call
|
|
162
|
-
// sees ENOENT (benign).
|
|
163
|
-
const cleanupErr = removeKeyFile(keyFilePath);
|
|
164
|
-
if (cleanupErr) {
|
|
165
|
-
logger.warning(`Failed to remove temp key file '${keyFilePath}': ${cleanupErr.message}. ` +
|
|
166
|
-
`The file has mode 0o600 but should be removed manually: rm ${keyFilePath}`);
|
|
167
|
-
}
|
|
168
|
-
cleanupCallbacks.delete(syncCleanup);
|
|
169
|
-
}
|
|
170
|
-
// Extract transaction hash from output via the shared helper
|
|
171
|
-
// (`utils/parseCliOutput.ts`). Same regex pair as before; lifted
|
|
172
|
-
// for reuse by harness/codeObject.ts and harness/script.ts.
|
|
173
|
-
const txHash = parseTxHash(publishOut);
|
|
99
|
+
// Publish the freshly-built package. movelite cannot consume the
|
|
100
|
+
// Movement CLI's `move publish` REST flow (its responses omit the
|
|
101
|
+
// ledger headers and fields the CLI requires), so when the backend
|
|
102
|
+
// is movelite we publish via the TypeScript SDK instead. Every other
|
|
103
|
+
// backend keeps the CLI path.
|
|
104
|
+
const txHash = input.sdkPublish
|
|
105
|
+
? await this.publishViaSdk(input, safeDir)
|
|
106
|
+
: await this.publishViaCli(config, safeDir, deployerAddress, namedAddrArgs);
|
|
174
107
|
logger.success("Module published successfully!");
|
|
175
108
|
// ←← "Publish succeeded" boundary. Anything thrown below this
|
|
176
109
|
// point did NOT cause the publish to fail — the module is on
|
|
@@ -226,4 +159,149 @@ export class Publisher {
|
|
|
226
159
|
throw error;
|
|
227
160
|
}
|
|
228
161
|
}
|
|
162
|
+
/**
|
|
163
|
+
* Publish via the Movement CLI (`movement move publish`). The default path
|
|
164
|
+
* for real Movement nodes, forks, and testnet. Returns the parsed tx hash.
|
|
165
|
+
*/
|
|
166
|
+
async publishViaCli(config, safeDir, deployerAddress, namedAddrArgs) {
|
|
167
|
+
// Format the private key into AIP-80 shape so the Movement CLI
|
|
168
|
+
// doesn't emit its raw-hex deprecation warning. `formatPrivateKey`
|
|
169
|
+
// is idempotent for already-prefixed inputs.
|
|
170
|
+
const formattedPrivateKey = PrivateKey.formatPrivateKey(config.privateKey, PrivateKeyVariants.Ed25519);
|
|
171
|
+
// Move.toml is NOT mutated. All address overrides flow through
|
|
172
|
+
// the `--named-addresses` flag above, which Movement CLI applies
|
|
173
|
+
// during build + publish. Rewriting Move.toml on disk would risk
|
|
174
|
+
// leaving the user's file mutated if the process died before the
|
|
175
|
+
// restore step.
|
|
176
|
+
let publishOut = "";
|
|
177
|
+
let publishErr = "";
|
|
178
|
+
// Pass the private key to Movement CLI via a 0o600 temp file
|
|
179
|
+
// (`--private-key-file <path>`) and the on-chain address via
|
|
180
|
+
// `--sender-account <addr>`. This avoids the CLI's profile-yaml
|
|
181
|
+
// lookup chain entirely — no CWD / HOME / .aptos / .movement
|
|
182
|
+
// dance, no CLI-variant dependency.
|
|
183
|
+
const keyFilePath = writeTempKeyFile(formattedPrivateKey);
|
|
184
|
+
// Register a sync cleanup hook BEFORE invoking the CLI. If the
|
|
185
|
+
// user Ctrl+C's (or the process is SIGTERM'd) between the file
|
|
186
|
+
// write and our finally, the SIGINT handler iterates every
|
|
187
|
+
// registered callback and unlinks this deploy's key file
|
|
188
|
+
// synchronously so the private key never persists on disk after
|
|
189
|
+
// an abnormal exit. The signal-handler path uses the
|
|
190
|
+
// best-effort variant because the event loop is dead and we
|
|
191
|
+
// cannot logger.warning.
|
|
192
|
+
ensureSignalHandler();
|
|
193
|
+
const syncCleanup = () => removeKeyFileSyncBestEffort(keyFilePath);
|
|
194
|
+
cleanupCallbacks.add(syncCleanup);
|
|
195
|
+
try {
|
|
196
|
+
// Execute publish command. Private key reaches the CLI via the
|
|
197
|
+
// temp key file path (--private-key-file) — never on the
|
|
198
|
+
// command line — so it can't leak through `ps aux`. runCli's
|
|
199
|
+
// stdout/stderr redaction still applies as defense in depth
|
|
200
|
+
// for any `ed25519-priv-…` substring that surfaces in CLI
|
|
201
|
+
// output (Movement CLI sometimes echoes the key on error).
|
|
202
|
+
const publishResult = await withSpinner("Publishing to blockchain", () => runCli({
|
|
203
|
+
command: "movement",
|
|
204
|
+
args: [
|
|
205
|
+
"move",
|
|
206
|
+
"publish",
|
|
207
|
+
"--package-dir",
|
|
208
|
+
safeDir,
|
|
209
|
+
"--url",
|
|
210
|
+
config.rpc,
|
|
211
|
+
"--private-key-file",
|
|
212
|
+
keyFilePath,
|
|
213
|
+
"--sender-account",
|
|
214
|
+
deployerAddress,
|
|
215
|
+
"--assume-yes",
|
|
216
|
+
...namedAddrArgs,
|
|
217
|
+
],
|
|
218
|
+
timeoutMs: 120000, // 2 minutes for blockchain transactions
|
|
219
|
+
}, { adapter: this.deps.adapter }));
|
|
220
|
+
publishOut = publishResult.stdout;
|
|
221
|
+
publishErr = publishResult.stderr;
|
|
222
|
+
// Both stdout and stderr from the publish subprocess are gated
|
|
223
|
+
// behind isVerbose() — Movement CLI emits progress to both
|
|
224
|
+
// streams ("Compiling, may take a little while..."), so a
|
|
225
|
+
// visible stderr line is not by itself a failure signal. The
|
|
226
|
+
// surrounding withSpinner converts the runCli throw on real
|
|
227
|
+
// failure into the visible spinner.fail() output instead.
|
|
228
|
+
if (isVerbose() && publishOut)
|
|
229
|
+
logger.info(publishOut.trim(), 2);
|
|
230
|
+
if (isVerbose() && publishErr)
|
|
231
|
+
logger.info(publishErr.trim(), 2);
|
|
232
|
+
}
|
|
233
|
+
finally {
|
|
234
|
+
// Unlink the temp key file via the observable cleanup helper.
|
|
235
|
+
// ENOENT and other already-gone outcomes are benign (null).
|
|
236
|
+
// A non-null Error means the unlink failed AND the file still
|
|
237
|
+
// exists on disk — the private key would persist silently
|
|
238
|
+
// otherwise, so we emit a warning with the manual-cleanup
|
|
239
|
+
// hint. The SIGINT signal handler's sync callback below also
|
|
240
|
+
// tries to remove the same file; if SIGINT fires before this
|
|
241
|
+
// finally runs the file is gone and the next finally call
|
|
242
|
+
// sees ENOENT (benign).
|
|
243
|
+
const cleanupErr = removeKeyFile(keyFilePath);
|
|
244
|
+
if (cleanupErr) {
|
|
245
|
+
logger.warning(`Failed to remove temp key file '${keyFilePath}': ${cleanupErr.message}. ` +
|
|
246
|
+
`The file has mode 0o600 but should be removed manually: rm ${keyFilePath}`);
|
|
247
|
+
}
|
|
248
|
+
cleanupCallbacks.delete(syncCleanup);
|
|
249
|
+
}
|
|
250
|
+
// Extract transaction hash from output via the shared helper
|
|
251
|
+
// (`utils/parseCliOutput.ts`). Same regex pair as before; lifted
|
|
252
|
+
// for reuse by harness/codeObject.ts and harness/script.ts.
|
|
253
|
+
return parseTxHash(publishOut);
|
|
254
|
+
}
|
|
255
|
+
/**
|
|
256
|
+
* Publish the already-built package via the TypeScript SDK. Used when the
|
|
257
|
+
* backend is movelite. Reads the compiled artifacts the CLI build produced
|
|
258
|
+
* under `<dir>/build/<pkg>/`, submits a `0x1::code::publish_package_txn`,
|
|
259
|
+
* and waits for it. Returns the on-chain tx hash.
|
|
260
|
+
*/
|
|
261
|
+
async publishViaSdk(input, safeDir) {
|
|
262
|
+
const aptos = input.aptos;
|
|
263
|
+
if (!aptos) {
|
|
264
|
+
throw new Error("sdkPublish requires an Aptos client");
|
|
265
|
+
}
|
|
266
|
+
const buildRoot = join(safeDir, "build");
|
|
267
|
+
// The root package's compiled output is the single directory under
|
|
268
|
+
// build/ that carries a package-metadata.bcs; dependency builds live
|
|
269
|
+
// in nested bytecode_modules/dependencies/ and have no metadata here.
|
|
270
|
+
const pkgDirs = existsSync(buildRoot)
|
|
271
|
+
? readdirSync(buildRoot, { withFileTypes: true })
|
|
272
|
+
.filter((e) => e.isDirectory())
|
|
273
|
+
.map((e) => join(buildRoot, e.name))
|
|
274
|
+
.filter((d) => existsSync(join(d, "package-metadata.bcs")))
|
|
275
|
+
: [];
|
|
276
|
+
if (pkgDirs.length !== 1) {
|
|
277
|
+
throw new Error(`Expected exactly one compiled package under ${buildRoot}, found ${pkgDirs.length}.`);
|
|
278
|
+
}
|
|
279
|
+
const pkgDir = pkgDirs[0];
|
|
280
|
+
const metadataBytes = new Uint8Array(readFileSync(join(pkgDir, "package-metadata.bcs")));
|
|
281
|
+
const modulesDir = join(pkgDir, "bytecode_modules");
|
|
282
|
+
const moduleBytecode = readdirSync(modulesDir)
|
|
283
|
+
.filter((f) => f.endsWith(".mv"))
|
|
284
|
+
.sort()
|
|
285
|
+
.map((f) => new Uint8Array(readFileSync(join(modulesDir, f))));
|
|
286
|
+
if (moduleBytecode.length === 0) {
|
|
287
|
+
throw new Error(`No compiled modules (*.mv) found in ${modulesDir}`);
|
|
288
|
+
}
|
|
289
|
+
return withSpinner("Publishing to blockchain", async () => {
|
|
290
|
+
const tx = await aptos.publishPackageTransaction({
|
|
291
|
+
account: input.account.accountAddress,
|
|
292
|
+
metadataBytes,
|
|
293
|
+
moduleBytecode,
|
|
294
|
+
});
|
|
295
|
+
const senderAuth = aptos.transaction.sign({
|
|
296
|
+
signer: input.account,
|
|
297
|
+
transaction: tx,
|
|
298
|
+
});
|
|
299
|
+
const committed = await aptos.transaction.submit.simple({
|
|
300
|
+
transaction: tx,
|
|
301
|
+
senderAuthenticator: senderAuth,
|
|
302
|
+
});
|
|
303
|
+
await aptos.waitForTransaction({ transactionHash: committed.hash });
|
|
304
|
+
return committed.hash;
|
|
305
|
+
});
|
|
306
|
+
}
|
|
229
307
|
}
|
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);
|
package/dist/fork/storage.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { chmodSync, existsSync, mkdirSync, readFileSync, writeFileSync, readdirSync, unlinkSync } from 'fs';
|
|
2
2
|
import { join } from 'path';
|
|
3
3
|
import { isHexAddress } from '../utils/address.js';
|
|
4
|
+
import { assertForkMetadata, assertAccountStateRecord } from './validation.js';
|
|
4
5
|
/**
|
|
5
6
|
* Sanitize address to create a safe filename. Validates the address through
|
|
6
7
|
* the shared `isHexAddress` helper (length 1–64 hex chars, optional `0x`),
|
|
@@ -108,7 +109,7 @@ export class ForkStorage {
|
|
|
108
109
|
if (!existsSync(metadataPath)) {
|
|
109
110
|
throw new Error(`Fork metadata not found at ${metadataPath}`);
|
|
110
111
|
}
|
|
111
|
-
return readJsonFile(metadataPath, 'fork metadata');
|
|
112
|
+
return assertForkMetadata(readJsonFile(metadataPath, 'fork metadata'));
|
|
112
113
|
}
|
|
113
114
|
/**
|
|
114
115
|
* Get account state
|
|
@@ -118,7 +119,7 @@ export class ForkStorage {
|
|
|
118
119
|
if (!existsSync(accountsPath)) {
|
|
119
120
|
return null;
|
|
120
121
|
}
|
|
121
|
-
const accounts = readJsonFile(accountsPath, 'fork accounts');
|
|
122
|
+
const accounts = assertAccountStateRecord(readJsonFile(accountsPath, 'fork accounts'));
|
|
122
123
|
return accounts[address] || null;
|
|
123
124
|
}
|
|
124
125
|
/**
|
|
@@ -128,7 +129,7 @@ export class ForkStorage {
|
|
|
128
129
|
const accountsPath = join(this.forkPath, 'accounts.json');
|
|
129
130
|
let accounts = {};
|
|
130
131
|
if (existsSync(accountsPath)) {
|
|
131
|
-
accounts = readJsonFile(accountsPath, 'fork accounts');
|
|
132
|
+
accounts = assertAccountStateRecord(readJsonFile(accountsPath, 'fork accounts'));
|
|
132
133
|
}
|
|
133
134
|
accounts[address] = state;
|
|
134
135
|
writePrivateFile(accountsPath, JSON.stringify(accounts, null, 2));
|
|
@@ -193,7 +194,7 @@ export class ForkStorage {
|
|
|
193
194
|
if (!existsSync(accountsPath)) {
|
|
194
195
|
return [];
|
|
195
196
|
}
|
|
196
|
-
const accounts = readJsonFile(accountsPath, 'fork accounts');
|
|
197
|
+
const accounts = assertAccountStateRecord(readJsonFile(accountsPath, 'fork accounts'));
|
|
197
198
|
return Object.keys(accounts);
|
|
198
199
|
}
|
|
199
200
|
/**
|
package/dist/fork/test.d.ts
CHANGED
|
@@ -59,7 +59,7 @@ export declare function getForkInfo(path: string): Promise<ForkInfo>;
|
|
|
59
59
|
*/
|
|
60
60
|
export declare function viewForkResource(sessionPath: string, account: string, resourceType: string, options?: {
|
|
61
61
|
adapter?: ChildProcessAdapter;
|
|
62
|
-
}): Promise<
|
|
62
|
+
}): Promise<unknown>;
|
|
63
63
|
/**
|
|
64
64
|
* Compare a resource between current network state and a fork
|
|
65
65
|
* Useful for verifying state changes after tests
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { LedgerInfo, AccountData, AccountResource, ForkMetadata, AccountState, CoinStore } from "../types/fork.js";
|
|
2
|
+
export declare function assertLedgerInfo(v: unknown): LedgerInfo;
|
|
3
|
+
export declare function assertAccountData(v: unknown): AccountData;
|
|
4
|
+
export declare function assertAccountResource(v: unknown): AccountResource;
|
|
5
|
+
export declare function assertAccountResourceArray(v: unknown): AccountResource[];
|
|
6
|
+
export declare function assertForkMetadata(v: unknown): ForkMetadata;
|
|
7
|
+
export declare function assertAccountState(v: unknown): AccountState;
|
|
8
|
+
export declare function assertAccountStateRecord(v: unknown): Record<string, AccountState>;
|
|
9
|
+
export declare function assertCoinStore(v: unknown): CoinStore;
|