@cfxdevkit/core 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/CHANGELOG.md +35 -0
- package/LICENSE +72 -0
- package/README.md +257 -0
- package/dist/clients/index.cjs +2053 -0
- package/dist/clients/index.cjs.map +1 -0
- package/dist/clients/index.d.cts +7 -0
- package/dist/clients/index.d.ts +7 -0
- package/dist/clients/index.js +2043 -0
- package/dist/clients/index.js.map +1 -0
- package/dist/config/index.cjs +423 -0
- package/dist/config/index.cjs.map +1 -0
- package/dist/config/index.d.cts +99 -0
- package/dist/config/index.d.ts +99 -0
- package/dist/config/index.js +380 -0
- package/dist/config/index.js.map +1 -0
- package/dist/config-BMtaWM0X.d.cts +165 -0
- package/dist/config-BMtaWM0X.d.ts +165 -0
- package/dist/core-C5qe16RS.d.ts +352 -0
- package/dist/core-RZA4aKwj.d.cts +352 -0
- package/dist/index-BhCpy6Fz.d.cts +165 -0
- package/dist/index-Qz84U9Oq.d.ts +165 -0
- package/dist/index.cjs +3773 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +945 -0
- package/dist/index.d.ts +945 -0
- package/dist/index.js +3730 -0
- package/dist/index.js.map +1 -0
- package/dist/types/index.cjs +44 -0
- package/dist/types/index.cjs.map +1 -0
- package/dist/types/index.d.cts +5 -0
- package/dist/types/index.d.ts +5 -0
- package/dist/types/index.js +17 -0
- package/dist/types/index.js.map +1 -0
- package/dist/utils/index.cjs +83 -0
- package/dist/utils/index.cjs.map +1 -0
- package/dist/utils/index.d.cts +11 -0
- package/dist/utils/index.d.ts +11 -0
- package/dist/utils/index.js +56 -0
- package/dist/utils/index.js.map +1 -0
- package/dist/wallet/index.cjs +852 -0
- package/dist/wallet/index.cjs.map +1 -0
- package/dist/wallet/index.d.cts +726 -0
- package/dist/wallet/index.d.ts +726 -0
- package/dist/wallet/index.js +815 -0
- package/dist/wallet/index.js.map +1 -0
- package/package.json +119 -0
|
@@ -0,0 +1,2043 @@
|
|
|
1
|
+
// src/clients/core.ts
|
|
2
|
+
import {
|
|
3
|
+
createPublicClient,
|
|
4
|
+
createTestClient,
|
|
5
|
+
createWalletClient,
|
|
6
|
+
formatCFX,
|
|
7
|
+
http,
|
|
8
|
+
parseCFX
|
|
9
|
+
} from "cive";
|
|
10
|
+
import { privateKeyToAccount } from "cive/accounts";
|
|
11
|
+
import {
|
|
12
|
+
defineChain as defineChain2,
|
|
13
|
+
encodeFunctionData,
|
|
14
|
+
formatUnits,
|
|
15
|
+
hexAddressToBase32,
|
|
16
|
+
isAddress as isCoreAddress
|
|
17
|
+
} from "cive/utils";
|
|
18
|
+
import { isAddress as isEspaceAddress } from "viem";
|
|
19
|
+
|
|
20
|
+
// src/config/chains.ts
|
|
21
|
+
import { defineChain } from "cive/utils";
|
|
22
|
+
import { defineChain as defineEvmChain } from "viem";
|
|
23
|
+
var CORE_MAINNET = {
|
|
24
|
+
id: 1029,
|
|
25
|
+
name: "conflux-core",
|
|
26
|
+
type: "core",
|
|
27
|
+
testnet: false,
|
|
28
|
+
nativeCurrency: {
|
|
29
|
+
name: "Conflux",
|
|
30
|
+
symbol: "CFX",
|
|
31
|
+
decimals: 18
|
|
32
|
+
},
|
|
33
|
+
rpcUrls: {
|
|
34
|
+
default: {
|
|
35
|
+
http: ["https://main.confluxrpc.com"],
|
|
36
|
+
webSocket: ["wss://main.confluxrpc.com/ws"]
|
|
37
|
+
}
|
|
38
|
+
},
|
|
39
|
+
blockExplorers: {
|
|
40
|
+
default: {
|
|
41
|
+
name: "ConfluxScan",
|
|
42
|
+
url: "https://confluxscan.io"
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
};
|
|
46
|
+
var CORE_TESTNET = {
|
|
47
|
+
id: 1,
|
|
48
|
+
name: "conflux-core-testnet",
|
|
49
|
+
type: "core",
|
|
50
|
+
testnet: true,
|
|
51
|
+
nativeCurrency: {
|
|
52
|
+
name: "Conflux",
|
|
53
|
+
symbol: "CFX",
|
|
54
|
+
decimals: 18
|
|
55
|
+
},
|
|
56
|
+
rpcUrls: {
|
|
57
|
+
default: {
|
|
58
|
+
http: ["https://test.confluxrpc.com"],
|
|
59
|
+
webSocket: ["wss://test.confluxrpc.com/ws"]
|
|
60
|
+
}
|
|
61
|
+
},
|
|
62
|
+
blockExplorers: {
|
|
63
|
+
default: {
|
|
64
|
+
name: "ConfluxScan Testnet",
|
|
65
|
+
url: "https://testnet.confluxscan.io"
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
};
|
|
69
|
+
var CORE_LOCAL = {
|
|
70
|
+
id: 2029,
|
|
71
|
+
name: "conflux-core-local",
|
|
72
|
+
type: "core",
|
|
73
|
+
testnet: true,
|
|
74
|
+
nativeCurrency: {
|
|
75
|
+
name: "Conflux",
|
|
76
|
+
symbol: "CFX",
|
|
77
|
+
decimals: 18
|
|
78
|
+
},
|
|
79
|
+
rpcUrls: {
|
|
80
|
+
default: {
|
|
81
|
+
http: ["http://localhost:12537"],
|
|
82
|
+
webSocket: ["ws://localhost:12536"]
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
};
|
|
86
|
+
var EVM_MAINNET = {
|
|
87
|
+
id: 1030,
|
|
88
|
+
name: "conflux-espace",
|
|
89
|
+
type: "evm",
|
|
90
|
+
testnet: false,
|
|
91
|
+
nativeCurrency: {
|
|
92
|
+
name: "Conflux",
|
|
93
|
+
symbol: "CFX",
|
|
94
|
+
decimals: 18
|
|
95
|
+
},
|
|
96
|
+
rpcUrls: {
|
|
97
|
+
default: {
|
|
98
|
+
http: ["https://evm.confluxrpc.com"]
|
|
99
|
+
}
|
|
100
|
+
},
|
|
101
|
+
blockExplorers: {
|
|
102
|
+
default: {
|
|
103
|
+
name: "ConfluxScan eSpace",
|
|
104
|
+
url: "https://evm.confluxscan.net"
|
|
105
|
+
}
|
|
106
|
+
},
|
|
107
|
+
contracts: {
|
|
108
|
+
multicall3: {
|
|
109
|
+
address: "0xcA11bde05977b3631167028862bE2a173976CA11",
|
|
110
|
+
blockCreated: 62512243
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
};
|
|
114
|
+
var EVM_TESTNET = {
|
|
115
|
+
id: 71,
|
|
116
|
+
name: "conflux-espace-testnet",
|
|
117
|
+
type: "evm",
|
|
118
|
+
testnet: true,
|
|
119
|
+
nativeCurrency: {
|
|
120
|
+
name: "Conflux",
|
|
121
|
+
symbol: "CFX",
|
|
122
|
+
decimals: 18
|
|
123
|
+
},
|
|
124
|
+
rpcUrls: {
|
|
125
|
+
default: {
|
|
126
|
+
http: ["https://evmtestnet.confluxrpc.com"]
|
|
127
|
+
}
|
|
128
|
+
},
|
|
129
|
+
blockExplorers: {
|
|
130
|
+
default: {
|
|
131
|
+
name: "ConfluxScan eSpace Testnet",
|
|
132
|
+
url: "https://evmtestnet.confluxscan.net"
|
|
133
|
+
}
|
|
134
|
+
},
|
|
135
|
+
contracts: {
|
|
136
|
+
multicall3: {
|
|
137
|
+
address: "0xcA11bde05977b3631167028862bE2a173976CA11",
|
|
138
|
+
blockCreated: 117499050
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
};
|
|
142
|
+
var EVM_LOCAL = {
|
|
143
|
+
id: 2030,
|
|
144
|
+
name: "conflux-espace-local",
|
|
145
|
+
type: "evm",
|
|
146
|
+
testnet: true,
|
|
147
|
+
nativeCurrency: {
|
|
148
|
+
name: "Conflux",
|
|
149
|
+
symbol: "CFX",
|
|
150
|
+
decimals: 18
|
|
151
|
+
},
|
|
152
|
+
rpcUrls: {
|
|
153
|
+
default: {
|
|
154
|
+
http: ["http://localhost:8545"]
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
};
|
|
158
|
+
var SUPPORTED_CHAINS = {
|
|
159
|
+
1029: CORE_MAINNET,
|
|
160
|
+
1: CORE_TESTNET,
|
|
161
|
+
2029: CORE_LOCAL,
|
|
162
|
+
1030: EVM_MAINNET,
|
|
163
|
+
71: EVM_TESTNET,
|
|
164
|
+
2030: EVM_LOCAL
|
|
165
|
+
};
|
|
166
|
+
function getChainConfig(chainId) {
|
|
167
|
+
const config = SUPPORTED_CHAINS[chainId];
|
|
168
|
+
if (!config) {
|
|
169
|
+
throw new Error(`Unsupported chain ID: ${chainId}`);
|
|
170
|
+
}
|
|
171
|
+
return config;
|
|
172
|
+
}
|
|
173
|
+
function isValidChainId(chainId) {
|
|
174
|
+
return chainId in SUPPORTED_CHAINS;
|
|
175
|
+
}
|
|
176
|
+
var NetworkSelector = class {
|
|
177
|
+
currentChainId;
|
|
178
|
+
previousChainId = null;
|
|
179
|
+
listeners = /* @__PURE__ */ new Set();
|
|
180
|
+
nodeRunningListeners = /* @__PURE__ */ new Set();
|
|
181
|
+
isNodeRunning = false;
|
|
182
|
+
lockedToLocal = false;
|
|
183
|
+
constructor(initialChainId = 1) {
|
|
184
|
+
this.currentChainId = initialChainId;
|
|
185
|
+
}
|
|
186
|
+
getCurrentChain() {
|
|
187
|
+
return getChainConfig(this.currentChainId);
|
|
188
|
+
}
|
|
189
|
+
getCurrentChainId() {
|
|
190
|
+
return this.currentChainId;
|
|
191
|
+
}
|
|
192
|
+
/**
|
|
193
|
+
* Switch to a specific chain
|
|
194
|
+
* @param chainId - Chain ID to switch to
|
|
195
|
+
* @param force - Force switch even if node is running (for wallet operations)
|
|
196
|
+
*/
|
|
197
|
+
switchChain(chainId, force = false) {
|
|
198
|
+
if (!isValidChainId(chainId)) {
|
|
199
|
+
throw new Error(`Invalid chain ID: ${chainId}`);
|
|
200
|
+
}
|
|
201
|
+
if (this.isNodeRunning && !this.isLocalChain(chainId) && !force) {
|
|
202
|
+
console.warn(
|
|
203
|
+
`Cannot switch to chain ${chainId} while local node is running. Use force=true for wallet operations.`
|
|
204
|
+
);
|
|
205
|
+
return;
|
|
206
|
+
}
|
|
207
|
+
if (this.currentChainId !== chainId) {
|
|
208
|
+
this.currentChainId = chainId;
|
|
209
|
+
this.notifyListeners();
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
/**
|
|
213
|
+
* Called when local node starts - automatically switches to local chains
|
|
214
|
+
*/
|
|
215
|
+
onNodeStart(coreChainId = 2029, evmChainId = 2030) {
|
|
216
|
+
if (!this.isNodeRunning) {
|
|
217
|
+
if (!this.isLocal()) {
|
|
218
|
+
this.previousChainId = this.currentChainId;
|
|
219
|
+
}
|
|
220
|
+
this.isNodeRunning = true;
|
|
221
|
+
this.lockedToLocal = true;
|
|
222
|
+
const targetLocalChain = this.isEvm() ? evmChainId : coreChainId;
|
|
223
|
+
this.switchChain(targetLocalChain, true);
|
|
224
|
+
this.notifyNodeRunningListeners();
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
/**
|
|
228
|
+
* Called when local node stops - can restore previous chain
|
|
229
|
+
*/
|
|
230
|
+
onNodeStop(restorePrevious = true) {
|
|
231
|
+
if (this.isNodeRunning) {
|
|
232
|
+
this.isNodeRunning = false;
|
|
233
|
+
this.lockedToLocal = false;
|
|
234
|
+
if (restorePrevious && this.previousChainId) {
|
|
235
|
+
this.switchChain(this.previousChainId, true);
|
|
236
|
+
this.previousChainId = null;
|
|
237
|
+
}
|
|
238
|
+
this.notifyNodeRunningListeners();
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
/**
|
|
242
|
+
* Check if node is currently running
|
|
243
|
+
*/
|
|
244
|
+
getNodeRunningStatus() {
|
|
245
|
+
return this.isNodeRunning;
|
|
246
|
+
}
|
|
247
|
+
/**
|
|
248
|
+
* Check if selector is locked to local chains
|
|
249
|
+
*/
|
|
250
|
+
isLockedToLocal() {
|
|
251
|
+
return this.lockedToLocal;
|
|
252
|
+
}
|
|
253
|
+
onChainChange(listener) {
|
|
254
|
+
this.listeners.add(listener);
|
|
255
|
+
return () => this.listeners.delete(listener);
|
|
256
|
+
}
|
|
257
|
+
onNodeRunningChange(listener) {
|
|
258
|
+
this.nodeRunningListeners.add(listener);
|
|
259
|
+
return () => this.nodeRunningListeners.delete(listener);
|
|
260
|
+
}
|
|
261
|
+
notifyListeners() {
|
|
262
|
+
for (const listener of this.listeners) {
|
|
263
|
+
try {
|
|
264
|
+
listener(this.currentChainId);
|
|
265
|
+
} catch (error) {
|
|
266
|
+
console.error("Error in chain change listener:", error);
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
notifyNodeRunningListeners() {
|
|
271
|
+
for (const listener of this.nodeRunningListeners) {
|
|
272
|
+
try {
|
|
273
|
+
listener(this.isNodeRunning);
|
|
274
|
+
} catch (error) {
|
|
275
|
+
console.error("Error in node running listener:", error);
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
isLocalChain(chainId) {
|
|
280
|
+
return chainId === 2029 || chainId === 2030;
|
|
281
|
+
}
|
|
282
|
+
// Helper methods for chain type detection
|
|
283
|
+
isCore() {
|
|
284
|
+
return this.getCurrentChain().type === "core";
|
|
285
|
+
}
|
|
286
|
+
isEvm() {
|
|
287
|
+
return this.getCurrentChain().type === "evm";
|
|
288
|
+
}
|
|
289
|
+
isTestnet() {
|
|
290
|
+
return this.getCurrentChain().testnet;
|
|
291
|
+
}
|
|
292
|
+
isLocal() {
|
|
293
|
+
return this.currentChainId === 2029 || this.currentChainId === 2030;
|
|
294
|
+
}
|
|
295
|
+
// Get corresponding chain IDs
|
|
296
|
+
getCorrespondingChainId() {
|
|
297
|
+
switch (this.currentChainId) {
|
|
298
|
+
case 1029:
|
|
299
|
+
return 1030;
|
|
300
|
+
// Core mainnet -> eSpace mainnet
|
|
301
|
+
case 1030:
|
|
302
|
+
return 1029;
|
|
303
|
+
// eSpace mainnet -> Core mainnet
|
|
304
|
+
case 1:
|
|
305
|
+
return 71;
|
|
306
|
+
// Core testnet -> eSpace testnet
|
|
307
|
+
case 71:
|
|
308
|
+
return 1;
|
|
309
|
+
// eSpace testnet -> Core testnet
|
|
310
|
+
case 2029:
|
|
311
|
+
return 2030;
|
|
312
|
+
// Core local -> eSpace local
|
|
313
|
+
case 2030:
|
|
314
|
+
return 2029;
|
|
315
|
+
// eSpace local -> Core local
|
|
316
|
+
default:
|
|
317
|
+
return null;
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
/**
|
|
321
|
+
* Update local chain configurations with actual node URLs
|
|
322
|
+
* Called when ServerManager starts with specific ports
|
|
323
|
+
*/
|
|
324
|
+
updateLocalChainUrls(coreRpcPort, evmRpcPort, wsPort) {
|
|
325
|
+
const coreLocal = SUPPORTED_CHAINS[2029];
|
|
326
|
+
if (coreLocal) {
|
|
327
|
+
coreLocal.rpcUrls.default.http = [`http://localhost:${coreRpcPort}`];
|
|
328
|
+
if (wsPort) {
|
|
329
|
+
coreLocal.rpcUrls.default.webSocket = [`ws://localhost:${wsPort}`];
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
const evmLocal = SUPPORTED_CHAINS[2030];
|
|
333
|
+
if (evmLocal) {
|
|
334
|
+
evmLocal.rpcUrls.default.http = [`http://localhost:${evmRpcPort}`];
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
};
|
|
338
|
+
var defaultNetworkSelector = new NetworkSelector();
|
|
339
|
+
|
|
340
|
+
// src/types/config.ts
|
|
341
|
+
var NodeError = class extends Error {
|
|
342
|
+
code;
|
|
343
|
+
chain;
|
|
344
|
+
context;
|
|
345
|
+
constructor(message, code, chain, context) {
|
|
346
|
+
super(message);
|
|
347
|
+
this.name = "NodeError";
|
|
348
|
+
this.code = code;
|
|
349
|
+
this.chain = chain;
|
|
350
|
+
this.context = context;
|
|
351
|
+
}
|
|
352
|
+
};
|
|
353
|
+
|
|
354
|
+
// src/clients/core.ts
|
|
355
|
+
var conflux = defineChain2({
|
|
356
|
+
id: 1029,
|
|
357
|
+
name: "Conflux Core",
|
|
358
|
+
nativeCurrency: { name: "Conflux", symbol: "CFX", decimals: 18 },
|
|
359
|
+
rpcUrls: { default: { http: ["https://main.confluxrpc.com"] } }
|
|
360
|
+
});
|
|
361
|
+
var confluxTestnet = defineChain2({
|
|
362
|
+
id: 1,
|
|
363
|
+
name: "Conflux Core Testnet",
|
|
364
|
+
nativeCurrency: { name: "Conflux", symbol: "CFX", decimals: 18 },
|
|
365
|
+
rpcUrls: { default: { http: ["https://test.confluxrpc.com"] } }
|
|
366
|
+
});
|
|
367
|
+
var CoreClient = class {
|
|
368
|
+
chainType = "core";
|
|
369
|
+
chainId;
|
|
370
|
+
address;
|
|
371
|
+
publicClient;
|
|
372
|
+
chain;
|
|
373
|
+
constructor(config) {
|
|
374
|
+
this.chainId = config.chainId;
|
|
375
|
+
this.chain = defineChain2({
|
|
376
|
+
id: this.chainId,
|
|
377
|
+
name: `ConfluxCore-${this.chainId}`,
|
|
378
|
+
nativeCurrency: {
|
|
379
|
+
decimals: 18,
|
|
380
|
+
name: "Conflux",
|
|
381
|
+
symbol: "CFX"
|
|
382
|
+
},
|
|
383
|
+
rpcUrls: {
|
|
384
|
+
default: {
|
|
385
|
+
http: [config.rpcUrl],
|
|
386
|
+
webSocket: config.wsUrl ? [config.wsUrl] : void 0
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
});
|
|
390
|
+
this.publicClient = createPublicClient({
|
|
391
|
+
chain: this.chain,
|
|
392
|
+
transport: http(config.rpcUrl),
|
|
393
|
+
pollingInterval: config.pollingInterval || 1e3
|
|
394
|
+
});
|
|
395
|
+
this.address = "";
|
|
396
|
+
}
|
|
397
|
+
async getBlockNumber() {
|
|
398
|
+
try {
|
|
399
|
+
const epochNumber = await this.publicClient.getEpochNumber();
|
|
400
|
+
return BigInt(epochNumber.toString());
|
|
401
|
+
} catch (error) {
|
|
402
|
+
throw new NodeError(
|
|
403
|
+
`Failed to get block number: ${error instanceof Error ? error.message : String(error)}`,
|
|
404
|
+
"BLOCK_NUMBER_ERROR",
|
|
405
|
+
"core",
|
|
406
|
+
{ originalError: error }
|
|
407
|
+
);
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
async getBalance(address) {
|
|
411
|
+
if (!isCoreAddress(address)) {
|
|
412
|
+
throw new NodeError(
|
|
413
|
+
"Invalid Core address format",
|
|
414
|
+
"INVALID_ADDRESS",
|
|
415
|
+
"core"
|
|
416
|
+
);
|
|
417
|
+
}
|
|
418
|
+
try {
|
|
419
|
+
const balance = await this.publicClient.getBalance({ address });
|
|
420
|
+
return formatCFX(balance);
|
|
421
|
+
} catch (error) {
|
|
422
|
+
throw new NodeError(
|
|
423
|
+
`Failed to get balance: ${error instanceof Error ? error.message : String(error)}`,
|
|
424
|
+
"BALANCE_ERROR",
|
|
425
|
+
"core",
|
|
426
|
+
{ address, originalError: error }
|
|
427
|
+
);
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
async getGasPrice() {
|
|
431
|
+
try {
|
|
432
|
+
const gasPrice = await this.publicClient.getGasPrice();
|
|
433
|
+
return gasPrice;
|
|
434
|
+
} catch (error) {
|
|
435
|
+
throw new NodeError(
|
|
436
|
+
`Failed to get gas price: ${error instanceof Error ? error.message : String(error)}`,
|
|
437
|
+
"GAS_PRICE_ERROR",
|
|
438
|
+
"core",
|
|
439
|
+
{ originalError: error }
|
|
440
|
+
);
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
async estimateGas(tx) {
|
|
444
|
+
try {
|
|
445
|
+
const estimate = await this.publicClient.request({
|
|
446
|
+
method: "cfx_estimateGasAndCollateral",
|
|
447
|
+
params: [
|
|
448
|
+
{
|
|
449
|
+
to: tx.to,
|
|
450
|
+
value: tx.value ? `0x${tx.value.toString(16)}` : void 0,
|
|
451
|
+
data: tx.data
|
|
452
|
+
}
|
|
453
|
+
]
|
|
454
|
+
});
|
|
455
|
+
return BigInt(estimate.gasLimit);
|
|
456
|
+
} catch (error) {
|
|
457
|
+
throw new NodeError(
|
|
458
|
+
`Failed to estimate gas: ${error instanceof Error ? error.message : String(error)}`,
|
|
459
|
+
"GAS_ESTIMATE_ERROR",
|
|
460
|
+
"core",
|
|
461
|
+
{ transaction: tx, originalError: error }
|
|
462
|
+
);
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
async sendTransaction(_tx) {
|
|
466
|
+
throw new NodeError(
|
|
467
|
+
"Cannot send transaction from public client. Use wallet client instead.",
|
|
468
|
+
"WALLET_REQUIRED",
|
|
469
|
+
"core"
|
|
470
|
+
);
|
|
471
|
+
}
|
|
472
|
+
async waitForTransaction(hash) {
|
|
473
|
+
try {
|
|
474
|
+
const receipt = await this.publicClient.waitForTransactionReceipt({
|
|
475
|
+
hash
|
|
476
|
+
});
|
|
477
|
+
return {
|
|
478
|
+
hash: receipt.transactionHash,
|
|
479
|
+
blockNumber: BigInt(receipt.epochNumber?.toString() || "0"),
|
|
480
|
+
blockHash: receipt.blockHash || "",
|
|
481
|
+
transactionIndex: Number(receipt.index || 0),
|
|
482
|
+
status: receipt.outcomeStatus === "success" ? "success" : "reverted",
|
|
483
|
+
gasUsed: receipt.gasUsed || 0n,
|
|
484
|
+
contractAddress: receipt.contractCreated || void 0,
|
|
485
|
+
logs: receipt.log?.map((log) => ({
|
|
486
|
+
address: log.address || "",
|
|
487
|
+
topics: log.topics || [],
|
|
488
|
+
data: log.data || "0x",
|
|
489
|
+
blockNumber: BigInt(log.epochNumber?.toString() || "0"),
|
|
490
|
+
transactionHash: log.transactionHash || "",
|
|
491
|
+
logIndex: Number(log.logIndex || 0)
|
|
492
|
+
})) || []
|
|
493
|
+
};
|
|
494
|
+
} catch (error) {
|
|
495
|
+
throw new NodeError(
|
|
496
|
+
`Failed to wait for transaction: ${error instanceof Error ? error.message : String(error)}`,
|
|
497
|
+
"TRANSACTION_WAIT_ERROR",
|
|
498
|
+
"core",
|
|
499
|
+
{ hash, originalError: error }
|
|
500
|
+
);
|
|
501
|
+
}
|
|
502
|
+
}
|
|
503
|
+
async getTokenBalance(tokenAddress, holderAddress) {
|
|
504
|
+
const holder = holderAddress || this.address;
|
|
505
|
+
if (!this.isValidAddress(tokenAddress)) {
|
|
506
|
+
throw new NodeError(
|
|
507
|
+
"Invalid token address format",
|
|
508
|
+
"INVALID_ADDRESS",
|
|
509
|
+
"core",
|
|
510
|
+
{ tokenAddress }
|
|
511
|
+
);
|
|
512
|
+
}
|
|
513
|
+
if (!this.isValidAddress(holder)) {
|
|
514
|
+
throw new NodeError(
|
|
515
|
+
"Invalid holder address format",
|
|
516
|
+
"INVALID_ADDRESS",
|
|
517
|
+
"core",
|
|
518
|
+
{ holder }
|
|
519
|
+
);
|
|
520
|
+
}
|
|
521
|
+
try {
|
|
522
|
+
const [balance, decimals] = await Promise.all([
|
|
523
|
+
this.publicClient.readContract({
|
|
524
|
+
address: tokenAddress,
|
|
525
|
+
abi: [
|
|
526
|
+
{
|
|
527
|
+
name: "balanceOf",
|
|
528
|
+
type: "function",
|
|
529
|
+
inputs: [{ name: "account", type: "address" }],
|
|
530
|
+
outputs: [{ name: "", type: "uint256" }],
|
|
531
|
+
stateMutability: "view"
|
|
532
|
+
}
|
|
533
|
+
],
|
|
534
|
+
functionName: "balanceOf",
|
|
535
|
+
args: [holder]
|
|
536
|
+
}),
|
|
537
|
+
this.publicClient.readContract({
|
|
538
|
+
address: tokenAddress,
|
|
539
|
+
abi: [
|
|
540
|
+
{
|
|
541
|
+
name: "decimals",
|
|
542
|
+
type: "function",
|
|
543
|
+
inputs: [],
|
|
544
|
+
outputs: [{ name: "", type: "uint8" }],
|
|
545
|
+
stateMutability: "view"
|
|
546
|
+
}
|
|
547
|
+
],
|
|
548
|
+
functionName: "decimals"
|
|
549
|
+
})
|
|
550
|
+
]);
|
|
551
|
+
return this.formatTokenAmount(balance, Number(decimals));
|
|
552
|
+
} catch (error) {
|
|
553
|
+
throw new NodeError(
|
|
554
|
+
`Failed to get token balance: ${error instanceof Error ? error.message : String(error)}`,
|
|
555
|
+
"TOKEN_BALANCE_ERROR",
|
|
556
|
+
"core",
|
|
557
|
+
{ tokenAddress, holder, originalError: error }
|
|
558
|
+
);
|
|
559
|
+
}
|
|
560
|
+
}
|
|
561
|
+
watchBlocks(callback) {
|
|
562
|
+
return this.publicClient.watchEpochNumber({
|
|
563
|
+
emitMissed: false,
|
|
564
|
+
epochTag: "latest_mined",
|
|
565
|
+
onEpochNumber: async (epochNumber) => {
|
|
566
|
+
try {
|
|
567
|
+
const blockHashes = await this.publicClient.getBlocksByEpoch({
|
|
568
|
+
epochNumber
|
|
569
|
+
});
|
|
570
|
+
for (const hash of blockHashes) {
|
|
571
|
+
try {
|
|
572
|
+
const block = await this.publicClient.getBlock({
|
|
573
|
+
blockHash: hash
|
|
574
|
+
});
|
|
575
|
+
const blockEvent = {
|
|
576
|
+
chainType: "core",
|
|
577
|
+
blockNumber: BigInt(block.epochNumber?.toString() || "0"),
|
|
578
|
+
blockHash: block.hash || "",
|
|
579
|
+
timestamp: Number(block.timestamp || 0),
|
|
580
|
+
transactionCount: block.transactions?.length || 0
|
|
581
|
+
};
|
|
582
|
+
callback(blockEvent);
|
|
583
|
+
} catch (error) {
|
|
584
|
+
console.error(`Failed to process block ${hash}:`, error);
|
|
585
|
+
}
|
|
586
|
+
}
|
|
587
|
+
} catch (error) {
|
|
588
|
+
console.error(
|
|
589
|
+
`Failed to get blocks for epoch ${epochNumber}:`,
|
|
590
|
+
error
|
|
591
|
+
);
|
|
592
|
+
}
|
|
593
|
+
}
|
|
594
|
+
});
|
|
595
|
+
}
|
|
596
|
+
watchTransactions(callback) {
|
|
597
|
+
return this.publicClient.watchEpochNumber({
|
|
598
|
+
emitMissed: false,
|
|
599
|
+
epochTag: "latest_mined",
|
|
600
|
+
onEpochNumber: async (epochNumber) => {
|
|
601
|
+
try {
|
|
602
|
+
const blockHashes = await this.publicClient.getBlocksByEpoch({
|
|
603
|
+
epochNumber
|
|
604
|
+
});
|
|
605
|
+
for (const hash of blockHashes) {
|
|
606
|
+
try {
|
|
607
|
+
const block = await this.publicClient.getBlock({
|
|
608
|
+
blockHash: hash
|
|
609
|
+
});
|
|
610
|
+
await Promise.all(
|
|
611
|
+
(block.transactions || []).map(
|
|
612
|
+
async (txHash) => {
|
|
613
|
+
try {
|
|
614
|
+
const tx = await this.publicClient.getTransaction({
|
|
615
|
+
hash: txHash
|
|
616
|
+
});
|
|
617
|
+
const txEvent = {
|
|
618
|
+
chainType: "core",
|
|
619
|
+
hash: tx.hash,
|
|
620
|
+
from: tx.from,
|
|
621
|
+
to: tx.to || void 0,
|
|
622
|
+
value: tx.value || 0n,
|
|
623
|
+
blockNumber: BigInt(
|
|
624
|
+
block.epochNumber?.toString() || "0"
|
|
625
|
+
)
|
|
626
|
+
};
|
|
627
|
+
callback(txEvent);
|
|
628
|
+
} catch (error) {
|
|
629
|
+
console.error(
|
|
630
|
+
`Failed to get transaction ${txHash}:`,
|
|
631
|
+
error
|
|
632
|
+
);
|
|
633
|
+
}
|
|
634
|
+
}
|
|
635
|
+
)
|
|
636
|
+
);
|
|
637
|
+
} catch (error) {
|
|
638
|
+
console.error(`Failed to process block ${hash}:`, error);
|
|
639
|
+
}
|
|
640
|
+
}
|
|
641
|
+
} catch (error) {
|
|
642
|
+
console.error(
|
|
643
|
+
`Failed to get blocks for epoch ${epochNumber}:`,
|
|
644
|
+
error
|
|
645
|
+
);
|
|
646
|
+
}
|
|
647
|
+
}
|
|
648
|
+
});
|
|
649
|
+
}
|
|
650
|
+
isValidAddress(address) {
|
|
651
|
+
return isCoreAddress(address);
|
|
652
|
+
}
|
|
653
|
+
formatAmount(amount) {
|
|
654
|
+
return formatCFX(amount);
|
|
655
|
+
}
|
|
656
|
+
parseAmount(amount) {
|
|
657
|
+
return parseCFX(amount);
|
|
658
|
+
}
|
|
659
|
+
getInternalClient() {
|
|
660
|
+
return this.publicClient;
|
|
661
|
+
}
|
|
662
|
+
formatTokenAmount(amount, decimals) {
|
|
663
|
+
const formatted = formatUnits(amount, decimals);
|
|
664
|
+
return Number(formatted).toFixed(4);
|
|
665
|
+
}
|
|
666
|
+
};
|
|
667
|
+
var CoreWalletClient = class {
|
|
668
|
+
chainType = "core";
|
|
669
|
+
address;
|
|
670
|
+
chainId;
|
|
671
|
+
walletClient;
|
|
672
|
+
publicClient;
|
|
673
|
+
account;
|
|
674
|
+
chain;
|
|
675
|
+
constructor(config) {
|
|
676
|
+
this.chainId = config.chainId;
|
|
677
|
+
this.chain = defineChain2({
|
|
678
|
+
id: config.chainId,
|
|
679
|
+
name: `ConfluxCore-${config.chainId}`,
|
|
680
|
+
nativeCurrency: {
|
|
681
|
+
decimals: 18,
|
|
682
|
+
name: "Conflux",
|
|
683
|
+
symbol: "CFX"
|
|
684
|
+
},
|
|
685
|
+
rpcUrls: {
|
|
686
|
+
default: {
|
|
687
|
+
http: [config.rpcUrl],
|
|
688
|
+
webSocket: config.wsUrl ? [config.wsUrl] : void 0
|
|
689
|
+
}
|
|
690
|
+
}
|
|
691
|
+
});
|
|
692
|
+
this.account = privateKeyToAccount(config.privateKey, {
|
|
693
|
+
networkId: config.chainId
|
|
694
|
+
});
|
|
695
|
+
this.address = this.account.address;
|
|
696
|
+
this.walletClient = createWalletClient({
|
|
697
|
+
account: this.account,
|
|
698
|
+
chain: this.chain,
|
|
699
|
+
transport: http(config.rpcUrl),
|
|
700
|
+
pollingInterval: config.pollingInterval || 1e3
|
|
701
|
+
});
|
|
702
|
+
this.publicClient = createPublicClient({
|
|
703
|
+
chain: this.chain,
|
|
704
|
+
transport: http(config.rpcUrl),
|
|
705
|
+
pollingInterval: config.pollingInterval || 1e3
|
|
706
|
+
});
|
|
707
|
+
}
|
|
708
|
+
async sendTransaction(tx) {
|
|
709
|
+
try {
|
|
710
|
+
return await this.walletClient.sendTransaction({
|
|
711
|
+
to: tx.to,
|
|
712
|
+
value: tx.value,
|
|
713
|
+
data: tx.data,
|
|
714
|
+
gas: tx.gasLimit,
|
|
715
|
+
gasPrice: tx.gasPrice,
|
|
716
|
+
nonce: tx.nonce,
|
|
717
|
+
account: this.account,
|
|
718
|
+
chain: this.chain
|
|
719
|
+
});
|
|
720
|
+
} catch (error) {
|
|
721
|
+
throw new NodeError(
|
|
722
|
+
`Failed to send transaction: ${error instanceof Error ? error.message : String(error)}`,
|
|
723
|
+
"TRANSACTION_SEND_ERROR",
|
|
724
|
+
"core",
|
|
725
|
+
{ transaction: tx, originalError: error }
|
|
726
|
+
);
|
|
727
|
+
}
|
|
728
|
+
}
|
|
729
|
+
async signMessage(message) {
|
|
730
|
+
try {
|
|
731
|
+
return await this.walletClient.signMessage({
|
|
732
|
+
account: this.account,
|
|
733
|
+
message
|
|
734
|
+
});
|
|
735
|
+
} catch (error) {
|
|
736
|
+
throw new NodeError(
|
|
737
|
+
`Failed to sign message: ${error instanceof Error ? error.message : String(error)}`,
|
|
738
|
+
"MESSAGE_SIGN_ERROR",
|
|
739
|
+
"core",
|
|
740
|
+
{ message, originalError: error }
|
|
741
|
+
);
|
|
742
|
+
}
|
|
743
|
+
}
|
|
744
|
+
getInternalClient() {
|
|
745
|
+
return this.walletClient;
|
|
746
|
+
}
|
|
747
|
+
async waitForTransaction(hash) {
|
|
748
|
+
try {
|
|
749
|
+
const receipt = await this.publicClient.waitForTransactionReceipt({
|
|
750
|
+
hash,
|
|
751
|
+
timeout: 5e3
|
|
752
|
+
// 5 second timeout for faster response
|
|
753
|
+
});
|
|
754
|
+
return {
|
|
755
|
+
hash: receipt.transactionHash,
|
|
756
|
+
blockNumber: BigInt(receipt.epochNumber?.toString() || "0"),
|
|
757
|
+
blockHash: receipt.blockHash || "",
|
|
758
|
+
transactionIndex: Number(receipt.index || 0),
|
|
759
|
+
status: receipt.outcomeStatus === "success" ? "success" : "reverted",
|
|
760
|
+
gasUsed: receipt.gasUsed || 0n,
|
|
761
|
+
contractAddress: receipt.contractCreated || void 0,
|
|
762
|
+
logs: receipt.log?.map((log) => ({
|
|
763
|
+
address: log.address || "",
|
|
764
|
+
topics: log.topics || [],
|
|
765
|
+
data: log.data || "0x",
|
|
766
|
+
blockNumber: BigInt(log.epochNumber?.toString() || "0"),
|
|
767
|
+
transactionHash: log.transactionHash || "",
|
|
768
|
+
logIndex: Number(log.logIndex || 0)
|
|
769
|
+
})) || []
|
|
770
|
+
};
|
|
771
|
+
} catch (error) {
|
|
772
|
+
throw new NodeError(
|
|
773
|
+
`Failed to wait for transaction: ${error instanceof Error ? error.message : String(error)}`,
|
|
774
|
+
"TRANSACTION_WAIT_ERROR",
|
|
775
|
+
"core",
|
|
776
|
+
{ hash, originalError: error }
|
|
777
|
+
);
|
|
778
|
+
}
|
|
779
|
+
}
|
|
780
|
+
/**
|
|
781
|
+
* Unified faucet functionality
|
|
782
|
+
* Automatically detects address type and sends CFX accordingly:
|
|
783
|
+
* - Core address: Direct transfer
|
|
784
|
+
* - eSpace address: Cross-chain transfer via internal contract
|
|
785
|
+
*/
|
|
786
|
+
async faucet(address, amount) {
|
|
787
|
+
const isCoreAddr = isCoreAddress(address);
|
|
788
|
+
const isEspaceAddr = isEspaceAddress(address);
|
|
789
|
+
if (!isCoreAddr && !isEspaceAddr) {
|
|
790
|
+
throw new NodeError(
|
|
791
|
+
"Invalid address format (must be Core or eSpace address)",
|
|
792
|
+
"INVALID_ADDRESS",
|
|
793
|
+
"core",
|
|
794
|
+
{ address }
|
|
795
|
+
);
|
|
796
|
+
}
|
|
797
|
+
try {
|
|
798
|
+
if (isCoreAddr) {
|
|
799
|
+
return await this.walletClient.sendTransaction({
|
|
800
|
+
chain: this.chain,
|
|
801
|
+
account: this.account,
|
|
802
|
+
to: address,
|
|
803
|
+
value: parseCFX(amount)
|
|
804
|
+
});
|
|
805
|
+
} else {
|
|
806
|
+
return await this.walletClient.sendTransaction({
|
|
807
|
+
chain: this.chain,
|
|
808
|
+
account: this.account,
|
|
809
|
+
to: hexAddressToBase32({
|
|
810
|
+
hexAddress: "0x0888000000000000000000000000000000000006",
|
|
811
|
+
networkId: this.chain.id
|
|
812
|
+
}),
|
|
813
|
+
value: parseCFX(amount),
|
|
814
|
+
data: encodeFunctionData({
|
|
815
|
+
abi: [
|
|
816
|
+
{
|
|
817
|
+
type: "function",
|
|
818
|
+
name: "transferEVM",
|
|
819
|
+
inputs: [{ name: "to", type: "bytes20" }],
|
|
820
|
+
outputs: [{ name: "output", type: "bytes" }],
|
|
821
|
+
stateMutability: "payable"
|
|
822
|
+
}
|
|
823
|
+
],
|
|
824
|
+
functionName: "transferEVM",
|
|
825
|
+
args: [address]
|
|
826
|
+
})
|
|
827
|
+
});
|
|
828
|
+
}
|
|
829
|
+
} catch (error) {
|
|
830
|
+
throw new NodeError(
|
|
831
|
+
`Failed to send faucet transaction: ${error instanceof Error ? error.message : String(error)}`,
|
|
832
|
+
"FAUCET_ERROR",
|
|
833
|
+
"core",
|
|
834
|
+
{ address, amount, originalError: error }
|
|
835
|
+
);
|
|
836
|
+
}
|
|
837
|
+
}
|
|
838
|
+
/**
|
|
839
|
+
* Cross-chain faucet functionality (Core → eSpace)
|
|
840
|
+
* Sends CFX from Core space to eSpace address via internal contract
|
|
841
|
+
* @deprecated Use faucet() instead which auto-detects address type
|
|
842
|
+
*/
|
|
843
|
+
async faucetToEspace(espaceAddress, amount) {
|
|
844
|
+
if (!isEspaceAddress(espaceAddress)) {
|
|
845
|
+
throw new NodeError(
|
|
846
|
+
"Invalid eSpace address format",
|
|
847
|
+
"INVALID_ADDRESS",
|
|
848
|
+
"core",
|
|
849
|
+
{ espaceAddress }
|
|
850
|
+
);
|
|
851
|
+
}
|
|
852
|
+
try {
|
|
853
|
+
return await this.walletClient.sendTransaction({
|
|
854
|
+
chain: this.chain,
|
|
855
|
+
account: this.account,
|
|
856
|
+
to: hexAddressToBase32({
|
|
857
|
+
hexAddress: "0x0888000000000000000000000000000000000006",
|
|
858
|
+
networkId: this.chain.id
|
|
859
|
+
}),
|
|
860
|
+
value: parseCFX(amount),
|
|
861
|
+
data: encodeFunctionData({
|
|
862
|
+
abi: [
|
|
863
|
+
{
|
|
864
|
+
type: "function",
|
|
865
|
+
name: "transferEVM",
|
|
866
|
+
inputs: [{ name: "to", type: "bytes20" }],
|
|
867
|
+
outputs: [{ name: "output", type: "bytes" }],
|
|
868
|
+
stateMutability: "payable"
|
|
869
|
+
}
|
|
870
|
+
],
|
|
871
|
+
functionName: "transferEVM",
|
|
872
|
+
args: [espaceAddress]
|
|
873
|
+
})
|
|
874
|
+
});
|
|
875
|
+
} catch (error) {
|
|
876
|
+
throw new NodeError(
|
|
877
|
+
`Failed to send faucet transaction to eSpace: ${error instanceof Error ? error.message : String(error)}`,
|
|
878
|
+
"FAUCET_ERROR",
|
|
879
|
+
"core",
|
|
880
|
+
{ espaceAddress, amount, originalError: error }
|
|
881
|
+
);
|
|
882
|
+
}
|
|
883
|
+
}
|
|
884
|
+
/**
|
|
885
|
+
* Deploy a contract to Core Space
|
|
886
|
+
*/
|
|
887
|
+
async deployContract(abi, bytecode, constructorArgs = []) {
|
|
888
|
+
try {
|
|
889
|
+
const hash = await this.walletClient.deployContract({
|
|
890
|
+
account: this.account,
|
|
891
|
+
chain: this.chain,
|
|
892
|
+
abi,
|
|
893
|
+
bytecode,
|
|
894
|
+
args: constructorArgs
|
|
895
|
+
});
|
|
896
|
+
const receipt = await this.waitForTransaction(hash);
|
|
897
|
+
if (!receipt.contractAddress) {
|
|
898
|
+
throw new Error("Contract address not found in transaction receipt");
|
|
899
|
+
}
|
|
900
|
+
return receipt.contractAddress;
|
|
901
|
+
} catch (error) {
|
|
902
|
+
throw new NodeError(
|
|
903
|
+
`Failed to deploy contract: ${error instanceof Error ? error.message : String(error)}`,
|
|
904
|
+
"DEPLOYMENT_ERROR",
|
|
905
|
+
"core",
|
|
906
|
+
{ abi, bytecode, constructorArgs, originalError: error }
|
|
907
|
+
);
|
|
908
|
+
}
|
|
909
|
+
}
|
|
910
|
+
/**
|
|
911
|
+
* Call a contract method (read-only)
|
|
912
|
+
*/
|
|
913
|
+
async callContract(address, abi, functionName, args = []) {
|
|
914
|
+
try {
|
|
915
|
+
const result = await this.publicClient.readContract({
|
|
916
|
+
address,
|
|
917
|
+
abi,
|
|
918
|
+
functionName,
|
|
919
|
+
args
|
|
920
|
+
});
|
|
921
|
+
return result;
|
|
922
|
+
} catch (error) {
|
|
923
|
+
throw new NodeError(
|
|
924
|
+
`Failed to call contract: ${error instanceof Error ? error.message : String(error)}`,
|
|
925
|
+
"CONTRACT_CALL_ERROR",
|
|
926
|
+
"core",
|
|
927
|
+
{ address, functionName, args, originalError: error }
|
|
928
|
+
);
|
|
929
|
+
}
|
|
930
|
+
}
|
|
931
|
+
/**
|
|
932
|
+
* Write to a contract (transaction)
|
|
933
|
+
*/
|
|
934
|
+
async writeContract(address, abi, functionName, args = [], value) {
|
|
935
|
+
try {
|
|
936
|
+
const hash = await this.walletClient.writeContract({
|
|
937
|
+
account: this.account,
|
|
938
|
+
chain: this.chain,
|
|
939
|
+
address,
|
|
940
|
+
abi,
|
|
941
|
+
functionName,
|
|
942
|
+
args,
|
|
943
|
+
value
|
|
944
|
+
});
|
|
945
|
+
return hash;
|
|
946
|
+
} catch (error) {
|
|
947
|
+
throw new NodeError(
|
|
948
|
+
`Failed to write to contract: ${error instanceof Error ? error.message : String(error)}`,
|
|
949
|
+
"CONTRACT_WRITE_ERROR",
|
|
950
|
+
"core",
|
|
951
|
+
{ address, functionName, args, value, originalError: error }
|
|
952
|
+
);
|
|
953
|
+
}
|
|
954
|
+
}
|
|
955
|
+
};
|
|
956
|
+
var CoreTestClient = class extends CoreClient {
|
|
957
|
+
testClient;
|
|
958
|
+
constructor(config) {
|
|
959
|
+
super(config);
|
|
960
|
+
this.testClient = createTestClient({
|
|
961
|
+
chain: this.chainId === 1029 ? conflux : confluxTestnet,
|
|
962
|
+
transport: http(config.rpcUrl),
|
|
963
|
+
pollingInterval: config.pollingInterval || 1e3
|
|
964
|
+
});
|
|
965
|
+
}
|
|
966
|
+
async mine(blocks = 1) {
|
|
967
|
+
try {
|
|
968
|
+
await this.testClient.mine({ blocks });
|
|
969
|
+
} catch (error) {
|
|
970
|
+
throw new NodeError(
|
|
971
|
+
`Failed to mine blocks: ${error instanceof Error ? error.message : String(error)}`,
|
|
972
|
+
"MINE_ERROR",
|
|
973
|
+
"core",
|
|
974
|
+
{ blocks, originalError: error }
|
|
975
|
+
);
|
|
976
|
+
}
|
|
977
|
+
}
|
|
978
|
+
async setNextBlockTimestamp(_timestamp) {
|
|
979
|
+
throw new NodeError(
|
|
980
|
+
"setNextBlockTimestamp not implemented for Core client",
|
|
981
|
+
"NOT_IMPLEMENTED",
|
|
982
|
+
"core"
|
|
983
|
+
);
|
|
984
|
+
}
|
|
985
|
+
async increaseTime(_seconds) {
|
|
986
|
+
throw new NodeError(
|
|
987
|
+
"increaseTime not implemented for Core client",
|
|
988
|
+
"NOT_IMPLEMENTED",
|
|
989
|
+
"core"
|
|
990
|
+
);
|
|
991
|
+
}
|
|
992
|
+
async impersonateAccount(_address) {
|
|
993
|
+
throw new NodeError(
|
|
994
|
+
"impersonateAccount not implemented for Core client",
|
|
995
|
+
"NOT_IMPLEMENTED",
|
|
996
|
+
"core"
|
|
997
|
+
);
|
|
998
|
+
}
|
|
999
|
+
async stopImpersonatingAccount(_address) {
|
|
1000
|
+
throw new NodeError(
|
|
1001
|
+
"stopImpersonatingAccount not implemented for Core client",
|
|
1002
|
+
"NOT_IMPLEMENTED",
|
|
1003
|
+
"core"
|
|
1004
|
+
);
|
|
1005
|
+
}
|
|
1006
|
+
async setBalance(_address, _balance) {
|
|
1007
|
+
throw new NodeError(
|
|
1008
|
+
"setBalance not implemented for Core client",
|
|
1009
|
+
"NOT_IMPLEMENTED",
|
|
1010
|
+
"core"
|
|
1011
|
+
);
|
|
1012
|
+
}
|
|
1013
|
+
async getStorageAt(_address, _slot) {
|
|
1014
|
+
throw new NodeError(
|
|
1015
|
+
"getStorageAt not implemented for Core client",
|
|
1016
|
+
"NOT_IMPLEMENTED",
|
|
1017
|
+
"core"
|
|
1018
|
+
);
|
|
1019
|
+
}
|
|
1020
|
+
async setStorageAt(_address, _slot, _value) {
|
|
1021
|
+
throw new NodeError(
|
|
1022
|
+
"setStorageAt not implemented for Core client",
|
|
1023
|
+
"NOT_IMPLEMENTED",
|
|
1024
|
+
"core"
|
|
1025
|
+
);
|
|
1026
|
+
}
|
|
1027
|
+
getInternalTestClient() {
|
|
1028
|
+
return this.testClient;
|
|
1029
|
+
}
|
|
1030
|
+
};
|
|
1031
|
+
async function createCoreClient(config) {
|
|
1032
|
+
const chainConfig = getChainConfig(config.chainId);
|
|
1033
|
+
if (chainConfig.type !== "core") {
|
|
1034
|
+
throw new NodeError(
|
|
1035
|
+
`Invalid chain type for Core client: ${chainConfig.type}`,
|
|
1036
|
+
"INVALID_CHAIN_TYPE",
|
|
1037
|
+
"core"
|
|
1038
|
+
);
|
|
1039
|
+
}
|
|
1040
|
+
const clientConfig = {
|
|
1041
|
+
...config,
|
|
1042
|
+
rpcUrl: config.rpcUrl || chainConfig.rpcUrls.default.http[0] || "http://localhost:12537",
|
|
1043
|
+
wsUrl: config.wsUrl || chainConfig.rpcUrls.default.webSocket?.[0]
|
|
1044
|
+
};
|
|
1045
|
+
const publicClient = new CoreClient(clientConfig);
|
|
1046
|
+
let walletClient;
|
|
1047
|
+
let testClient;
|
|
1048
|
+
if (config.account) {
|
|
1049
|
+
let privateKey;
|
|
1050
|
+
if (typeof config.account === "string") {
|
|
1051
|
+
privateKey = config.account;
|
|
1052
|
+
} else {
|
|
1053
|
+
privateKey = config.account.privateKey;
|
|
1054
|
+
}
|
|
1055
|
+
const walletConfig = {
|
|
1056
|
+
...clientConfig,
|
|
1057
|
+
privateKey,
|
|
1058
|
+
accountIndex: typeof config.account === "object" ? config.account.accountIndex : 0
|
|
1059
|
+
};
|
|
1060
|
+
walletClient = new CoreWalletClient(walletConfig);
|
|
1061
|
+
}
|
|
1062
|
+
if (config.testMode) {
|
|
1063
|
+
const testConfig = {
|
|
1064
|
+
...clientConfig,
|
|
1065
|
+
enableTestMode: true
|
|
1066
|
+
};
|
|
1067
|
+
testClient = new CoreTestClient(testConfig);
|
|
1068
|
+
}
|
|
1069
|
+
return {
|
|
1070
|
+
publicClient,
|
|
1071
|
+
walletClient,
|
|
1072
|
+
testClient
|
|
1073
|
+
};
|
|
1074
|
+
}
|
|
1075
|
+
|
|
1076
|
+
// src/clients/evm.ts
|
|
1077
|
+
import {
|
|
1078
|
+
createPublicClient as createPublicClient2,
|
|
1079
|
+
createTestClient as createTestClient2,
|
|
1080
|
+
createWalletClient as createWalletClient2,
|
|
1081
|
+
defineChain as defineChain3,
|
|
1082
|
+
encodeFunctionData as encodeFunctionData2,
|
|
1083
|
+
formatEther,
|
|
1084
|
+
http as http2,
|
|
1085
|
+
isAddress as isEvmAddress,
|
|
1086
|
+
parseEther
|
|
1087
|
+
} from "viem";
|
|
1088
|
+
import { privateKeyToAccount as privateKeyToAccount2 } from "viem/accounts";
|
|
1089
|
+
var espaceMainnet = defineChain3({
|
|
1090
|
+
id: 1030,
|
|
1091
|
+
name: "Conflux eSpace",
|
|
1092
|
+
nativeCurrency: { name: "Conflux", symbol: "CFX", decimals: 18 },
|
|
1093
|
+
rpcUrls: { default: { http: ["https://evm.confluxrpc.com"] } },
|
|
1094
|
+
blockExplorers: {
|
|
1095
|
+
default: { name: "ConfluxScan", url: "https://evm.confluxscan.net" }
|
|
1096
|
+
}
|
|
1097
|
+
});
|
|
1098
|
+
var espaceTestnet = defineChain3({
|
|
1099
|
+
id: 71,
|
|
1100
|
+
name: "Conflux eSpace Testnet",
|
|
1101
|
+
nativeCurrency: { name: "Conflux", symbol: "CFX", decimals: 18 },
|
|
1102
|
+
rpcUrls: { default: { http: ["https://evmtestnet.confluxrpc.com"] } },
|
|
1103
|
+
blockExplorers: {
|
|
1104
|
+
default: { name: "ConfluxScan", url: "https://evmtestnet.confluxscan.net" }
|
|
1105
|
+
}
|
|
1106
|
+
});
|
|
1107
|
+
var EspaceClient = class {
|
|
1108
|
+
chainId;
|
|
1109
|
+
chainType = "evm";
|
|
1110
|
+
publicClient;
|
|
1111
|
+
chain;
|
|
1112
|
+
address;
|
|
1113
|
+
constructor(config) {
|
|
1114
|
+
this.chainId = config.chainId;
|
|
1115
|
+
if (config.chainId === 1030) {
|
|
1116
|
+
this.chain = espaceMainnet;
|
|
1117
|
+
} else if (config.chainId === 71) {
|
|
1118
|
+
this.chain = espaceTestnet;
|
|
1119
|
+
} else {
|
|
1120
|
+
this.chain = defineChain3({
|
|
1121
|
+
id: config.chainId,
|
|
1122
|
+
name: `Conflux eSpace (${config.chainId})`,
|
|
1123
|
+
nativeCurrency: { name: "Conflux", symbol: "CFX", decimals: 18 },
|
|
1124
|
+
rpcUrls: { default: { http: [config.rpcUrl] } }
|
|
1125
|
+
});
|
|
1126
|
+
}
|
|
1127
|
+
this.publicClient = createPublicClient2({
|
|
1128
|
+
chain: this.chain,
|
|
1129
|
+
transport: http2(config.rpcUrl),
|
|
1130
|
+
pollingInterval: config.pollingInterval || 1e3
|
|
1131
|
+
});
|
|
1132
|
+
this.address = "";
|
|
1133
|
+
}
|
|
1134
|
+
async getBlockNumber() {
|
|
1135
|
+
try {
|
|
1136
|
+
const blockNumber = await this.publicClient.getBlockNumber();
|
|
1137
|
+
return blockNumber;
|
|
1138
|
+
} catch (error) {
|
|
1139
|
+
throw new NodeError(
|
|
1140
|
+
`Failed to get block number: ${error instanceof Error ? error.message : String(error)}`,
|
|
1141
|
+
"BLOCK_NUMBER_ERROR",
|
|
1142
|
+
"evm",
|
|
1143
|
+
{ originalError: error }
|
|
1144
|
+
);
|
|
1145
|
+
}
|
|
1146
|
+
}
|
|
1147
|
+
async getBalance(address) {
|
|
1148
|
+
if (!isEvmAddress(address)) {
|
|
1149
|
+
throw new NodeError(
|
|
1150
|
+
"Invalid EVM address format",
|
|
1151
|
+
"INVALID_ADDRESS",
|
|
1152
|
+
"evm"
|
|
1153
|
+
);
|
|
1154
|
+
}
|
|
1155
|
+
try {
|
|
1156
|
+
const balance = await this.publicClient.getBalance({ address });
|
|
1157
|
+
return formatEther(balance);
|
|
1158
|
+
} catch (error) {
|
|
1159
|
+
throw new NodeError(
|
|
1160
|
+
`Failed to get balance: ${error instanceof Error ? error.message : String(error)}`,
|
|
1161
|
+
"BALANCE_ERROR",
|
|
1162
|
+
"evm",
|
|
1163
|
+
{ address, originalError: error }
|
|
1164
|
+
);
|
|
1165
|
+
}
|
|
1166
|
+
}
|
|
1167
|
+
async estimateGas(tx) {
|
|
1168
|
+
try {
|
|
1169
|
+
const gas = await this.publicClient.estimateGas({
|
|
1170
|
+
to: tx.to,
|
|
1171
|
+
value: tx.value,
|
|
1172
|
+
data: tx.data
|
|
1173
|
+
});
|
|
1174
|
+
return gas;
|
|
1175
|
+
} catch (error) {
|
|
1176
|
+
throw new NodeError(
|
|
1177
|
+
`Failed to estimate gas: ${error instanceof Error ? error.message : String(error)}`,
|
|
1178
|
+
"GAS_ESTIMATE_ERROR",
|
|
1179
|
+
"evm",
|
|
1180
|
+
{ transaction: tx, originalError: error }
|
|
1181
|
+
);
|
|
1182
|
+
}
|
|
1183
|
+
}
|
|
1184
|
+
async waitForTransaction(hash) {
|
|
1185
|
+
try {
|
|
1186
|
+
const receipt = await this.publicClient.waitForTransactionReceipt({
|
|
1187
|
+
hash,
|
|
1188
|
+
timeout: 5e3
|
|
1189
|
+
// 5 second timeout for faster response
|
|
1190
|
+
});
|
|
1191
|
+
return {
|
|
1192
|
+
hash: receipt.transactionHash,
|
|
1193
|
+
blockNumber: receipt.blockNumber,
|
|
1194
|
+
blockHash: receipt.blockHash,
|
|
1195
|
+
transactionIndex: receipt.transactionIndex,
|
|
1196
|
+
status: receipt.status === "success" ? "success" : "reverted",
|
|
1197
|
+
gasUsed: receipt.gasUsed,
|
|
1198
|
+
contractAddress: receipt.contractAddress || void 0,
|
|
1199
|
+
logs: receipt.logs.map((log) => ({
|
|
1200
|
+
address: log.address,
|
|
1201
|
+
topics: log.topics,
|
|
1202
|
+
data: log.data,
|
|
1203
|
+
blockNumber: log.blockNumber || 0n,
|
|
1204
|
+
transactionHash: log.transactionHash || "",
|
|
1205
|
+
logIndex: log.logIndex || 0
|
|
1206
|
+
}))
|
|
1207
|
+
};
|
|
1208
|
+
} catch (error) {
|
|
1209
|
+
throw new NodeError(
|
|
1210
|
+
`Failed to wait for transaction: ${error instanceof Error ? error.message : String(error)}`,
|
|
1211
|
+
"TRANSACTION_WAIT_ERROR",
|
|
1212
|
+
"evm",
|
|
1213
|
+
{ hash, originalError: error }
|
|
1214
|
+
);
|
|
1215
|
+
}
|
|
1216
|
+
}
|
|
1217
|
+
async getGasPrice() {
|
|
1218
|
+
try {
|
|
1219
|
+
const gasPrice = await this.publicClient.getGasPrice();
|
|
1220
|
+
return gasPrice;
|
|
1221
|
+
} catch (error) {
|
|
1222
|
+
throw new NodeError(
|
|
1223
|
+
`Failed to get gas price: ${error instanceof Error ? error.message : String(error)}`,
|
|
1224
|
+
"GAS_PRICE_ERROR",
|
|
1225
|
+
"evm",
|
|
1226
|
+
{ originalError: error }
|
|
1227
|
+
);
|
|
1228
|
+
}
|
|
1229
|
+
}
|
|
1230
|
+
/**
|
|
1231
|
+
* Get the current chain ID from the network
|
|
1232
|
+
*/
|
|
1233
|
+
async getChainId() {
|
|
1234
|
+
try {
|
|
1235
|
+
const chainId = await this.publicClient.getChainId();
|
|
1236
|
+
return chainId;
|
|
1237
|
+
} catch (error) {
|
|
1238
|
+
throw new NodeError(
|
|
1239
|
+
`Failed to get chain ID: ${error instanceof Error ? error.message : String(error)}`,
|
|
1240
|
+
"CHAIN_ID_ERROR",
|
|
1241
|
+
"evm",
|
|
1242
|
+
{ originalError: error }
|
|
1243
|
+
);
|
|
1244
|
+
}
|
|
1245
|
+
}
|
|
1246
|
+
/**
|
|
1247
|
+
* Check if the client is connected to the network
|
|
1248
|
+
*/
|
|
1249
|
+
async isConnected() {
|
|
1250
|
+
try {
|
|
1251
|
+
await this.publicClient.getBlockNumber();
|
|
1252
|
+
return true;
|
|
1253
|
+
} catch {
|
|
1254
|
+
return false;
|
|
1255
|
+
}
|
|
1256
|
+
}
|
|
1257
|
+
// Base implementation - should be overridden by WalletClient
|
|
1258
|
+
async sendTransaction(_tx) {
|
|
1259
|
+
throw new NodeError(
|
|
1260
|
+
"sendTransaction not available on public client",
|
|
1261
|
+
"METHOD_NOT_AVAILABLE",
|
|
1262
|
+
"evm"
|
|
1263
|
+
);
|
|
1264
|
+
}
|
|
1265
|
+
async getTokenBalance(_address, _tokenAddress) {
|
|
1266
|
+
try {
|
|
1267
|
+
const balance = await this.publicClient.readContract({
|
|
1268
|
+
address: _tokenAddress,
|
|
1269
|
+
abi: [
|
|
1270
|
+
{
|
|
1271
|
+
type: "function",
|
|
1272
|
+
name: "balanceOf",
|
|
1273
|
+
inputs: [{ name: "account", type: "address" }],
|
|
1274
|
+
outputs: [{ name: "balance", type: "uint256" }],
|
|
1275
|
+
stateMutability: "view"
|
|
1276
|
+
}
|
|
1277
|
+
],
|
|
1278
|
+
functionName: "balanceOf",
|
|
1279
|
+
args: [_address]
|
|
1280
|
+
});
|
|
1281
|
+
return balance.toString();
|
|
1282
|
+
} catch (error) {
|
|
1283
|
+
throw new NodeError(
|
|
1284
|
+
`Failed to get token balance: ${error instanceof Error ? error.message : String(error)}`,
|
|
1285
|
+
"TOKEN_BALANCE_ERROR",
|
|
1286
|
+
"evm",
|
|
1287
|
+
{ address: _address, tokenAddress: _tokenAddress, originalError: error }
|
|
1288
|
+
);
|
|
1289
|
+
}
|
|
1290
|
+
}
|
|
1291
|
+
watchBlocks(callback) {
|
|
1292
|
+
const unwatch = this.publicClient.watchBlocks({
|
|
1293
|
+
onBlock: (block) => callback({
|
|
1294
|
+
chainType: "evm",
|
|
1295
|
+
blockNumber: block.number || 0n,
|
|
1296
|
+
blockHash: block.hash || "",
|
|
1297
|
+
timestamp: Number(block.timestamp || 0),
|
|
1298
|
+
transactionCount: block.transactions?.length || 0
|
|
1299
|
+
})
|
|
1300
|
+
});
|
|
1301
|
+
return unwatch;
|
|
1302
|
+
}
|
|
1303
|
+
async watchTransaction(_hash, _callback) {
|
|
1304
|
+
const pollTransaction = async () => {
|
|
1305
|
+
try {
|
|
1306
|
+
const receipt = await this.waitForTransaction(_hash);
|
|
1307
|
+
_callback(receipt);
|
|
1308
|
+
} catch {
|
|
1309
|
+
setTimeout(pollTransaction, 1e3);
|
|
1310
|
+
}
|
|
1311
|
+
};
|
|
1312
|
+
setTimeout(pollTransaction, 1e3);
|
|
1313
|
+
return () => {
|
|
1314
|
+
};
|
|
1315
|
+
}
|
|
1316
|
+
getInternalClient() {
|
|
1317
|
+
return this.publicClient;
|
|
1318
|
+
}
|
|
1319
|
+
// Base implementation - should be overridden by TestClient
|
|
1320
|
+
watchTransactions(_callback) {
|
|
1321
|
+
throw new NodeError(
|
|
1322
|
+
"watchTransactions not available on public client",
|
|
1323
|
+
"METHOD_NOT_AVAILABLE",
|
|
1324
|
+
"evm"
|
|
1325
|
+
);
|
|
1326
|
+
}
|
|
1327
|
+
isValidAddress(address) {
|
|
1328
|
+
return isEvmAddress(address);
|
|
1329
|
+
}
|
|
1330
|
+
formatAmount(amount) {
|
|
1331
|
+
return formatEther(amount);
|
|
1332
|
+
}
|
|
1333
|
+
parseAmount(amount) {
|
|
1334
|
+
return parseEther(amount);
|
|
1335
|
+
}
|
|
1336
|
+
};
|
|
1337
|
+
var EspaceWalletClient = class extends EspaceClient {
|
|
1338
|
+
walletClient;
|
|
1339
|
+
account;
|
|
1340
|
+
constructor(config) {
|
|
1341
|
+
super(config);
|
|
1342
|
+
this.account = privateKeyToAccount2(config.privateKey);
|
|
1343
|
+
this.address = this.account.address;
|
|
1344
|
+
this.walletClient = createWalletClient2({
|
|
1345
|
+
account: this.account,
|
|
1346
|
+
chain: this.chain,
|
|
1347
|
+
transport: http2(config.rpcUrl)
|
|
1348
|
+
});
|
|
1349
|
+
}
|
|
1350
|
+
getAddress() {
|
|
1351
|
+
return this.address;
|
|
1352
|
+
}
|
|
1353
|
+
async sendTransaction(tx) {
|
|
1354
|
+
try {
|
|
1355
|
+
const hash = await this.walletClient.sendTransaction({
|
|
1356
|
+
account: this.account,
|
|
1357
|
+
chain: this.chain,
|
|
1358
|
+
to: tx.to,
|
|
1359
|
+
value: tx.value,
|
|
1360
|
+
data: tx.data,
|
|
1361
|
+
gas: tx.gasLimit,
|
|
1362
|
+
gasPrice: tx.gasPrice,
|
|
1363
|
+
nonce: tx.nonce
|
|
1364
|
+
});
|
|
1365
|
+
return hash;
|
|
1366
|
+
} catch (error) {
|
|
1367
|
+
throw new NodeError(
|
|
1368
|
+
`Failed to send transaction: ${error instanceof Error ? error.message : String(error)}`,
|
|
1369
|
+
"TRANSACTION_ERROR",
|
|
1370
|
+
"evm",
|
|
1371
|
+
{ transaction: tx, originalError: error }
|
|
1372
|
+
);
|
|
1373
|
+
}
|
|
1374
|
+
}
|
|
1375
|
+
async signMessage(message) {
|
|
1376
|
+
try {
|
|
1377
|
+
const signature = await this.walletClient.signMessage({
|
|
1378
|
+
account: this.account,
|
|
1379
|
+
message
|
|
1380
|
+
});
|
|
1381
|
+
return signature;
|
|
1382
|
+
} catch (error) {
|
|
1383
|
+
throw new NodeError(
|
|
1384
|
+
`Failed to sign message: ${error instanceof Error ? error.message : String(error)}`,
|
|
1385
|
+
"SIGNING_ERROR",
|
|
1386
|
+
"evm",
|
|
1387
|
+
{ message, originalError: error }
|
|
1388
|
+
);
|
|
1389
|
+
}
|
|
1390
|
+
}
|
|
1391
|
+
async deployContract(abi, bytecode, constructorArgs = []) {
|
|
1392
|
+
try {
|
|
1393
|
+
const hash = await this.walletClient.deployContract({
|
|
1394
|
+
account: this.account,
|
|
1395
|
+
chain: this.chain,
|
|
1396
|
+
abi,
|
|
1397
|
+
bytecode,
|
|
1398
|
+
args: constructorArgs
|
|
1399
|
+
});
|
|
1400
|
+
const receipt = await this.waitForTransaction(hash);
|
|
1401
|
+
if (!receipt.contractAddress) {
|
|
1402
|
+
throw new Error("Contract address not found in transaction receipt");
|
|
1403
|
+
}
|
|
1404
|
+
return receipt.contractAddress;
|
|
1405
|
+
} catch (error) {
|
|
1406
|
+
throw new NodeError(
|
|
1407
|
+
`Failed to deploy contract: ${error instanceof Error ? error.message : String(error)}`,
|
|
1408
|
+
"DEPLOYMENT_ERROR",
|
|
1409
|
+
"evm",
|
|
1410
|
+
{ bytecode, constructorArgs, originalError: error }
|
|
1411
|
+
);
|
|
1412
|
+
}
|
|
1413
|
+
}
|
|
1414
|
+
async callContract(address, abi, functionName, args = []) {
|
|
1415
|
+
try {
|
|
1416
|
+
const result = await this.publicClient.readContract({
|
|
1417
|
+
address,
|
|
1418
|
+
abi,
|
|
1419
|
+
functionName,
|
|
1420
|
+
args
|
|
1421
|
+
});
|
|
1422
|
+
return result;
|
|
1423
|
+
} catch (error) {
|
|
1424
|
+
throw new NodeError(
|
|
1425
|
+
`Failed to call contract: ${error instanceof Error ? error.message : String(error)}`,
|
|
1426
|
+
"CONTRACT_CALL_ERROR",
|
|
1427
|
+
"evm",
|
|
1428
|
+
{ address, functionName, args, originalError: error }
|
|
1429
|
+
);
|
|
1430
|
+
}
|
|
1431
|
+
}
|
|
1432
|
+
async writeContract(address, abi, functionName, args = [], value) {
|
|
1433
|
+
try {
|
|
1434
|
+
const hash = await this.walletClient.writeContract({
|
|
1435
|
+
account: this.account,
|
|
1436
|
+
chain: this.chain,
|
|
1437
|
+
address,
|
|
1438
|
+
abi,
|
|
1439
|
+
functionName,
|
|
1440
|
+
args,
|
|
1441
|
+
value
|
|
1442
|
+
});
|
|
1443
|
+
return hash;
|
|
1444
|
+
} catch (error) {
|
|
1445
|
+
throw new NodeError(
|
|
1446
|
+
`Failed to write contract: ${error instanceof Error ? error.message : String(error)}`,
|
|
1447
|
+
"CONTRACT_WRITE_ERROR",
|
|
1448
|
+
"evm",
|
|
1449
|
+
{ address, functionName, args, value, originalError: error }
|
|
1450
|
+
);
|
|
1451
|
+
}
|
|
1452
|
+
}
|
|
1453
|
+
/**
|
|
1454
|
+
* Transfer CFX from eSpace to Core Space
|
|
1455
|
+
* Uses the built-in withdrawal mechanism
|
|
1456
|
+
*/
|
|
1457
|
+
async faucetToCore(coreAddress, amount) {
|
|
1458
|
+
if (!coreAddress.startsWith("cfx:") || coreAddress.length < 30) {
|
|
1459
|
+
throw new NodeError(
|
|
1460
|
+
"Invalid Core address format",
|
|
1461
|
+
"INVALID_ADDRESS",
|
|
1462
|
+
"evm",
|
|
1463
|
+
{ coreAddress }
|
|
1464
|
+
);
|
|
1465
|
+
}
|
|
1466
|
+
try {
|
|
1467
|
+
const hash = await this.walletClient.sendTransaction({
|
|
1468
|
+
account: this.account,
|
|
1469
|
+
chain: this.chain,
|
|
1470
|
+
to: "0x0888000000000000000000000000000000000006",
|
|
1471
|
+
// CrossSpaceCall precompiled address
|
|
1472
|
+
value: parseEther(amount),
|
|
1473
|
+
data: encodeFunctionData2({
|
|
1474
|
+
abi: [
|
|
1475
|
+
{
|
|
1476
|
+
type: "function",
|
|
1477
|
+
name: "withdrawFromMapped",
|
|
1478
|
+
inputs: [{ name: "value", type: "uint256" }],
|
|
1479
|
+
outputs: [],
|
|
1480
|
+
stateMutability: "payable"
|
|
1481
|
+
}
|
|
1482
|
+
],
|
|
1483
|
+
functionName: "withdrawFromMapped",
|
|
1484
|
+
args: [parseEther(amount)]
|
|
1485
|
+
})
|
|
1486
|
+
});
|
|
1487
|
+
return hash;
|
|
1488
|
+
} catch (error) {
|
|
1489
|
+
throw new NodeError(
|
|
1490
|
+
`Failed to send faucet transaction to Core: ${error instanceof Error ? error.message : String(error)}`,
|
|
1491
|
+
"FAUCET_ERROR",
|
|
1492
|
+
"evm",
|
|
1493
|
+
{ coreAddress, amount, originalError: error }
|
|
1494
|
+
);
|
|
1495
|
+
}
|
|
1496
|
+
}
|
|
1497
|
+
getInternalClient() {
|
|
1498
|
+
return this.walletClient;
|
|
1499
|
+
}
|
|
1500
|
+
};
|
|
1501
|
+
var EspaceTestClient = class extends EspaceWalletClient {
|
|
1502
|
+
testClient;
|
|
1503
|
+
constructor(config) {
|
|
1504
|
+
super(config);
|
|
1505
|
+
this.testClient = createTestClient2({
|
|
1506
|
+
mode: "anvil",
|
|
1507
|
+
chain: this.chainId === 1030 ? espaceMainnet : espaceTestnet,
|
|
1508
|
+
transport: http2(config.rpcUrl),
|
|
1509
|
+
pollingInterval: config.pollingInterval || 1e3
|
|
1510
|
+
});
|
|
1511
|
+
}
|
|
1512
|
+
async mine(blocks = 1) {
|
|
1513
|
+
try {
|
|
1514
|
+
await this.testClient.mine({ blocks });
|
|
1515
|
+
} catch (error) {
|
|
1516
|
+
throw new NodeError(
|
|
1517
|
+
`Failed to mine blocks: ${error instanceof Error ? error.message : String(error)}`,
|
|
1518
|
+
"MINING_ERROR",
|
|
1519
|
+
"evm",
|
|
1520
|
+
{ blocks, originalError: error }
|
|
1521
|
+
);
|
|
1522
|
+
}
|
|
1523
|
+
}
|
|
1524
|
+
async setNextBlockTimestamp(timestamp) {
|
|
1525
|
+
try {
|
|
1526
|
+
await this.testClient.setNextBlockTimestamp({
|
|
1527
|
+
timestamp: BigInt(timestamp)
|
|
1528
|
+
});
|
|
1529
|
+
} catch (error) {
|
|
1530
|
+
throw new NodeError(
|
|
1531
|
+
`Failed to set next block timestamp: ${error instanceof Error ? error.message : String(error)}`,
|
|
1532
|
+
"TIMESTAMP_ERROR",
|
|
1533
|
+
"evm",
|
|
1534
|
+
{ timestamp, originalError: error }
|
|
1535
|
+
);
|
|
1536
|
+
}
|
|
1537
|
+
}
|
|
1538
|
+
async increaseTime(seconds) {
|
|
1539
|
+
try {
|
|
1540
|
+
await this.testClient.increaseTime({ seconds });
|
|
1541
|
+
} catch (error) {
|
|
1542
|
+
throw new NodeError(
|
|
1543
|
+
`Failed to increase time: ${error instanceof Error ? error.message : String(error)}`,
|
|
1544
|
+
"TIME_INCREASE_ERROR",
|
|
1545
|
+
"evm",
|
|
1546
|
+
{ seconds, originalError: error }
|
|
1547
|
+
);
|
|
1548
|
+
}
|
|
1549
|
+
}
|
|
1550
|
+
async impersonateAccount(address) {
|
|
1551
|
+
try {
|
|
1552
|
+
await this.testClient.impersonateAccount({ address });
|
|
1553
|
+
} catch (error) {
|
|
1554
|
+
throw new NodeError(
|
|
1555
|
+
`Failed to impersonate account: ${error instanceof Error ? error.message : String(error)}`,
|
|
1556
|
+
"IMPERSONATION_ERROR",
|
|
1557
|
+
"evm",
|
|
1558
|
+
{ address, originalError: error }
|
|
1559
|
+
);
|
|
1560
|
+
}
|
|
1561
|
+
}
|
|
1562
|
+
async stopImpersonatingAccount(address) {
|
|
1563
|
+
try {
|
|
1564
|
+
await this.testClient.stopImpersonatingAccount({
|
|
1565
|
+
address
|
|
1566
|
+
});
|
|
1567
|
+
} catch (error) {
|
|
1568
|
+
throw new NodeError(
|
|
1569
|
+
`Failed to stop impersonating account: ${error instanceof Error ? error.message : String(error)}`,
|
|
1570
|
+
"IMPERSONATION_STOP_ERROR",
|
|
1571
|
+
"evm",
|
|
1572
|
+
{ address, originalError: error }
|
|
1573
|
+
);
|
|
1574
|
+
}
|
|
1575
|
+
}
|
|
1576
|
+
async setBalance(address, balance) {
|
|
1577
|
+
try {
|
|
1578
|
+
await this.testClient.setBalance({
|
|
1579
|
+
address,
|
|
1580
|
+
value: balance
|
|
1581
|
+
});
|
|
1582
|
+
} catch (error) {
|
|
1583
|
+
throw new NodeError(
|
|
1584
|
+
`Failed to set balance: ${error instanceof Error ? error.message : String(error)}`,
|
|
1585
|
+
"BALANCE_SET_ERROR",
|
|
1586
|
+
"evm",
|
|
1587
|
+
{ address, balance, originalError: error }
|
|
1588
|
+
);
|
|
1589
|
+
}
|
|
1590
|
+
}
|
|
1591
|
+
async snapshot() {
|
|
1592
|
+
try {
|
|
1593
|
+
const snapshotId = await this.testClient.snapshot();
|
|
1594
|
+
return snapshotId;
|
|
1595
|
+
} catch (error) {
|
|
1596
|
+
throw new NodeError(
|
|
1597
|
+
`Failed to create snapshot: ${error instanceof Error ? error.message : String(error)}`,
|
|
1598
|
+
"SNAPSHOT_ERROR",
|
|
1599
|
+
"evm",
|
|
1600
|
+
{ originalError: error }
|
|
1601
|
+
);
|
|
1602
|
+
}
|
|
1603
|
+
}
|
|
1604
|
+
async revert(snapshotId) {
|
|
1605
|
+
try {
|
|
1606
|
+
await this.testClient.revert({ id: snapshotId });
|
|
1607
|
+
} catch (error) {
|
|
1608
|
+
throw new NodeError(
|
|
1609
|
+
`Failed to revert to snapshot: ${error instanceof Error ? error.message : String(error)}`,
|
|
1610
|
+
"REVERT_ERROR",
|
|
1611
|
+
"evm",
|
|
1612
|
+
{ snapshotId, originalError: error }
|
|
1613
|
+
);
|
|
1614
|
+
}
|
|
1615
|
+
}
|
|
1616
|
+
async getStorageAt(address, slot) {
|
|
1617
|
+
try {
|
|
1618
|
+
const value = await this.publicClient.getStorageAt({
|
|
1619
|
+
address,
|
|
1620
|
+
slot
|
|
1621
|
+
});
|
|
1622
|
+
return value || "0x";
|
|
1623
|
+
} catch (error) {
|
|
1624
|
+
throw new NodeError(
|
|
1625
|
+
`Failed to get storage: ${error instanceof Error ? error.message : String(error)}`,
|
|
1626
|
+
"STORAGE_GET_ERROR",
|
|
1627
|
+
"evm",
|
|
1628
|
+
{ address, slot, originalError: error }
|
|
1629
|
+
);
|
|
1630
|
+
}
|
|
1631
|
+
}
|
|
1632
|
+
async setStorageAt(address, slot, value) {
|
|
1633
|
+
try {
|
|
1634
|
+
await this.testClient.setStorageAt({
|
|
1635
|
+
address,
|
|
1636
|
+
index: slot,
|
|
1637
|
+
value
|
|
1638
|
+
});
|
|
1639
|
+
} catch (error) {
|
|
1640
|
+
throw new NodeError(
|
|
1641
|
+
`Failed to set storage: ${error instanceof Error ? error.message : String(error)}`,
|
|
1642
|
+
"STORAGE_SET_ERROR",
|
|
1643
|
+
"evm",
|
|
1644
|
+
{ address, slot, value, originalError: error }
|
|
1645
|
+
);
|
|
1646
|
+
}
|
|
1647
|
+
}
|
|
1648
|
+
watchTransactions(callback) {
|
|
1649
|
+
const unwatch = this.publicClient.watchPendingTransactions({
|
|
1650
|
+
onTransactions: (hashes) => {
|
|
1651
|
+
for (const hash of hashes) {
|
|
1652
|
+
callback({
|
|
1653
|
+
chainType: "evm",
|
|
1654
|
+
hash,
|
|
1655
|
+
from: "",
|
|
1656
|
+
// Would need to fetch transaction details
|
|
1657
|
+
to: "",
|
|
1658
|
+
// Would need to fetch transaction details
|
|
1659
|
+
value: 0n,
|
|
1660
|
+
// Would need to fetch transaction details
|
|
1661
|
+
blockNumber: 0n
|
|
1662
|
+
// Would need to fetch transaction details
|
|
1663
|
+
});
|
|
1664
|
+
}
|
|
1665
|
+
}
|
|
1666
|
+
});
|
|
1667
|
+
return unwatch;
|
|
1668
|
+
}
|
|
1669
|
+
isValidAddress(address) {
|
|
1670
|
+
return isEvmAddress(address);
|
|
1671
|
+
}
|
|
1672
|
+
async getCurrentEpoch() {
|
|
1673
|
+
return await this.getBlockNumber();
|
|
1674
|
+
}
|
|
1675
|
+
async generateAccounts(count) {
|
|
1676
|
+
const accounts = [];
|
|
1677
|
+
for (let i = 0; i < count; i++) {
|
|
1678
|
+
const privateKey = `0x${"0".repeat(64 - 2)}${i.toString(16).padStart(2, "0")}${"0".repeat(60)}`;
|
|
1679
|
+
const account = privateKeyToAccount2(privateKey);
|
|
1680
|
+
accounts.push(account.address);
|
|
1681
|
+
}
|
|
1682
|
+
return accounts;
|
|
1683
|
+
}
|
|
1684
|
+
};
|
|
1685
|
+
async function createEspaceClient(config) {
|
|
1686
|
+
const chainConfig = getChainConfig(config.chainId);
|
|
1687
|
+
if (chainConfig.type !== "evm") {
|
|
1688
|
+
throw new NodeError(
|
|
1689
|
+
`Invalid chain type for eSpace client: ${chainConfig.type}`,
|
|
1690
|
+
"INVALID_CHAIN_TYPE",
|
|
1691
|
+
"evm"
|
|
1692
|
+
);
|
|
1693
|
+
}
|
|
1694
|
+
const clientConfig = {
|
|
1695
|
+
...config,
|
|
1696
|
+
rpcUrl: config.rpcUrl || chainConfig.rpcUrls.default.http[0] || "http://localhost:8545"
|
|
1697
|
+
};
|
|
1698
|
+
const publicClient = new EspaceClient(clientConfig);
|
|
1699
|
+
let walletClient;
|
|
1700
|
+
let testClient;
|
|
1701
|
+
if (config.account) {
|
|
1702
|
+
let privateKey;
|
|
1703
|
+
if (typeof config.account === "string") {
|
|
1704
|
+
privateKey = config.account;
|
|
1705
|
+
} else {
|
|
1706
|
+
privateKey = config.account.privateKey;
|
|
1707
|
+
}
|
|
1708
|
+
const walletConfig = {
|
|
1709
|
+
...clientConfig,
|
|
1710
|
+
privateKey,
|
|
1711
|
+
accountIndex: typeof config.account === "object" ? config.account.accountIndex : 0
|
|
1712
|
+
};
|
|
1713
|
+
walletClient = new EspaceWalletClient(walletConfig);
|
|
1714
|
+
}
|
|
1715
|
+
if (config.testMode) {
|
|
1716
|
+
let privateKey;
|
|
1717
|
+
if (config.account) {
|
|
1718
|
+
if (typeof config.account === "string") {
|
|
1719
|
+
privateKey = config.account;
|
|
1720
|
+
} else {
|
|
1721
|
+
privateKey = config.account.privateKey;
|
|
1722
|
+
}
|
|
1723
|
+
} else {
|
|
1724
|
+
privateKey = "0x0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef";
|
|
1725
|
+
}
|
|
1726
|
+
const testConfig = {
|
|
1727
|
+
...clientConfig,
|
|
1728
|
+
enableTestMode: true,
|
|
1729
|
+
privateKey
|
|
1730
|
+
};
|
|
1731
|
+
testClient = new EspaceTestClient(testConfig);
|
|
1732
|
+
}
|
|
1733
|
+
return {
|
|
1734
|
+
publicClient,
|
|
1735
|
+
walletClient,
|
|
1736
|
+
testClient
|
|
1737
|
+
};
|
|
1738
|
+
}
|
|
1739
|
+
|
|
1740
|
+
// src/clients/manager.ts
|
|
1741
|
+
import { EventEmitter } from "events";
|
|
1742
|
+
var HEALTH_CHECK_INTERVAL = 3e4;
|
|
1743
|
+
var HEALTH_CHECK_TIMEOUT = 5e3;
|
|
1744
|
+
var ClientManager = class extends EventEmitter {
|
|
1745
|
+
config;
|
|
1746
|
+
coreClient = null;
|
|
1747
|
+
evmClient = null;
|
|
1748
|
+
healthCheckInterval = null;
|
|
1749
|
+
networkSelectorUnsubscribe = null;
|
|
1750
|
+
nodeRunningUnsubscribe = null;
|
|
1751
|
+
initialized = false;
|
|
1752
|
+
constructor(config) {
|
|
1753
|
+
super();
|
|
1754
|
+
this.config = {
|
|
1755
|
+
enableHealthMonitoring: true,
|
|
1756
|
+
healthCheckInterval: HEALTH_CHECK_INTERVAL,
|
|
1757
|
+
healthCheckTimeout: HEALTH_CHECK_TIMEOUT,
|
|
1758
|
+
...config
|
|
1759
|
+
};
|
|
1760
|
+
}
|
|
1761
|
+
/**
|
|
1762
|
+
* Initialize the Client Manager
|
|
1763
|
+
* Sets up clients, server, and monitoring
|
|
1764
|
+
*/
|
|
1765
|
+
async initialize() {
|
|
1766
|
+
if (this.initialized) {
|
|
1767
|
+
return;
|
|
1768
|
+
}
|
|
1769
|
+
try {
|
|
1770
|
+
this.setupNetworkListeners();
|
|
1771
|
+
await this.initializeClients();
|
|
1772
|
+
if (this.config.enableHealthMonitoring) {
|
|
1773
|
+
this.startHealthMonitoring();
|
|
1774
|
+
}
|
|
1775
|
+
this.initialized = true;
|
|
1776
|
+
this.emit("manager:ready");
|
|
1777
|
+
} catch (error) {
|
|
1778
|
+
const managerError = error instanceof Error ? error : new Error(String(error));
|
|
1779
|
+
this.emit("manager:error", { error: managerError });
|
|
1780
|
+
throw managerError;
|
|
1781
|
+
}
|
|
1782
|
+
}
|
|
1783
|
+
/**
|
|
1784
|
+
* Gracefully shutdown the Client Manager
|
|
1785
|
+
*/
|
|
1786
|
+
async shutdown() {
|
|
1787
|
+
if (!this.initialized) {
|
|
1788
|
+
return;
|
|
1789
|
+
}
|
|
1790
|
+
try {
|
|
1791
|
+
this.stopHealthMonitoring();
|
|
1792
|
+
if (this.networkSelectorUnsubscribe) {
|
|
1793
|
+
this.networkSelectorUnsubscribe();
|
|
1794
|
+
this.networkSelectorUnsubscribe = null;
|
|
1795
|
+
}
|
|
1796
|
+
if (this.nodeRunningUnsubscribe) {
|
|
1797
|
+
this.nodeRunningUnsubscribe();
|
|
1798
|
+
this.nodeRunningUnsubscribe = null;
|
|
1799
|
+
}
|
|
1800
|
+
this.coreClient = null;
|
|
1801
|
+
this.evmClient = null;
|
|
1802
|
+
this.initialized = false;
|
|
1803
|
+
this.removeAllListeners();
|
|
1804
|
+
} catch (error) {
|
|
1805
|
+
const shutdownError = error instanceof Error ? error : new Error(String(error));
|
|
1806
|
+
this.emit("manager:error", { error: shutdownError });
|
|
1807
|
+
throw shutdownError;
|
|
1808
|
+
}
|
|
1809
|
+
}
|
|
1810
|
+
/**
|
|
1811
|
+
* Get current Core Space client
|
|
1812
|
+
*/
|
|
1813
|
+
getCoreClient() {
|
|
1814
|
+
return this.coreClient;
|
|
1815
|
+
}
|
|
1816
|
+
/**
|
|
1817
|
+
* Get current eSpace client
|
|
1818
|
+
*/
|
|
1819
|
+
getEvmClient() {
|
|
1820
|
+
return this.evmClient;
|
|
1821
|
+
}
|
|
1822
|
+
/**
|
|
1823
|
+
* Get comprehensive status
|
|
1824
|
+
*/
|
|
1825
|
+
getStatus() {
|
|
1826
|
+
const currentChainId = defaultNetworkSelector.getCurrentChainId();
|
|
1827
|
+
const currentChain = getChainConfig(currentChainId);
|
|
1828
|
+
return {
|
|
1829
|
+
initialized: this.initialized,
|
|
1830
|
+
coreClient: {
|
|
1831
|
+
connected: !!this.coreClient,
|
|
1832
|
+
chainId: currentChain.type === "core" ? currentChainId : defaultNetworkSelector.getCorrespondingChainId() || 1,
|
|
1833
|
+
health: "unknown",
|
|
1834
|
+
// TODO: Implement health status tracking
|
|
1835
|
+
lastHealthCheck: void 0
|
|
1836
|
+
},
|
|
1837
|
+
evmClient: {
|
|
1838
|
+
connected: !!this.evmClient,
|
|
1839
|
+
chainId: currentChain.type === "evm" ? currentChainId : defaultNetworkSelector.getCorrespondingChainId() || 71,
|
|
1840
|
+
health: "unknown",
|
|
1841
|
+
// TODO: Implement health status tracking
|
|
1842
|
+
lastHealthCheck: void 0
|
|
1843
|
+
},
|
|
1844
|
+
networkSelector: {
|
|
1845
|
+
currentChain: currentChainId,
|
|
1846
|
+
isLocalNode: defaultNetworkSelector.getNodeRunningStatus(),
|
|
1847
|
+
lockedToLocal: defaultNetworkSelector.isLockedToLocal()
|
|
1848
|
+
}
|
|
1849
|
+
};
|
|
1850
|
+
}
|
|
1851
|
+
/**
|
|
1852
|
+
* Switch to a specific network
|
|
1853
|
+
* @param chainId - Target chain ID
|
|
1854
|
+
* @param force - Force switch even if node is running (for wallet operations)
|
|
1855
|
+
*/
|
|
1856
|
+
async switchNetwork(chainId, force = false) {
|
|
1857
|
+
if (!isValidChainId(chainId)) {
|
|
1858
|
+
throw new Error(`Invalid chain ID: ${chainId}`);
|
|
1859
|
+
}
|
|
1860
|
+
const previousChainId = defaultNetworkSelector.getCurrentChainId();
|
|
1861
|
+
if (previousChainId === chainId) {
|
|
1862
|
+
return;
|
|
1863
|
+
}
|
|
1864
|
+
defaultNetworkSelector.switchChain(chainId, force);
|
|
1865
|
+
if (defaultNetworkSelector.getCurrentChainId() === chainId) {
|
|
1866
|
+
await this.initializeClients();
|
|
1867
|
+
this.emit("network:switched", { from: previousChainId, to: chainId });
|
|
1868
|
+
}
|
|
1869
|
+
}
|
|
1870
|
+
/**
|
|
1871
|
+
* Initialize or reinitialize client instances based on current network
|
|
1872
|
+
*/
|
|
1873
|
+
async initializeClients() {
|
|
1874
|
+
const currentChainId = defaultNetworkSelector.getCurrentChainId();
|
|
1875
|
+
const currentChain = getChainConfig(currentChainId);
|
|
1876
|
+
try {
|
|
1877
|
+
if (currentChain.type === "core") {
|
|
1878
|
+
const evmChainId = defaultNetworkSelector.getCorrespondingChainId() || 71;
|
|
1879
|
+
this.coreClient = await createCoreClient({
|
|
1880
|
+
...this.config.core,
|
|
1881
|
+
chainId: currentChainId
|
|
1882
|
+
});
|
|
1883
|
+
this.evmClient = await createEspaceClient({
|
|
1884
|
+
...this.config.evm,
|
|
1885
|
+
chainId: evmChainId
|
|
1886
|
+
});
|
|
1887
|
+
} else {
|
|
1888
|
+
const coreChainId = defaultNetworkSelector.getCorrespondingChainId() || 1;
|
|
1889
|
+
this.coreClient = await createCoreClient({
|
|
1890
|
+
...this.config.core,
|
|
1891
|
+
chainId: coreChainId
|
|
1892
|
+
});
|
|
1893
|
+
this.evmClient = await createEspaceClient({
|
|
1894
|
+
...this.config.evm,
|
|
1895
|
+
chainId: currentChainId
|
|
1896
|
+
});
|
|
1897
|
+
}
|
|
1898
|
+
this.emit("client:ready", {
|
|
1899
|
+
type: "core",
|
|
1900
|
+
chainId: this.coreClient.publicClient.chainId
|
|
1901
|
+
});
|
|
1902
|
+
this.emit("client:ready", {
|
|
1903
|
+
type: "evm",
|
|
1904
|
+
chainId: this.evmClient.publicClient.chainId
|
|
1905
|
+
});
|
|
1906
|
+
} catch (error) {
|
|
1907
|
+
const clientError = error instanceof Error ? error : new Error(String(error));
|
|
1908
|
+
this.emit("manager:error", { error: clientError });
|
|
1909
|
+
throw clientError;
|
|
1910
|
+
}
|
|
1911
|
+
}
|
|
1912
|
+
/**
|
|
1913
|
+
* Set up network selector event listeners
|
|
1914
|
+
*/
|
|
1915
|
+
setupNetworkListeners() {
|
|
1916
|
+
this.networkSelectorUnsubscribe = defaultNetworkSelector.onChainChange(
|
|
1917
|
+
async (_chainId) => {
|
|
1918
|
+
try {
|
|
1919
|
+
await this.initializeClients();
|
|
1920
|
+
} catch (error) {
|
|
1921
|
+
const networkError = error instanceof Error ? error : new Error(String(error));
|
|
1922
|
+
this.emit("manager:error", { error: networkError });
|
|
1923
|
+
}
|
|
1924
|
+
}
|
|
1925
|
+
);
|
|
1926
|
+
this.nodeRunningUnsubscribe = defaultNetworkSelector.onNodeRunningChange(
|
|
1927
|
+
async (isRunning) => {
|
|
1928
|
+
if (isRunning) {
|
|
1929
|
+
} else {
|
|
1930
|
+
}
|
|
1931
|
+
}
|
|
1932
|
+
);
|
|
1933
|
+
}
|
|
1934
|
+
/**
|
|
1935
|
+
* Start health monitoring for all clients
|
|
1936
|
+
*/
|
|
1937
|
+
startHealthMonitoring() {
|
|
1938
|
+
if (this.healthCheckInterval) {
|
|
1939
|
+
return;
|
|
1940
|
+
}
|
|
1941
|
+
this.healthCheckInterval = setInterval(async () => {
|
|
1942
|
+
await this.performHealthChecks();
|
|
1943
|
+
}, this.config.healthCheckInterval || HEALTH_CHECK_INTERVAL);
|
|
1944
|
+
setTimeout(() => this.performHealthChecks(), 1e3);
|
|
1945
|
+
}
|
|
1946
|
+
/**
|
|
1947
|
+
* Stop health monitoring
|
|
1948
|
+
*/
|
|
1949
|
+
stopHealthMonitoring() {
|
|
1950
|
+
if (this.healthCheckInterval) {
|
|
1951
|
+
clearInterval(this.healthCheckInterval);
|
|
1952
|
+
this.healthCheckInterval = null;
|
|
1953
|
+
}
|
|
1954
|
+
}
|
|
1955
|
+
/**
|
|
1956
|
+
* Perform health checks on all clients
|
|
1957
|
+
*/
|
|
1958
|
+
async performHealthChecks() {
|
|
1959
|
+
const timeout = this.config.healthCheckTimeout || HEALTH_CHECK_TIMEOUT;
|
|
1960
|
+
if (this.coreClient) {
|
|
1961
|
+
try {
|
|
1962
|
+
const healthPromise = this.checkCoreClientHealth();
|
|
1963
|
+
const result = await Promise.race([
|
|
1964
|
+
healthPromise,
|
|
1965
|
+
new Promise(
|
|
1966
|
+
(_, reject) => setTimeout(() => reject(new Error("Health check timeout")), timeout)
|
|
1967
|
+
)
|
|
1968
|
+
]);
|
|
1969
|
+
this.emit("client:health", {
|
|
1970
|
+
type: "core",
|
|
1971
|
+
chainId: this.coreClient.publicClient.chainId,
|
|
1972
|
+
status: result
|
|
1973
|
+
});
|
|
1974
|
+
} catch (error) {
|
|
1975
|
+
this.emit("client:error", {
|
|
1976
|
+
type: "core",
|
|
1977
|
+
chainId: this.coreClient.publicClient.chainId,
|
|
1978
|
+
error: error instanceof Error ? error : new Error(String(error))
|
|
1979
|
+
});
|
|
1980
|
+
}
|
|
1981
|
+
}
|
|
1982
|
+
if (this.evmClient) {
|
|
1983
|
+
try {
|
|
1984
|
+
const healthPromise = this.checkEvmClientHealth();
|
|
1985
|
+
const result = await Promise.race([
|
|
1986
|
+
healthPromise,
|
|
1987
|
+
new Promise(
|
|
1988
|
+
(_, reject) => setTimeout(() => reject(new Error("Health check timeout")), timeout)
|
|
1989
|
+
)
|
|
1990
|
+
]);
|
|
1991
|
+
this.emit("client:health", {
|
|
1992
|
+
type: "evm",
|
|
1993
|
+
chainId: this.evmClient.publicClient.chainId,
|
|
1994
|
+
status: result
|
|
1995
|
+
});
|
|
1996
|
+
} catch (error) {
|
|
1997
|
+
this.emit("client:error", {
|
|
1998
|
+
type: "evm",
|
|
1999
|
+
chainId: this.evmClient.publicClient.chainId,
|
|
2000
|
+
error: error instanceof Error ? error : new Error(String(error))
|
|
2001
|
+
});
|
|
2002
|
+
}
|
|
2003
|
+
}
|
|
2004
|
+
}
|
|
2005
|
+
/**
|
|
2006
|
+
* Check Core client health
|
|
2007
|
+
*/
|
|
2008
|
+
async checkCoreClientHealth() {
|
|
2009
|
+
if (!this.coreClient) {
|
|
2010
|
+
return "disconnected";
|
|
2011
|
+
}
|
|
2012
|
+
try {
|
|
2013
|
+
await this.coreClient.publicClient.getBlockNumber();
|
|
2014
|
+
return "healthy";
|
|
2015
|
+
} catch (_error) {
|
|
2016
|
+
return "unhealthy";
|
|
2017
|
+
}
|
|
2018
|
+
}
|
|
2019
|
+
/**
|
|
2020
|
+
* Check eSpace client health
|
|
2021
|
+
*/
|
|
2022
|
+
async checkEvmClientHealth() {
|
|
2023
|
+
if (!this.evmClient) {
|
|
2024
|
+
return "disconnected";
|
|
2025
|
+
}
|
|
2026
|
+
try {
|
|
2027
|
+
await this.evmClient.publicClient.getBlockNumber();
|
|
2028
|
+
return "healthy";
|
|
2029
|
+
} catch (_error) {
|
|
2030
|
+
return "unhealthy";
|
|
2031
|
+
}
|
|
2032
|
+
}
|
|
2033
|
+
};
|
|
2034
|
+
export {
|
|
2035
|
+
ClientManager,
|
|
2036
|
+
CoreClient,
|
|
2037
|
+
CoreTestClient,
|
|
2038
|
+
CoreWalletClient,
|
|
2039
|
+
EspaceClient,
|
|
2040
|
+
EspaceTestClient,
|
|
2041
|
+
EspaceWalletClient
|
|
2042
|
+
};
|
|
2043
|
+
//# sourceMappingURL=index.js.map
|