@cfxdevkit/devnode 0.1.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/README.md +35 -0
- package/dist/index.cjs +1033 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +234 -0
- package/dist/index.d.ts +234 -0
- package/dist/index.js +998 -0
- package/dist/index.js.map +1 -0
- package/dist/plugin.cjs +1026 -0
- package/dist/plugin.cjs.map +1 -0
- package/dist/plugin.d.cts +54 -0
- package/dist/plugin.d.ts +54 -0
- package/dist/plugin.js +993 -0
- package/dist/plugin.js.map +1 -0
- package/dist/types.cjs +42 -0
- package/dist/types.cjs.map +1 -0
- package/dist/types.d.cts +72 -0
- package/dist/types.d.ts +72 -0
- package/dist/types.js +17 -0
- package/dist/types.js.map +1 -0
- package/package.json +90 -0
package/dist/plugin.cjs
ADDED
|
@@ -0,0 +1,1026 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __create = Object.create;
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
7
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
+
var __export = (target, all) => {
|
|
9
|
+
for (var name in all)
|
|
10
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
11
|
+
};
|
|
12
|
+
var __copyProps = (to, from, except, desc) => {
|
|
13
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
14
|
+
for (let key of __getOwnPropNames(from))
|
|
15
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
16
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
17
|
+
}
|
|
18
|
+
return to;
|
|
19
|
+
};
|
|
20
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
21
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
22
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
23
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
24
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
25
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
26
|
+
mod
|
|
27
|
+
));
|
|
28
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
29
|
+
|
|
30
|
+
// src/plugin.ts
|
|
31
|
+
var plugin_exports = {};
|
|
32
|
+
__export(plugin_exports, {
|
|
33
|
+
DevKitWithDevNode: () => DevKitWithDevNode,
|
|
34
|
+
devNodePlugin: () => devNodePlugin
|
|
35
|
+
});
|
|
36
|
+
module.exports = __toCommonJS(plugin_exports);
|
|
37
|
+
|
|
38
|
+
// src/server-manager.ts
|
|
39
|
+
var import_node_crypto = require("crypto");
|
|
40
|
+
var import_node_fs = require("fs");
|
|
41
|
+
var import_node_os = require("os");
|
|
42
|
+
var import_node_path = require("path");
|
|
43
|
+
var import_config = require("@cfxdevkit/core/config");
|
|
44
|
+
var import_wallet = require("@cfxdevkit/core/wallet");
|
|
45
|
+
var import_node = require("@xcfx/node");
|
|
46
|
+
var import_accounts = require("cive/accounts");
|
|
47
|
+
var import_accounts2 = require("viem/accounts");
|
|
48
|
+
|
|
49
|
+
// src/types.ts
|
|
50
|
+
var NodeError = class extends Error {
|
|
51
|
+
code;
|
|
52
|
+
chain;
|
|
53
|
+
context;
|
|
54
|
+
constructor(message, code, chain, context) {
|
|
55
|
+
super(message);
|
|
56
|
+
this.name = "NodeError";
|
|
57
|
+
this.code = code;
|
|
58
|
+
this.chain = chain;
|
|
59
|
+
this.context = context;
|
|
60
|
+
}
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
// src/server-manager.ts
|
|
64
|
+
var DEFAULT_CORE_RPC_PORT = 12537;
|
|
65
|
+
var DEFAULT_EVM_RPC_PORT = 8545;
|
|
66
|
+
var DEFAULT_WS_PORT = 12536;
|
|
67
|
+
var DEFAULT_EVM_WS_PORT = 8546;
|
|
68
|
+
var ServerManager = class {
|
|
69
|
+
nodeProcess = null;
|
|
70
|
+
server = null;
|
|
71
|
+
config;
|
|
72
|
+
status = "stopped";
|
|
73
|
+
accounts = [];
|
|
74
|
+
mnemonic = "";
|
|
75
|
+
miningAccount = null;
|
|
76
|
+
miningStatus;
|
|
77
|
+
miningTimer = null;
|
|
78
|
+
testClient = null;
|
|
79
|
+
/** True while packMine() is running — auto-miner skips its tick to avoid concurrent mining RPCs. */
|
|
80
|
+
_packMining = false;
|
|
81
|
+
constructor(config) {
|
|
82
|
+
this.config = {
|
|
83
|
+
...config,
|
|
84
|
+
coreRpcPort: config.coreRpcPort || DEFAULT_CORE_RPC_PORT,
|
|
85
|
+
evmRpcPort: config.evmRpcPort || DEFAULT_EVM_RPC_PORT,
|
|
86
|
+
wsPort: config.wsPort || DEFAULT_WS_PORT,
|
|
87
|
+
evmWsPort: config.evmWsPort || DEFAULT_EVM_WS_PORT,
|
|
88
|
+
chainId: config.chainId || 2029,
|
|
89
|
+
// Local Core chain ID
|
|
90
|
+
evmChainId: config.evmChainId || 2030,
|
|
91
|
+
// Local eSpace chain ID
|
|
92
|
+
accounts: config.accounts || 10,
|
|
93
|
+
balance: config.balance || "1000000",
|
|
94
|
+
mnemonic: config.mnemonic,
|
|
95
|
+
// Following xcfx-node test pattern: devPackTxImmediately should be false
|
|
96
|
+
// All mining is managed via testClient.mine() calls
|
|
97
|
+
devPackTxImmediately: false
|
|
98
|
+
};
|
|
99
|
+
this.miningStatus = {
|
|
100
|
+
isRunning: false,
|
|
101
|
+
interval: 1e3,
|
|
102
|
+
blocksMined: 0,
|
|
103
|
+
startTime: void 0
|
|
104
|
+
};
|
|
105
|
+
this.mnemonic = this.config.mnemonic || (0, import_wallet.generateMnemonic)(128);
|
|
106
|
+
this.generateAccountsSync();
|
|
107
|
+
this.generateMiningAccountSync();
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* Return a sanitized copy of the server config with sensitive fields redacted.
|
|
111
|
+
*/
|
|
112
|
+
redactConfig(config) {
|
|
113
|
+
const safe = { ...config };
|
|
114
|
+
if (safe.mnemonic) safe.mnemonic = "[REDACTED]";
|
|
115
|
+
if (safe.accounts) delete safe.accounts;
|
|
116
|
+
return safe;
|
|
117
|
+
}
|
|
118
|
+
/**
|
|
119
|
+
* Start the Conflux development node
|
|
120
|
+
*/
|
|
121
|
+
async start() {
|
|
122
|
+
if (this.status === "running") {
|
|
123
|
+
throw new NodeError(
|
|
124
|
+
"Server is already running",
|
|
125
|
+
"SERVER_ALREADY_RUNNING"
|
|
126
|
+
);
|
|
127
|
+
}
|
|
128
|
+
try {
|
|
129
|
+
this.status = "starting";
|
|
130
|
+
const dataDir = this.config.dataDir || (0, import_node_path.join)((0, import_node_os.homedir)(), ".conflux-devkit", "data");
|
|
131
|
+
try {
|
|
132
|
+
await import_node_fs.promises.mkdir(dataDir, { recursive: true, mode: 493 });
|
|
133
|
+
} catch (error) {
|
|
134
|
+
console.warn("Failed to create data directory:", error);
|
|
135
|
+
}
|
|
136
|
+
const serverConfig = {
|
|
137
|
+
// Correct property names according to @xcfx/node API
|
|
138
|
+
jsonrpcHttpPort: this.config.coreRpcPort,
|
|
139
|
+
jsonrpcHttpEthPort: this.config.evmRpcPort,
|
|
140
|
+
jsonrpcWsPort: this.config.wsPort,
|
|
141
|
+
jsonrpcWsEthPort: this.config.evmWsPort,
|
|
142
|
+
chainId: this.config.chainId,
|
|
143
|
+
evmChainId: this.config.evmChainId,
|
|
144
|
+
// Specify data directory to avoid permission issues
|
|
145
|
+
confluxDataDir: dataDir,
|
|
146
|
+
// Genesis accounts configuration - include mining account for initial funding
|
|
147
|
+
genesisSecrets: [
|
|
148
|
+
...this.accounts.map((acc) => acc.privateKey),
|
|
149
|
+
this.requireMiningAccount().privateKey
|
|
150
|
+
// Add mining account to get initial funds
|
|
151
|
+
],
|
|
152
|
+
genesisEvmSecrets: [
|
|
153
|
+
...this.accounts.map((acc) => acc.evmPrivateKey || acc.privateKey),
|
|
154
|
+
this.requireMiningAccount().evmPrivateKey || this.requireMiningAccount().privateKey
|
|
155
|
+
// Add mining account EVM key
|
|
156
|
+
],
|
|
157
|
+
// Mining configuration - use config value or default to mining account address
|
|
158
|
+
// 'auto' is a sentinel value meaning "use the derived mining account address"
|
|
159
|
+
miningAuthor: this.config.miningAuthor && this.config.miningAuthor !== "auto" ? this.config.miningAuthor : this.miningAccount?.coreAddress,
|
|
160
|
+
// Following the reference test (xcfx-node/evmManualBlockGeneration.test.ts):
|
|
161
|
+
// devPackTxImmediately: false — eSpace txs are ONLY packed by mine({ numTxs }),
|
|
162
|
+
// never by mine({ blocks }). This flag only affects Core space.
|
|
163
|
+
devPackTxImmediately: false,
|
|
164
|
+
log: this.config.logging || false,
|
|
165
|
+
// Genesis block initialization can take time; pass a generous timeout so
|
|
166
|
+
// the native binary doesn't abort the startup handshake prematurely.
|
|
167
|
+
timeout: 6e4,
|
|
168
|
+
retryInterval: 300
|
|
169
|
+
};
|
|
170
|
+
this.server = await (0, import_node.createServer)(serverConfig);
|
|
171
|
+
await this.server.start();
|
|
172
|
+
this.status = "running";
|
|
173
|
+
import_config.defaultNetworkSelector.updateLocalChainUrls(
|
|
174
|
+
this.config.coreRpcPort || DEFAULT_CORE_RPC_PORT,
|
|
175
|
+
this.config.evmRpcPort || DEFAULT_EVM_RPC_PORT,
|
|
176
|
+
this.config.wsPort || DEFAULT_WS_PORT
|
|
177
|
+
);
|
|
178
|
+
import_config.defaultNetworkSelector.onNodeStart(2029, 2030);
|
|
179
|
+
this.setupCleanupHandlers();
|
|
180
|
+
await this.startMining(2e3);
|
|
181
|
+
} catch (error) {
|
|
182
|
+
this.status = "error";
|
|
183
|
+
throw new NodeError(
|
|
184
|
+
`Failed to start server: ${error instanceof Error ? error.message : String(error)}`,
|
|
185
|
+
"SERVER_START_ERROR",
|
|
186
|
+
void 0,
|
|
187
|
+
{
|
|
188
|
+
config: this.redactConfig(this.config),
|
|
189
|
+
originalError: error
|
|
190
|
+
}
|
|
191
|
+
);
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
/**
|
|
195
|
+
* Stop the Conflux development node
|
|
196
|
+
*/
|
|
197
|
+
async stop() {
|
|
198
|
+
if (this.status === "stopped") {
|
|
199
|
+
return;
|
|
200
|
+
}
|
|
201
|
+
try {
|
|
202
|
+
this.status = "stopping";
|
|
203
|
+
if (this.miningStatus.isRunning) {
|
|
204
|
+
try {
|
|
205
|
+
await this.stopMining();
|
|
206
|
+
} catch (error) {
|
|
207
|
+
console.warn("Failed to stop mining during server shutdown:", error);
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
if (this.server) {
|
|
211
|
+
await this.server.stop();
|
|
212
|
+
this.server = null;
|
|
213
|
+
}
|
|
214
|
+
if (this.nodeProcess) {
|
|
215
|
+
this.nodeProcess.kill("SIGTERM");
|
|
216
|
+
this.nodeProcess = null;
|
|
217
|
+
}
|
|
218
|
+
this.testClient = null;
|
|
219
|
+
this.status = "stopped";
|
|
220
|
+
import_config.defaultNetworkSelector.onNodeStop();
|
|
221
|
+
} catch (error) {
|
|
222
|
+
this.status = "error";
|
|
223
|
+
throw new NodeError(
|
|
224
|
+
`Failed to stop server: ${error instanceof Error ? error.message : String(error)}`,
|
|
225
|
+
"SERVER_STOP_ERROR",
|
|
226
|
+
void 0,
|
|
227
|
+
{ originalError: error }
|
|
228
|
+
);
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
/**
|
|
232
|
+
* Restart the Conflux development node
|
|
233
|
+
*/
|
|
234
|
+
async restart() {
|
|
235
|
+
await this.stop();
|
|
236
|
+
await this.start();
|
|
237
|
+
}
|
|
238
|
+
/**
|
|
239
|
+
* Get current server status
|
|
240
|
+
*/
|
|
241
|
+
getStatus() {
|
|
242
|
+
return this.status;
|
|
243
|
+
}
|
|
244
|
+
/**
|
|
245
|
+
* Get comprehensive node status including mining
|
|
246
|
+
*/
|
|
247
|
+
getNodeStatus() {
|
|
248
|
+
return {
|
|
249
|
+
server: this.status,
|
|
250
|
+
mining: this.getMiningStatus(),
|
|
251
|
+
config: this.redactConfig(this.config),
|
|
252
|
+
accounts: this.accounts.length,
|
|
253
|
+
rpcUrls: this.getRpcUrls()
|
|
254
|
+
};
|
|
255
|
+
}
|
|
256
|
+
/**
|
|
257
|
+
* Check if server is running
|
|
258
|
+
*/
|
|
259
|
+
isRunning() {
|
|
260
|
+
return this.status === "running";
|
|
261
|
+
}
|
|
262
|
+
/**
|
|
263
|
+
* Get server configuration
|
|
264
|
+
*/
|
|
265
|
+
getConfig() {
|
|
266
|
+
return {
|
|
267
|
+
...this.redactConfig(this.config)
|
|
268
|
+
};
|
|
269
|
+
}
|
|
270
|
+
/**
|
|
271
|
+
* Get generated accounts
|
|
272
|
+
*/
|
|
273
|
+
getAccounts() {
|
|
274
|
+
return [...this.accounts];
|
|
275
|
+
}
|
|
276
|
+
/**
|
|
277
|
+
* Get the mnemonic phrase
|
|
278
|
+
*/
|
|
279
|
+
getMnemonic() {
|
|
280
|
+
return this.mnemonic;
|
|
281
|
+
}
|
|
282
|
+
/**
|
|
283
|
+
* Get RPC URLs
|
|
284
|
+
*/
|
|
285
|
+
getRpcUrls() {
|
|
286
|
+
const coreWs = `ws://localhost:${this.config.wsPort}`;
|
|
287
|
+
return {
|
|
288
|
+
core: `http://localhost:${this.config.coreRpcPort}`,
|
|
289
|
+
evm: `http://localhost:${this.config.evmRpcPort}`,
|
|
290
|
+
coreWs,
|
|
291
|
+
evmWs: `ws://localhost:${this.config.evmWsPort}`,
|
|
292
|
+
ws: coreWs
|
|
293
|
+
// backward-compat alias for Core WS
|
|
294
|
+
};
|
|
295
|
+
}
|
|
296
|
+
/**
|
|
297
|
+
* Add a new account to the server
|
|
298
|
+
*/
|
|
299
|
+
async addAccount(privateKey) {
|
|
300
|
+
const accountPrivateKey = privateKey || `0x${(0, import_node_crypto.randomBytes)(32).toString("hex")}`;
|
|
301
|
+
const coreAccount = (0, import_accounts.privateKeyToAccount)(
|
|
302
|
+
accountPrivateKey,
|
|
303
|
+
{
|
|
304
|
+
networkId: this.config.chainId || 1
|
|
305
|
+
}
|
|
306
|
+
);
|
|
307
|
+
const evmAccount = (0, import_accounts2.privateKeyToAccount)(
|
|
308
|
+
accountPrivateKey
|
|
309
|
+
);
|
|
310
|
+
const accountInfo = {
|
|
311
|
+
index: this.accounts.length,
|
|
312
|
+
privateKey: accountPrivateKey,
|
|
313
|
+
coreAddress: coreAccount.address,
|
|
314
|
+
evmAddress: evmAccount.address,
|
|
315
|
+
mnemonic: this.mnemonic,
|
|
316
|
+
path: `m/44'/503'/0'/0/${this.accounts.length}`
|
|
317
|
+
};
|
|
318
|
+
this.accounts.push(accountInfo);
|
|
319
|
+
return accountInfo;
|
|
320
|
+
}
|
|
321
|
+
/**
|
|
322
|
+
* Fund an account with CFX
|
|
323
|
+
* Note: @xcfx/node doesn't provide direct funding methods.
|
|
324
|
+
* This would require using RPC calls to send transactions from funded genesis accounts.
|
|
325
|
+
*/
|
|
326
|
+
async fundAccount(address, amount, chainType = "core") {
|
|
327
|
+
if (!this.isRunning() || !this.server) {
|
|
328
|
+
throw new NodeError("Server is not running", "SERVER_NOT_RUNNING");
|
|
329
|
+
}
|
|
330
|
+
throw new NodeError(
|
|
331
|
+
"Direct account funding not implemented. Genesis accounts are automatically funded by @xcfx/node.",
|
|
332
|
+
"NOT_IMPLEMENTED",
|
|
333
|
+
chainType,
|
|
334
|
+
{ address, amount, chainType }
|
|
335
|
+
);
|
|
336
|
+
}
|
|
337
|
+
/**
|
|
338
|
+
* Set next block timestamp (for testing)
|
|
339
|
+
* Note: @xcfx/node doesn't provide direct timestamp control.
|
|
340
|
+
* Use createTestClient from 'cive' and connect to the running node's RPC.
|
|
341
|
+
*/
|
|
342
|
+
async setNextBlockTimestamp(timestamp) {
|
|
343
|
+
if (!this.isRunning() || !this.server) {
|
|
344
|
+
throw new NodeError("Server is not running", "SERVER_NOT_RUNNING");
|
|
345
|
+
}
|
|
346
|
+
throw new NodeError(
|
|
347
|
+
"Direct timestamp control not implemented. Use createTestClient from cive to control block timestamps via RPC.",
|
|
348
|
+
"NOT_IMPLEMENTED",
|
|
349
|
+
"core",
|
|
350
|
+
{ timestamp }
|
|
351
|
+
);
|
|
352
|
+
}
|
|
353
|
+
/**
|
|
354
|
+
* Get server logs
|
|
355
|
+
* Note: @xcfx/node doesn't provide direct log access.
|
|
356
|
+
* Logs would need to be captured during server startup or accessed via system logs.
|
|
357
|
+
*/
|
|
358
|
+
async getLogs(lines = 50) {
|
|
359
|
+
if (!this.isRunning() || !this.server) {
|
|
360
|
+
throw new NodeError("Server is not running", "SERVER_NOT_RUNNING");
|
|
361
|
+
}
|
|
362
|
+
return [
|
|
363
|
+
"Log access not implemented for @xcfx/node.",
|
|
364
|
+
"Consider capturing server output during startup or checking system logs.",
|
|
365
|
+
`Requested ${lines} lines of logs.`
|
|
366
|
+
];
|
|
367
|
+
}
|
|
368
|
+
/**
|
|
369
|
+
* Save server configuration to file
|
|
370
|
+
*/
|
|
371
|
+
async saveConfig(filepath) {
|
|
372
|
+
try {
|
|
373
|
+
const configData = {
|
|
374
|
+
...this.redactConfig(this.config),
|
|
375
|
+
mnemonic: "[REDACTED]",
|
|
376
|
+
accounts: this.accounts.map((a) => ({
|
|
377
|
+
index: a.index,
|
|
378
|
+
coreAddress: a.coreAddress,
|
|
379
|
+
evmAddress: a.evmAddress,
|
|
380
|
+
path: a.path
|
|
381
|
+
})),
|
|
382
|
+
rpcUrls: this.getRpcUrls()
|
|
383
|
+
};
|
|
384
|
+
await import_node_fs.promises.writeFile(filepath, JSON.stringify(configData, null, 2), "utf8");
|
|
385
|
+
} catch (error) {
|
|
386
|
+
throw new NodeError(
|
|
387
|
+
`Failed to save config: ${error instanceof Error ? error.message : String(error)}`,
|
|
388
|
+
"CONFIG_SAVE_ERROR",
|
|
389
|
+
"core",
|
|
390
|
+
{ filepath, originalError: error }
|
|
391
|
+
);
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
/**
|
|
395
|
+
* Load server configuration from file
|
|
396
|
+
*/
|
|
397
|
+
static async loadConfig(filepath) {
|
|
398
|
+
try {
|
|
399
|
+
const configData = await import_node_fs.promises.readFile(filepath, "utf8");
|
|
400
|
+
return JSON.parse(configData);
|
|
401
|
+
} catch (error) {
|
|
402
|
+
throw new NodeError(
|
|
403
|
+
`Failed to load config: ${error instanceof Error ? error.message : String(error)}`,
|
|
404
|
+
"CONFIG_LOAD_ERROR",
|
|
405
|
+
"core",
|
|
406
|
+
{ filepath, originalError: error }
|
|
407
|
+
);
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
/**
|
|
411
|
+
* Generate accounts from mnemonic using core wallet module
|
|
412
|
+
* Called from constructor to ensure accounts are always available
|
|
413
|
+
*/
|
|
414
|
+
generateAccountsSync() {
|
|
415
|
+
const derivedAccounts = (0, import_wallet.deriveAccounts)(this.mnemonic, {
|
|
416
|
+
count: this.config.accounts || 10,
|
|
417
|
+
coreNetworkId: this.config.chainId || 2029
|
|
418
|
+
});
|
|
419
|
+
this.accounts = derivedAccounts.map((account) => ({
|
|
420
|
+
index: account.index,
|
|
421
|
+
privateKey: account.corePrivateKey,
|
|
422
|
+
// Keep Conflux private key as primary
|
|
423
|
+
coreAddress: account.coreAddress,
|
|
424
|
+
evmAddress: account.evmAddress,
|
|
425
|
+
mnemonic: this.mnemonic,
|
|
426
|
+
path: account.paths.core,
|
|
427
|
+
// Store additional EVM-specific info
|
|
428
|
+
evmPrivateKey: account.evmPrivateKey,
|
|
429
|
+
evmPath: account.paths.evm
|
|
430
|
+
}));
|
|
431
|
+
}
|
|
432
|
+
/**
|
|
433
|
+
* Generate dedicated mining account (separate from genesis accounts)
|
|
434
|
+
* This account will receive mining rewards and serve as the faucet
|
|
435
|
+
* Called synchronously from constructor to ensure always available
|
|
436
|
+
*/
|
|
437
|
+
generateMiningAccountSync() {
|
|
438
|
+
const faucetAccount = (0, import_wallet.deriveFaucetAccount)(
|
|
439
|
+
this.mnemonic,
|
|
440
|
+
this.config.chainId || 2029
|
|
441
|
+
);
|
|
442
|
+
this.miningAccount = {
|
|
443
|
+
index: -1,
|
|
444
|
+
// Special index for mining account
|
|
445
|
+
privateKey: faucetAccount.corePrivateKey,
|
|
446
|
+
// Core/Conflux private key
|
|
447
|
+
coreAddress: faucetAccount.coreAddress,
|
|
448
|
+
evmAddress: faucetAccount.evmAddress,
|
|
449
|
+
mnemonic: this.mnemonic,
|
|
450
|
+
path: faucetAccount.paths.core,
|
|
451
|
+
// Store additional EVM-specific info
|
|
452
|
+
evmPrivateKey: faucetAccount.evmPrivateKey,
|
|
453
|
+
evmPath: faucetAccount.paths.evm
|
|
454
|
+
};
|
|
455
|
+
console.log(
|
|
456
|
+
`Generated mining account: Core=${this.miningAccount.coreAddress}, eSpace=${this.miningAccount.evmAddress}`
|
|
457
|
+
);
|
|
458
|
+
}
|
|
459
|
+
/**
|
|
460
|
+
* Returns the mining account, throwing if it has not been initialized.
|
|
461
|
+
* In practice this is always set by generateMiningAccountSync() in the
|
|
462
|
+
* constructor, so the throw is a safety net for unexpected states.
|
|
463
|
+
*/
|
|
464
|
+
requireMiningAccount() {
|
|
465
|
+
if (!this.miningAccount) {
|
|
466
|
+
throw new Error("Mining account has not been initialized.");
|
|
467
|
+
}
|
|
468
|
+
return this.miningAccount;
|
|
469
|
+
}
|
|
470
|
+
// ===== MINING METHODS =====
|
|
471
|
+
/**
|
|
472
|
+
* Start automatic block mining using testClient
|
|
473
|
+
* This creates an interval that mines blocks automatically
|
|
474
|
+
* @param interval Mining interval in milliseconds (default: 2000ms)
|
|
475
|
+
*/
|
|
476
|
+
async startMining(interval) {
|
|
477
|
+
if (!this.isRunning()) {
|
|
478
|
+
throw new NodeError(
|
|
479
|
+
"Server must be running to start mining",
|
|
480
|
+
"SERVER_NOT_RUNNING"
|
|
481
|
+
);
|
|
482
|
+
}
|
|
483
|
+
if (this.miningStatus.isRunning) {
|
|
484
|
+
throw new NodeError(
|
|
485
|
+
"Mining is already running",
|
|
486
|
+
"MINING_ALREADY_RUNNING"
|
|
487
|
+
);
|
|
488
|
+
}
|
|
489
|
+
if (!this.testClient) {
|
|
490
|
+
const { createTestClient, http } = await import("cive");
|
|
491
|
+
this.testClient = createTestClient({
|
|
492
|
+
transport: http(`http://localhost:${this.config.coreRpcPort}`, {
|
|
493
|
+
timeout: 6e4
|
|
494
|
+
})
|
|
495
|
+
});
|
|
496
|
+
}
|
|
497
|
+
const miningInterval = interval || 2e3;
|
|
498
|
+
this.miningStatus = {
|
|
499
|
+
...this.miningStatus,
|
|
500
|
+
isRunning: true,
|
|
501
|
+
interval: miningInterval,
|
|
502
|
+
startTime: /* @__PURE__ */ new Date()
|
|
503
|
+
};
|
|
504
|
+
this.miningTimer = setInterval(async () => {
|
|
505
|
+
try {
|
|
506
|
+
if (this.testClient && !this._packMining) {
|
|
507
|
+
await this.testClient.mine({ numTxs: 1 });
|
|
508
|
+
this.miningStatus = {
|
|
509
|
+
...this.miningStatus,
|
|
510
|
+
blocksMined: (this.miningStatus.blocksMined || 0) + 5
|
|
511
|
+
};
|
|
512
|
+
}
|
|
513
|
+
} catch (error) {
|
|
514
|
+
console.error("Mining error:", error);
|
|
515
|
+
}
|
|
516
|
+
}, miningInterval);
|
|
517
|
+
console.log(`Mining started with ${miningInterval}ms interval`);
|
|
518
|
+
}
|
|
519
|
+
/**
|
|
520
|
+
* Stop automatic block mining
|
|
521
|
+
*/
|
|
522
|
+
async stopMining() {
|
|
523
|
+
if (!this.miningStatus.isRunning) {
|
|
524
|
+
throw new NodeError("Mining is not running", "MINING_NOT_RUNNING");
|
|
525
|
+
}
|
|
526
|
+
if (this.miningTimer) {
|
|
527
|
+
clearInterval(this.miningTimer);
|
|
528
|
+
this.miningTimer = null;
|
|
529
|
+
}
|
|
530
|
+
this.miningStatus = {
|
|
531
|
+
...this.miningStatus,
|
|
532
|
+
isRunning: false,
|
|
533
|
+
startTime: void 0
|
|
534
|
+
};
|
|
535
|
+
console.log("Mining stopped");
|
|
536
|
+
}
|
|
537
|
+
/**
|
|
538
|
+
* Change mining interval (stops and restarts mining with new interval)
|
|
539
|
+
*/
|
|
540
|
+
async setMiningInterval(interval) {
|
|
541
|
+
if (interval < 100) {
|
|
542
|
+
throw new NodeError(
|
|
543
|
+
"Mining interval must be at least 100ms",
|
|
544
|
+
"INVALID_INTERVAL"
|
|
545
|
+
);
|
|
546
|
+
}
|
|
547
|
+
const wasRunning = this.miningStatus.isRunning;
|
|
548
|
+
if (wasRunning) {
|
|
549
|
+
await this.stopMining();
|
|
550
|
+
}
|
|
551
|
+
this.miningStatus = {
|
|
552
|
+
...this.miningStatus,
|
|
553
|
+
interval
|
|
554
|
+
};
|
|
555
|
+
if (wasRunning) {
|
|
556
|
+
await this.startMining(interval);
|
|
557
|
+
}
|
|
558
|
+
console.log(`Mining interval set to ${interval}ms`);
|
|
559
|
+
}
|
|
560
|
+
/**
|
|
561
|
+
* Mine a specific number of blocks immediately
|
|
562
|
+
*/
|
|
563
|
+
async mine(blocks = 1) {
|
|
564
|
+
if (!this.isRunning()) {
|
|
565
|
+
throw new NodeError(
|
|
566
|
+
"Server must be running to mine blocks",
|
|
567
|
+
"SERVER_NOT_RUNNING"
|
|
568
|
+
);
|
|
569
|
+
}
|
|
570
|
+
if (!this.testClient) {
|
|
571
|
+
const { createTestClient, http } = await import("cive");
|
|
572
|
+
this.testClient = createTestClient({
|
|
573
|
+
transport: http(`http://localhost:${this.config.coreRpcPort}`, {
|
|
574
|
+
timeout: 6e4
|
|
575
|
+
})
|
|
576
|
+
});
|
|
577
|
+
}
|
|
578
|
+
try {
|
|
579
|
+
await this.testClient.mine({ blocks });
|
|
580
|
+
this.miningStatus = {
|
|
581
|
+
...this.miningStatus,
|
|
582
|
+
blocksMined: (this.miningStatus.blocksMined || 0) + blocks
|
|
583
|
+
};
|
|
584
|
+
console.log(`Mined ${blocks} empty block(s)`);
|
|
585
|
+
} catch (error) {
|
|
586
|
+
throw new NodeError(
|
|
587
|
+
`Failed to mine blocks: ${error instanceof Error ? error.message : String(error)}`,
|
|
588
|
+
"MINING_ERROR",
|
|
589
|
+
"core",
|
|
590
|
+
{ blocks, originalError: error }
|
|
591
|
+
);
|
|
592
|
+
}
|
|
593
|
+
}
|
|
594
|
+
/**
|
|
595
|
+
* Pack and mine: calls test_generateOneBlock (mine({ numTxs:1 })) which
|
|
596
|
+
* forces pending eSpace/Core transactions into a block. Each call
|
|
597
|
+
* internally generates deferredStateEpochCount (default 5) blocks.
|
|
598
|
+
*
|
|
599
|
+
* This is the ONLY way to include eSpace (EVM) transactions — mine({ blocks })
|
|
600
|
+
* skips the txpool for eSpace. Uses a long timeout because
|
|
601
|
+
* test_generateOneBlock can take several seconds on slow machines.
|
|
602
|
+
*/
|
|
603
|
+
async packMine() {
|
|
604
|
+
if (!this.isRunning()) {
|
|
605
|
+
throw new NodeError(
|
|
606
|
+
"Server must be running to mine blocks",
|
|
607
|
+
"SERVER_NOT_RUNNING"
|
|
608
|
+
);
|
|
609
|
+
}
|
|
610
|
+
const { createTestClient, http } = await import("cive");
|
|
611
|
+
const packClient = createTestClient({
|
|
612
|
+
transport: http(`http://localhost:${this.config.coreRpcPort}`, {
|
|
613
|
+
timeout: 12e4
|
|
614
|
+
})
|
|
615
|
+
});
|
|
616
|
+
this._packMining = true;
|
|
617
|
+
try {
|
|
618
|
+
await packClient.mine({ numTxs: 1 });
|
|
619
|
+
this.miningStatus = {
|
|
620
|
+
...this.miningStatus,
|
|
621
|
+
blocksMined: (this.miningStatus.blocksMined || 0) + 5
|
|
622
|
+
// generates 5 blocks internally
|
|
623
|
+
};
|
|
624
|
+
console.log("Pack-mined: 5 blocks generated, pending txs included");
|
|
625
|
+
} catch (error) {
|
|
626
|
+
throw new NodeError(
|
|
627
|
+
`Failed to pack-mine: ${error instanceof Error ? error.message : String(error)}`,
|
|
628
|
+
"MINING_ERROR",
|
|
629
|
+
"core",
|
|
630
|
+
{ originalError: error }
|
|
631
|
+
);
|
|
632
|
+
} finally {
|
|
633
|
+
this._packMining = false;
|
|
634
|
+
}
|
|
635
|
+
}
|
|
636
|
+
/**
|
|
637
|
+
* Get current mining status
|
|
638
|
+
*/
|
|
639
|
+
getMiningStatus() {
|
|
640
|
+
return { ...this.miningStatus };
|
|
641
|
+
}
|
|
642
|
+
// ===== FAUCET METHODS =====
|
|
643
|
+
/**
|
|
644
|
+
* Get the faucet/mining account (dedicated mining account with separate derivation path)
|
|
645
|
+
* This account receives mining rewards and serves as the faucet
|
|
646
|
+
* Derivation paths: Core=m/44'/503'/1'/0/0, EVM=m/44'/60'/1'/0/0
|
|
647
|
+
*/
|
|
648
|
+
getFaucetAccount() {
|
|
649
|
+
if (!this.miningAccount) {
|
|
650
|
+
throw new NodeError(
|
|
651
|
+
"Mining account not available. Server must be started first.",
|
|
652
|
+
"NO_MINING_ACCOUNT"
|
|
653
|
+
);
|
|
654
|
+
}
|
|
655
|
+
return this.miningAccount;
|
|
656
|
+
}
|
|
657
|
+
/**
|
|
658
|
+
* Fund a Core Space account using the faucet account
|
|
659
|
+
*/
|
|
660
|
+
async fundCoreAccount(targetAddress, amount) {
|
|
661
|
+
if (!this.isRunning()) {
|
|
662
|
+
throw new NodeError(
|
|
663
|
+
"Server must be running to fund accounts",
|
|
664
|
+
"SERVER_NOT_RUNNING"
|
|
665
|
+
);
|
|
666
|
+
}
|
|
667
|
+
const faucetAccount = this.getFaucetAccount();
|
|
668
|
+
try {
|
|
669
|
+
const { createWalletClient, http } = await import("cive");
|
|
670
|
+
const { privateKeyToAccount: privateKeyToAccount2 } = await import("cive/accounts");
|
|
671
|
+
const account = privateKeyToAccount2(
|
|
672
|
+
faucetAccount.privateKey,
|
|
673
|
+
{
|
|
674
|
+
networkId: this.config.chainId || 1
|
|
675
|
+
}
|
|
676
|
+
);
|
|
677
|
+
const walletClient = createWalletClient({
|
|
678
|
+
account,
|
|
679
|
+
chain: (this.config.chainId || 1) === 1029 ? {
|
|
680
|
+
id: 1029,
|
|
681
|
+
name: "Conflux Core",
|
|
682
|
+
nativeCurrency: {
|
|
683
|
+
name: "Conflux",
|
|
684
|
+
symbol: "CFX",
|
|
685
|
+
decimals: 18
|
|
686
|
+
},
|
|
687
|
+
rpcUrls: {
|
|
688
|
+
default: {
|
|
689
|
+
http: [`http://localhost:${this.config.coreRpcPort}`]
|
|
690
|
+
}
|
|
691
|
+
}
|
|
692
|
+
} : {
|
|
693
|
+
id: this.config.chainId || 1,
|
|
694
|
+
name: "Conflux Core Testnet",
|
|
695
|
+
nativeCurrency: {
|
|
696
|
+
name: "Conflux",
|
|
697
|
+
symbol: "CFX",
|
|
698
|
+
decimals: 18
|
|
699
|
+
},
|
|
700
|
+
rpcUrls: {
|
|
701
|
+
default: {
|
|
702
|
+
http: [`http://localhost:${this.config.coreRpcPort}`]
|
|
703
|
+
}
|
|
704
|
+
}
|
|
705
|
+
},
|
|
706
|
+
transport: http(`http://localhost:${this.config.coreRpcPort}`)
|
|
707
|
+
});
|
|
708
|
+
const { parseCFX } = await import("cive");
|
|
709
|
+
const hash = await walletClient.sendTransaction({
|
|
710
|
+
account,
|
|
711
|
+
to: targetAddress,
|
|
712
|
+
value: parseCFX(amount)
|
|
713
|
+
});
|
|
714
|
+
console.log(
|
|
715
|
+
`Funded Core account ${targetAddress} with ${amount} CFX. TX: ${hash}`
|
|
716
|
+
);
|
|
717
|
+
return hash;
|
|
718
|
+
} catch (error) {
|
|
719
|
+
throw new NodeError(
|
|
720
|
+
`Failed to fund Core account: ${error instanceof Error ? error.message : String(error)}`,
|
|
721
|
+
"FAUCET_ERROR",
|
|
722
|
+
"core",
|
|
723
|
+
{
|
|
724
|
+
targetAddress,
|
|
725
|
+
amount,
|
|
726
|
+
faucetAccount: faucetAccount.coreAddress,
|
|
727
|
+
originalError: error
|
|
728
|
+
}
|
|
729
|
+
);
|
|
730
|
+
}
|
|
731
|
+
}
|
|
732
|
+
/**
|
|
733
|
+
* Fund an eSpace account from the Core-Space faucet/mining account.
|
|
734
|
+
*
|
|
735
|
+
* Funds ALWAYS originate from the Core-Space faucet wallet (which accumulates
|
|
736
|
+
* mining rewards). For eSpace (0x…) targets the transfer is routed through the
|
|
737
|
+
* Conflux internal cross-chain bridge contract (0x0888…0006 / transferEVM),
|
|
738
|
+
* which locks CFX on Core and mints it on eSpace — no separate eSpace balance is
|
|
739
|
+
* needed on the faucet account.
|
|
740
|
+
*/
|
|
741
|
+
async fundEvmAccount(targetAddress, amount) {
|
|
742
|
+
if (!this.isRunning()) {
|
|
743
|
+
throw new NodeError(
|
|
744
|
+
"Server must be running to fund accounts",
|
|
745
|
+
"SERVER_NOT_RUNNING"
|
|
746
|
+
);
|
|
747
|
+
}
|
|
748
|
+
const faucetAccount = this.getFaucetAccount();
|
|
749
|
+
try {
|
|
750
|
+
const { createWalletClient, http, parseCFX } = await import("cive");
|
|
751
|
+
const { privateKeyToAccount: privateKeyToAccount2 } = await import("cive/accounts");
|
|
752
|
+
const { hexAddressToBase32, encodeFunctionData, defineChain } = await import("cive/utils");
|
|
753
|
+
const chainId = this.config.chainId || 2029;
|
|
754
|
+
const chain = defineChain({
|
|
755
|
+
id: chainId,
|
|
756
|
+
name: "Conflux Core Local",
|
|
757
|
+
nativeCurrency: { decimals: 18, name: "Conflux", symbol: "CFX" },
|
|
758
|
+
rpcUrls: {
|
|
759
|
+
default: { http: [`http://localhost:${this.config.coreRpcPort}`] }
|
|
760
|
+
}
|
|
761
|
+
});
|
|
762
|
+
const account = privateKeyToAccount2(
|
|
763
|
+
faucetAccount.privateKey,
|
|
764
|
+
{ networkId: chainId }
|
|
765
|
+
);
|
|
766
|
+
const walletClient = createWalletClient({
|
|
767
|
+
account,
|
|
768
|
+
chain,
|
|
769
|
+
transport: http(`http://localhost:${this.config.coreRpcPort}`)
|
|
770
|
+
});
|
|
771
|
+
const bridgeAddress = hexAddressToBase32({
|
|
772
|
+
hexAddress: "0x0888000000000000000000000000000000000006",
|
|
773
|
+
networkId: chainId
|
|
774
|
+
});
|
|
775
|
+
const hash = await walletClient.sendTransaction({
|
|
776
|
+
account,
|
|
777
|
+
chain,
|
|
778
|
+
to: bridgeAddress,
|
|
779
|
+
value: parseCFX(amount),
|
|
780
|
+
data: encodeFunctionData({
|
|
781
|
+
abi: [
|
|
782
|
+
{
|
|
783
|
+
type: "function",
|
|
784
|
+
name: "transferEVM",
|
|
785
|
+
inputs: [{ name: "to", type: "bytes20" }],
|
|
786
|
+
outputs: [{ name: "output", type: "bytes" }],
|
|
787
|
+
stateMutability: "payable"
|
|
788
|
+
}
|
|
789
|
+
],
|
|
790
|
+
functionName: "transferEVM",
|
|
791
|
+
args: [targetAddress]
|
|
792
|
+
})
|
|
793
|
+
});
|
|
794
|
+
console.log(
|
|
795
|
+
`Funded eSpace account ${targetAddress} with ${amount} CFX via Core\u2192eSpace bridge. TX: ${hash}`
|
|
796
|
+
);
|
|
797
|
+
return hash;
|
|
798
|
+
} catch (error) {
|
|
799
|
+
throw new NodeError(
|
|
800
|
+
`Failed to fund eSpace account: ${error instanceof Error ? error.message : String(error)}`,
|
|
801
|
+
"FAUCET_ERROR",
|
|
802
|
+
"evm",
|
|
803
|
+
{
|
|
804
|
+
targetAddress,
|
|
805
|
+
amount,
|
|
806
|
+
faucetCoreAddress: faucetAccount.coreAddress,
|
|
807
|
+
originalError: error
|
|
808
|
+
}
|
|
809
|
+
);
|
|
810
|
+
}
|
|
811
|
+
}
|
|
812
|
+
/**
|
|
813
|
+
* Fund both Core and eSpace accounts for the same private key
|
|
814
|
+
*/
|
|
815
|
+
async fundDualChainAccount(privateKey, coreAmount, evmAmount) {
|
|
816
|
+
const { privateKeyToAccount: privateKeyToAccount2 } = await import("cive/accounts");
|
|
817
|
+
const { privateKeyToAccount: privateKeyToEvmAccount2 } = await import("viem/accounts");
|
|
818
|
+
const coreAccount = privateKeyToAccount2(privateKey, {
|
|
819
|
+
networkId: this.config.chainId || 1
|
|
820
|
+
});
|
|
821
|
+
const evmAccount = privateKeyToEvmAccount2(privateKey);
|
|
822
|
+
const [coreHash, evmHash] = await Promise.all([
|
|
823
|
+
this.fundCoreAccount(coreAccount.address, coreAmount),
|
|
824
|
+
this.fundEvmAccount(evmAccount.address, evmAmount)
|
|
825
|
+
]);
|
|
826
|
+
return {
|
|
827
|
+
coreHash,
|
|
828
|
+
evmHash,
|
|
829
|
+
coreAddress: coreAccount.address,
|
|
830
|
+
evmAddress: evmAccount.address
|
|
831
|
+
};
|
|
832
|
+
}
|
|
833
|
+
/**
|
|
834
|
+
* Check faucet account balances on both chains
|
|
835
|
+
*/
|
|
836
|
+
async getFaucetBalances() {
|
|
837
|
+
if (!this.isRunning()) {
|
|
838
|
+
throw new NodeError(
|
|
839
|
+
"Server must be running to check balances",
|
|
840
|
+
"SERVER_NOT_RUNNING"
|
|
841
|
+
);
|
|
842
|
+
}
|
|
843
|
+
const faucetAccount = this.getFaucetAccount();
|
|
844
|
+
try {
|
|
845
|
+
const [core, evm] = await Promise.all([
|
|
846
|
+
this.getCoreBalance(faucetAccount.coreAddress),
|
|
847
|
+
this.getEvmBalance(faucetAccount.evmAddress)
|
|
848
|
+
]);
|
|
849
|
+
return { core, evm };
|
|
850
|
+
} catch (error) {
|
|
851
|
+
throw new NodeError(
|
|
852
|
+
`Failed to get faucet balances: ${error instanceof Error ? error.message : String(error)}`,
|
|
853
|
+
"BALANCE_CHECK_ERROR",
|
|
854
|
+
void 0,
|
|
855
|
+
{ faucetAccount, originalError: error }
|
|
856
|
+
);
|
|
857
|
+
}
|
|
858
|
+
}
|
|
859
|
+
/**
|
|
860
|
+
* Check Core Space balance
|
|
861
|
+
*/
|
|
862
|
+
async getCoreBalance(address) {
|
|
863
|
+
const { createPublicClient, http, formatCFX } = await import("cive");
|
|
864
|
+
const publicClient = createPublicClient({
|
|
865
|
+
chain: this.config.chainId === 1029 ? {
|
|
866
|
+
id: 1029,
|
|
867
|
+
name: "Conflux Core",
|
|
868
|
+
nativeCurrency: { name: "Conflux", symbol: "CFX", decimals: 18 },
|
|
869
|
+
rpcUrls: {
|
|
870
|
+
default: {
|
|
871
|
+
http: [`http://localhost:${this.config.coreRpcPort}`]
|
|
872
|
+
}
|
|
873
|
+
}
|
|
874
|
+
} : {
|
|
875
|
+
id: 1,
|
|
876
|
+
name: "Conflux Core Testnet",
|
|
877
|
+
nativeCurrency: { name: "Conflux", symbol: "CFX", decimals: 18 },
|
|
878
|
+
rpcUrls: {
|
|
879
|
+
default: {
|
|
880
|
+
http: [`http://localhost:${this.config.coreRpcPort}`]
|
|
881
|
+
}
|
|
882
|
+
}
|
|
883
|
+
},
|
|
884
|
+
transport: http(`http://localhost:${this.config.coreRpcPort}`)
|
|
885
|
+
});
|
|
886
|
+
const balance = await publicClient.getBalance({
|
|
887
|
+
address
|
|
888
|
+
});
|
|
889
|
+
return formatCFX(balance);
|
|
890
|
+
}
|
|
891
|
+
/**
|
|
892
|
+
* Check eSpace balance
|
|
893
|
+
*/
|
|
894
|
+
async getEvmBalance(address) {
|
|
895
|
+
const { createPublicClient, http, formatEther } = await import("viem");
|
|
896
|
+
const publicClient = createPublicClient({
|
|
897
|
+
chain: {
|
|
898
|
+
id: this.config.evmChainId || 71,
|
|
899
|
+
name: "Conflux eSpace Local",
|
|
900
|
+
nativeCurrency: { name: "Conflux", symbol: "CFX", decimals: 18 },
|
|
901
|
+
rpcUrls: {
|
|
902
|
+
default: { http: [`http://localhost:${this.config.evmRpcPort}`] }
|
|
903
|
+
}
|
|
904
|
+
},
|
|
905
|
+
transport: http(`http://localhost:${this.config.evmRpcPort}`)
|
|
906
|
+
});
|
|
907
|
+
const balance = await publicClient.getBalance({
|
|
908
|
+
address
|
|
909
|
+
});
|
|
910
|
+
return formatEther(balance);
|
|
911
|
+
}
|
|
912
|
+
/**
|
|
913
|
+
* Get Ethereum-compatible admin address derived from mnemonic
|
|
914
|
+
* Uses the standard Ethereum derivation path: m/44'/60'/0'/0/0
|
|
915
|
+
* This address will match what MetaMask and other Ethereum wallets derive
|
|
916
|
+
*/
|
|
917
|
+
getEthereumAdminAddress() {
|
|
918
|
+
const account = (0, import_wallet.deriveAccount)(
|
|
919
|
+
this.mnemonic,
|
|
920
|
+
0,
|
|
921
|
+
this.config.chainId || 2029
|
|
922
|
+
);
|
|
923
|
+
return account.evmAddress.toLowerCase();
|
|
924
|
+
}
|
|
925
|
+
/**
|
|
926
|
+
* Set up cleanup handlers for graceful shutdown
|
|
927
|
+
*/
|
|
928
|
+
setupCleanupHandlers() {
|
|
929
|
+
const cleanup = async () => {
|
|
930
|
+
if (this.isRunning()) {
|
|
931
|
+
console.log("Shutting down Conflux development node...");
|
|
932
|
+
await this.stop();
|
|
933
|
+
}
|
|
934
|
+
};
|
|
935
|
+
process.on("SIGINT", cleanup);
|
|
936
|
+
process.on("SIGTERM", cleanup);
|
|
937
|
+
process.on("exit", cleanup);
|
|
938
|
+
}
|
|
939
|
+
};
|
|
940
|
+
|
|
941
|
+
// src/plugin.ts
|
|
942
|
+
var DevKitWithDevNode = class {
|
|
943
|
+
server;
|
|
944
|
+
baseDevKit;
|
|
945
|
+
constructor(baseDevKit, config) {
|
|
946
|
+
this.baseDevKit = baseDevKit;
|
|
947
|
+
this.server = new ServerManager(config);
|
|
948
|
+
}
|
|
949
|
+
// Delegate to base DevKit
|
|
950
|
+
getConfig() {
|
|
951
|
+
return this.baseDevKit.getConfig();
|
|
952
|
+
}
|
|
953
|
+
getRpcUrls() {
|
|
954
|
+
return this.baseDevKit.getRpcUrls();
|
|
955
|
+
}
|
|
956
|
+
// Node lifecycle methods
|
|
957
|
+
async startNode(options = {}) {
|
|
958
|
+
await this.server.start();
|
|
959
|
+
if (options.mining !== false) {
|
|
960
|
+
await this.server.startMining();
|
|
961
|
+
}
|
|
962
|
+
}
|
|
963
|
+
async stopNode() {
|
|
964
|
+
await this.server.stop();
|
|
965
|
+
}
|
|
966
|
+
// Mining methods
|
|
967
|
+
async startMining() {
|
|
968
|
+
await this.server.startMining();
|
|
969
|
+
}
|
|
970
|
+
async stopMining() {
|
|
971
|
+
await this.server.stopMining();
|
|
972
|
+
}
|
|
973
|
+
async mine(blocks = 1) {
|
|
974
|
+
await this.server.mine(blocks);
|
|
975
|
+
}
|
|
976
|
+
getMiningStatus() {
|
|
977
|
+
return this.server.getMiningStatus();
|
|
978
|
+
}
|
|
979
|
+
async setMiningInterval(interval) {
|
|
980
|
+
await this.server.setMiningInterval(interval);
|
|
981
|
+
}
|
|
982
|
+
// Faucet methods
|
|
983
|
+
async getFaucetBalances() {
|
|
984
|
+
return await this.server.getFaucetBalances();
|
|
985
|
+
}
|
|
986
|
+
getFaucetAccount() {
|
|
987
|
+
return this.server.getFaucetAccount();
|
|
988
|
+
}
|
|
989
|
+
async fundAccount(address, amount, chain) {
|
|
990
|
+
if (chain === "core") {
|
|
991
|
+
return await this.server.fundCoreAccount(address, amount);
|
|
992
|
+
} else {
|
|
993
|
+
return await this.server.fundEvmAccount(address, amount);
|
|
994
|
+
}
|
|
995
|
+
}
|
|
996
|
+
// Account methods
|
|
997
|
+
async addAccount() {
|
|
998
|
+
return await this.server.addAccount();
|
|
999
|
+
}
|
|
1000
|
+
getAccounts() {
|
|
1001
|
+
return this.server.getAccounts();
|
|
1002
|
+
}
|
|
1003
|
+
// Utility methods
|
|
1004
|
+
getEthereumAdminAddress() {
|
|
1005
|
+
return this.server.getEthereumAdminAddress();
|
|
1006
|
+
}
|
|
1007
|
+
getServerStatus() {
|
|
1008
|
+
return this.server.getStatus();
|
|
1009
|
+
}
|
|
1010
|
+
isNodeRunning() {
|
|
1011
|
+
return this.server.isRunning();
|
|
1012
|
+
}
|
|
1013
|
+
};
|
|
1014
|
+
var devNodePlugin = {
|
|
1015
|
+
name: "devnode",
|
|
1016
|
+
version: "0.1.0",
|
|
1017
|
+
extendDevKit(devkit, config) {
|
|
1018
|
+
return new DevKitWithDevNode(devkit, config);
|
|
1019
|
+
}
|
|
1020
|
+
};
|
|
1021
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
1022
|
+
0 && (module.exports = {
|
|
1023
|
+
DevKitWithDevNode,
|
|
1024
|
+
devNodePlugin
|
|
1025
|
+
});
|
|
1026
|
+
//# sourceMappingURL=plugin.cjs.map
|