@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
package/dist/index.js
ADDED
|
@@ -0,0 +1,3730 @@
|
|
|
1
|
+
// src/index.ts
|
|
2
|
+
import { formatCFX as formatCFX2, parseCFX as parseCFX2 } from "cive";
|
|
3
|
+
import { isAddress } from "cive/utils";
|
|
4
|
+
import { formatUnits as formatUnits2, isAddress as isAddress2, parseUnits } from "viem";
|
|
5
|
+
|
|
6
|
+
// src/clients/core.ts
|
|
7
|
+
import {
|
|
8
|
+
createPublicClient,
|
|
9
|
+
createTestClient,
|
|
10
|
+
createWalletClient,
|
|
11
|
+
formatCFX,
|
|
12
|
+
http,
|
|
13
|
+
parseCFX
|
|
14
|
+
} from "cive";
|
|
15
|
+
import { privateKeyToAccount } from "cive/accounts";
|
|
16
|
+
import {
|
|
17
|
+
defineChain as defineChain2,
|
|
18
|
+
encodeFunctionData,
|
|
19
|
+
formatUnits,
|
|
20
|
+
hexAddressToBase32,
|
|
21
|
+
isAddress as isCoreAddress
|
|
22
|
+
} from "cive/utils";
|
|
23
|
+
import { isAddress as isEspaceAddress } from "viem";
|
|
24
|
+
|
|
25
|
+
// src/config/chains.ts
|
|
26
|
+
import { defineChain } from "cive/utils";
|
|
27
|
+
import { defineChain as defineEvmChain } from "viem";
|
|
28
|
+
var CORE_MAINNET = {
|
|
29
|
+
id: 1029,
|
|
30
|
+
name: "conflux-core",
|
|
31
|
+
type: "core",
|
|
32
|
+
testnet: false,
|
|
33
|
+
nativeCurrency: {
|
|
34
|
+
name: "Conflux",
|
|
35
|
+
symbol: "CFX",
|
|
36
|
+
decimals: 18
|
|
37
|
+
},
|
|
38
|
+
rpcUrls: {
|
|
39
|
+
default: {
|
|
40
|
+
http: ["https://main.confluxrpc.com"],
|
|
41
|
+
webSocket: ["wss://main.confluxrpc.com/ws"]
|
|
42
|
+
}
|
|
43
|
+
},
|
|
44
|
+
blockExplorers: {
|
|
45
|
+
default: {
|
|
46
|
+
name: "ConfluxScan",
|
|
47
|
+
url: "https://confluxscan.io"
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
};
|
|
51
|
+
var CORE_TESTNET = {
|
|
52
|
+
id: 1,
|
|
53
|
+
name: "conflux-core-testnet",
|
|
54
|
+
type: "core",
|
|
55
|
+
testnet: true,
|
|
56
|
+
nativeCurrency: {
|
|
57
|
+
name: "Conflux",
|
|
58
|
+
symbol: "CFX",
|
|
59
|
+
decimals: 18
|
|
60
|
+
},
|
|
61
|
+
rpcUrls: {
|
|
62
|
+
default: {
|
|
63
|
+
http: ["https://test.confluxrpc.com"],
|
|
64
|
+
webSocket: ["wss://test.confluxrpc.com/ws"]
|
|
65
|
+
}
|
|
66
|
+
},
|
|
67
|
+
blockExplorers: {
|
|
68
|
+
default: {
|
|
69
|
+
name: "ConfluxScan Testnet",
|
|
70
|
+
url: "https://testnet.confluxscan.io"
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
};
|
|
74
|
+
var CORE_LOCAL = {
|
|
75
|
+
id: 2029,
|
|
76
|
+
name: "conflux-core-local",
|
|
77
|
+
type: "core",
|
|
78
|
+
testnet: true,
|
|
79
|
+
nativeCurrency: {
|
|
80
|
+
name: "Conflux",
|
|
81
|
+
symbol: "CFX",
|
|
82
|
+
decimals: 18
|
|
83
|
+
},
|
|
84
|
+
rpcUrls: {
|
|
85
|
+
default: {
|
|
86
|
+
http: ["http://localhost:12537"],
|
|
87
|
+
webSocket: ["ws://localhost:12536"]
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
};
|
|
91
|
+
var EVM_MAINNET = {
|
|
92
|
+
id: 1030,
|
|
93
|
+
name: "conflux-espace",
|
|
94
|
+
type: "evm",
|
|
95
|
+
testnet: false,
|
|
96
|
+
nativeCurrency: {
|
|
97
|
+
name: "Conflux",
|
|
98
|
+
symbol: "CFX",
|
|
99
|
+
decimals: 18
|
|
100
|
+
},
|
|
101
|
+
rpcUrls: {
|
|
102
|
+
default: {
|
|
103
|
+
http: ["https://evm.confluxrpc.com"]
|
|
104
|
+
}
|
|
105
|
+
},
|
|
106
|
+
blockExplorers: {
|
|
107
|
+
default: {
|
|
108
|
+
name: "ConfluxScan eSpace",
|
|
109
|
+
url: "https://evm.confluxscan.net"
|
|
110
|
+
}
|
|
111
|
+
},
|
|
112
|
+
contracts: {
|
|
113
|
+
multicall3: {
|
|
114
|
+
address: "0xcA11bde05977b3631167028862bE2a173976CA11",
|
|
115
|
+
blockCreated: 62512243
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
};
|
|
119
|
+
var EVM_TESTNET = {
|
|
120
|
+
id: 71,
|
|
121
|
+
name: "conflux-espace-testnet",
|
|
122
|
+
type: "evm",
|
|
123
|
+
testnet: true,
|
|
124
|
+
nativeCurrency: {
|
|
125
|
+
name: "Conflux",
|
|
126
|
+
symbol: "CFX",
|
|
127
|
+
decimals: 18
|
|
128
|
+
},
|
|
129
|
+
rpcUrls: {
|
|
130
|
+
default: {
|
|
131
|
+
http: ["https://evmtestnet.confluxrpc.com"]
|
|
132
|
+
}
|
|
133
|
+
},
|
|
134
|
+
blockExplorers: {
|
|
135
|
+
default: {
|
|
136
|
+
name: "ConfluxScan eSpace Testnet",
|
|
137
|
+
url: "https://evmtestnet.confluxscan.net"
|
|
138
|
+
}
|
|
139
|
+
},
|
|
140
|
+
contracts: {
|
|
141
|
+
multicall3: {
|
|
142
|
+
address: "0xcA11bde05977b3631167028862bE2a173976CA11",
|
|
143
|
+
blockCreated: 117499050
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
};
|
|
147
|
+
var EVM_LOCAL = {
|
|
148
|
+
id: 2030,
|
|
149
|
+
name: "conflux-espace-local",
|
|
150
|
+
type: "evm",
|
|
151
|
+
testnet: true,
|
|
152
|
+
nativeCurrency: {
|
|
153
|
+
name: "Conflux",
|
|
154
|
+
symbol: "CFX",
|
|
155
|
+
decimals: 18
|
|
156
|
+
},
|
|
157
|
+
rpcUrls: {
|
|
158
|
+
default: {
|
|
159
|
+
http: ["http://localhost:8545"]
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
};
|
|
163
|
+
var SUPPORTED_CHAINS = {
|
|
164
|
+
1029: CORE_MAINNET,
|
|
165
|
+
1: CORE_TESTNET,
|
|
166
|
+
2029: CORE_LOCAL,
|
|
167
|
+
1030: EVM_MAINNET,
|
|
168
|
+
71: EVM_TESTNET,
|
|
169
|
+
2030: EVM_LOCAL
|
|
170
|
+
};
|
|
171
|
+
function getChainConfig(chainId) {
|
|
172
|
+
const config = SUPPORTED_CHAINS[chainId];
|
|
173
|
+
if (!config) {
|
|
174
|
+
throw new Error(`Unsupported chain ID: ${chainId}`);
|
|
175
|
+
}
|
|
176
|
+
return config;
|
|
177
|
+
}
|
|
178
|
+
function isValidChainId(chainId) {
|
|
179
|
+
return chainId in SUPPORTED_CHAINS;
|
|
180
|
+
}
|
|
181
|
+
function getCoreChains() {
|
|
182
|
+
return Object.values(SUPPORTED_CHAINS).filter(
|
|
183
|
+
(chain) => chain.type === "core"
|
|
184
|
+
);
|
|
185
|
+
}
|
|
186
|
+
function getEvmChains() {
|
|
187
|
+
return Object.values(SUPPORTED_CHAINS).filter(
|
|
188
|
+
(chain) => chain.type === "evm"
|
|
189
|
+
);
|
|
190
|
+
}
|
|
191
|
+
function getMainnetChains() {
|
|
192
|
+
return Object.values(SUPPORTED_CHAINS).filter((chain) => !chain.testnet);
|
|
193
|
+
}
|
|
194
|
+
var NetworkSelector = class {
|
|
195
|
+
currentChainId;
|
|
196
|
+
previousChainId = null;
|
|
197
|
+
listeners = /* @__PURE__ */ new Set();
|
|
198
|
+
nodeRunningListeners = /* @__PURE__ */ new Set();
|
|
199
|
+
isNodeRunning = false;
|
|
200
|
+
lockedToLocal = false;
|
|
201
|
+
constructor(initialChainId = 1) {
|
|
202
|
+
this.currentChainId = initialChainId;
|
|
203
|
+
}
|
|
204
|
+
getCurrentChain() {
|
|
205
|
+
return getChainConfig(this.currentChainId);
|
|
206
|
+
}
|
|
207
|
+
getCurrentChainId() {
|
|
208
|
+
return this.currentChainId;
|
|
209
|
+
}
|
|
210
|
+
/**
|
|
211
|
+
* Switch to a specific chain
|
|
212
|
+
* @param chainId - Chain ID to switch to
|
|
213
|
+
* @param force - Force switch even if node is running (for wallet operations)
|
|
214
|
+
*/
|
|
215
|
+
switchChain(chainId, force = false) {
|
|
216
|
+
if (!isValidChainId(chainId)) {
|
|
217
|
+
throw new Error(`Invalid chain ID: ${chainId}`);
|
|
218
|
+
}
|
|
219
|
+
if (this.isNodeRunning && !this.isLocalChain(chainId) && !force) {
|
|
220
|
+
console.warn(
|
|
221
|
+
`Cannot switch to chain ${chainId} while local node is running. Use force=true for wallet operations.`
|
|
222
|
+
);
|
|
223
|
+
return;
|
|
224
|
+
}
|
|
225
|
+
if (this.currentChainId !== chainId) {
|
|
226
|
+
this.currentChainId = chainId;
|
|
227
|
+
this.notifyListeners();
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
/**
|
|
231
|
+
* Called when local node starts - automatically switches to local chains
|
|
232
|
+
*/
|
|
233
|
+
onNodeStart(coreChainId = 2029, evmChainId = 2030) {
|
|
234
|
+
if (!this.isNodeRunning) {
|
|
235
|
+
if (!this.isLocal()) {
|
|
236
|
+
this.previousChainId = this.currentChainId;
|
|
237
|
+
}
|
|
238
|
+
this.isNodeRunning = true;
|
|
239
|
+
this.lockedToLocal = true;
|
|
240
|
+
const targetLocalChain = this.isEvm() ? evmChainId : coreChainId;
|
|
241
|
+
this.switchChain(targetLocalChain, true);
|
|
242
|
+
this.notifyNodeRunningListeners();
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
/**
|
|
246
|
+
* Called when local node stops - can restore previous chain
|
|
247
|
+
*/
|
|
248
|
+
onNodeStop(restorePrevious = true) {
|
|
249
|
+
if (this.isNodeRunning) {
|
|
250
|
+
this.isNodeRunning = false;
|
|
251
|
+
this.lockedToLocal = false;
|
|
252
|
+
if (restorePrevious && this.previousChainId) {
|
|
253
|
+
this.switchChain(this.previousChainId, true);
|
|
254
|
+
this.previousChainId = null;
|
|
255
|
+
}
|
|
256
|
+
this.notifyNodeRunningListeners();
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
/**
|
|
260
|
+
* Check if node is currently running
|
|
261
|
+
*/
|
|
262
|
+
getNodeRunningStatus() {
|
|
263
|
+
return this.isNodeRunning;
|
|
264
|
+
}
|
|
265
|
+
/**
|
|
266
|
+
* Check if selector is locked to local chains
|
|
267
|
+
*/
|
|
268
|
+
isLockedToLocal() {
|
|
269
|
+
return this.lockedToLocal;
|
|
270
|
+
}
|
|
271
|
+
onChainChange(listener) {
|
|
272
|
+
this.listeners.add(listener);
|
|
273
|
+
return () => this.listeners.delete(listener);
|
|
274
|
+
}
|
|
275
|
+
onNodeRunningChange(listener) {
|
|
276
|
+
this.nodeRunningListeners.add(listener);
|
|
277
|
+
return () => this.nodeRunningListeners.delete(listener);
|
|
278
|
+
}
|
|
279
|
+
notifyListeners() {
|
|
280
|
+
for (const listener of this.listeners) {
|
|
281
|
+
try {
|
|
282
|
+
listener(this.currentChainId);
|
|
283
|
+
} catch (error) {
|
|
284
|
+
console.error("Error in chain change listener:", error);
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
notifyNodeRunningListeners() {
|
|
289
|
+
for (const listener of this.nodeRunningListeners) {
|
|
290
|
+
try {
|
|
291
|
+
listener(this.isNodeRunning);
|
|
292
|
+
} catch (error) {
|
|
293
|
+
console.error("Error in node running listener:", error);
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
isLocalChain(chainId) {
|
|
298
|
+
return chainId === 2029 || chainId === 2030;
|
|
299
|
+
}
|
|
300
|
+
// Helper methods for chain type detection
|
|
301
|
+
isCore() {
|
|
302
|
+
return this.getCurrentChain().type === "core";
|
|
303
|
+
}
|
|
304
|
+
isEvm() {
|
|
305
|
+
return this.getCurrentChain().type === "evm";
|
|
306
|
+
}
|
|
307
|
+
isTestnet() {
|
|
308
|
+
return this.getCurrentChain().testnet;
|
|
309
|
+
}
|
|
310
|
+
isLocal() {
|
|
311
|
+
return this.currentChainId === 2029 || this.currentChainId === 2030;
|
|
312
|
+
}
|
|
313
|
+
// Get corresponding chain IDs
|
|
314
|
+
getCorrespondingChainId() {
|
|
315
|
+
switch (this.currentChainId) {
|
|
316
|
+
case 1029:
|
|
317
|
+
return 1030;
|
|
318
|
+
// Core mainnet -> eSpace mainnet
|
|
319
|
+
case 1030:
|
|
320
|
+
return 1029;
|
|
321
|
+
// eSpace mainnet -> Core mainnet
|
|
322
|
+
case 1:
|
|
323
|
+
return 71;
|
|
324
|
+
// Core testnet -> eSpace testnet
|
|
325
|
+
case 71:
|
|
326
|
+
return 1;
|
|
327
|
+
// eSpace testnet -> Core testnet
|
|
328
|
+
case 2029:
|
|
329
|
+
return 2030;
|
|
330
|
+
// Core local -> eSpace local
|
|
331
|
+
case 2030:
|
|
332
|
+
return 2029;
|
|
333
|
+
// eSpace local -> Core local
|
|
334
|
+
default:
|
|
335
|
+
return null;
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
/**
|
|
339
|
+
* Update local chain configurations with actual node URLs
|
|
340
|
+
* Called when ServerManager starts with specific ports
|
|
341
|
+
*/
|
|
342
|
+
updateLocalChainUrls(coreRpcPort, evmRpcPort, wsPort) {
|
|
343
|
+
const coreLocal = SUPPORTED_CHAINS[2029];
|
|
344
|
+
if (coreLocal) {
|
|
345
|
+
coreLocal.rpcUrls.default.http = [`http://localhost:${coreRpcPort}`];
|
|
346
|
+
if (wsPort) {
|
|
347
|
+
coreLocal.rpcUrls.default.webSocket = [`ws://localhost:${wsPort}`];
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
const evmLocal = SUPPORTED_CHAINS[2030];
|
|
351
|
+
if (evmLocal) {
|
|
352
|
+
evmLocal.rpcUrls.default.http = [`http://localhost:${evmRpcPort}`];
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
};
|
|
356
|
+
var defaultNetworkSelector = new NetworkSelector();
|
|
357
|
+
|
|
358
|
+
// src/types/config.ts
|
|
359
|
+
var NodeError = class extends Error {
|
|
360
|
+
code;
|
|
361
|
+
chain;
|
|
362
|
+
context;
|
|
363
|
+
constructor(message, code, chain, context) {
|
|
364
|
+
super(message);
|
|
365
|
+
this.name = "NodeError";
|
|
366
|
+
this.code = code;
|
|
367
|
+
this.chain = chain;
|
|
368
|
+
this.context = context;
|
|
369
|
+
}
|
|
370
|
+
};
|
|
371
|
+
|
|
372
|
+
// src/clients/core.ts
|
|
373
|
+
var conflux = defineChain2({
|
|
374
|
+
id: 1029,
|
|
375
|
+
name: "Conflux Core",
|
|
376
|
+
nativeCurrency: { name: "Conflux", symbol: "CFX", decimals: 18 },
|
|
377
|
+
rpcUrls: { default: { http: ["https://main.confluxrpc.com"] } }
|
|
378
|
+
});
|
|
379
|
+
var confluxTestnet = defineChain2({
|
|
380
|
+
id: 1,
|
|
381
|
+
name: "Conflux Core Testnet",
|
|
382
|
+
nativeCurrency: { name: "Conflux", symbol: "CFX", decimals: 18 },
|
|
383
|
+
rpcUrls: { default: { http: ["https://test.confluxrpc.com"] } }
|
|
384
|
+
});
|
|
385
|
+
var CoreClient = class {
|
|
386
|
+
chainType = "core";
|
|
387
|
+
chainId;
|
|
388
|
+
address;
|
|
389
|
+
publicClient;
|
|
390
|
+
chain;
|
|
391
|
+
constructor(config) {
|
|
392
|
+
this.chainId = config.chainId;
|
|
393
|
+
this.chain = defineChain2({
|
|
394
|
+
id: this.chainId,
|
|
395
|
+
name: `ConfluxCore-${this.chainId}`,
|
|
396
|
+
nativeCurrency: {
|
|
397
|
+
decimals: 18,
|
|
398
|
+
name: "Conflux",
|
|
399
|
+
symbol: "CFX"
|
|
400
|
+
},
|
|
401
|
+
rpcUrls: {
|
|
402
|
+
default: {
|
|
403
|
+
http: [config.rpcUrl],
|
|
404
|
+
webSocket: config.wsUrl ? [config.wsUrl] : void 0
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
});
|
|
408
|
+
this.publicClient = createPublicClient({
|
|
409
|
+
chain: this.chain,
|
|
410
|
+
transport: http(config.rpcUrl),
|
|
411
|
+
pollingInterval: config.pollingInterval || 1e3
|
|
412
|
+
});
|
|
413
|
+
this.address = "";
|
|
414
|
+
}
|
|
415
|
+
async getBlockNumber() {
|
|
416
|
+
try {
|
|
417
|
+
const epochNumber = await this.publicClient.getEpochNumber();
|
|
418
|
+
return BigInt(epochNumber.toString());
|
|
419
|
+
} catch (error) {
|
|
420
|
+
throw new NodeError(
|
|
421
|
+
`Failed to get block number: ${error instanceof Error ? error.message : String(error)}`,
|
|
422
|
+
"BLOCK_NUMBER_ERROR",
|
|
423
|
+
"core",
|
|
424
|
+
{ originalError: error }
|
|
425
|
+
);
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
async getBalance(address) {
|
|
429
|
+
if (!isCoreAddress(address)) {
|
|
430
|
+
throw new NodeError(
|
|
431
|
+
"Invalid Core address format",
|
|
432
|
+
"INVALID_ADDRESS",
|
|
433
|
+
"core"
|
|
434
|
+
);
|
|
435
|
+
}
|
|
436
|
+
try {
|
|
437
|
+
const balance = await this.publicClient.getBalance({ address });
|
|
438
|
+
return formatCFX(balance);
|
|
439
|
+
} catch (error) {
|
|
440
|
+
throw new NodeError(
|
|
441
|
+
`Failed to get balance: ${error instanceof Error ? error.message : String(error)}`,
|
|
442
|
+
"BALANCE_ERROR",
|
|
443
|
+
"core",
|
|
444
|
+
{ address, originalError: error }
|
|
445
|
+
);
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
async getGasPrice() {
|
|
449
|
+
try {
|
|
450
|
+
const gasPrice = await this.publicClient.getGasPrice();
|
|
451
|
+
return gasPrice;
|
|
452
|
+
} catch (error) {
|
|
453
|
+
throw new NodeError(
|
|
454
|
+
`Failed to get gas price: ${error instanceof Error ? error.message : String(error)}`,
|
|
455
|
+
"GAS_PRICE_ERROR",
|
|
456
|
+
"core",
|
|
457
|
+
{ originalError: error }
|
|
458
|
+
);
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
async estimateGas(tx) {
|
|
462
|
+
try {
|
|
463
|
+
const estimate = await this.publicClient.request({
|
|
464
|
+
method: "cfx_estimateGasAndCollateral",
|
|
465
|
+
params: [
|
|
466
|
+
{
|
|
467
|
+
to: tx.to,
|
|
468
|
+
value: tx.value ? `0x${tx.value.toString(16)}` : void 0,
|
|
469
|
+
data: tx.data
|
|
470
|
+
}
|
|
471
|
+
]
|
|
472
|
+
});
|
|
473
|
+
return BigInt(estimate.gasLimit);
|
|
474
|
+
} catch (error) {
|
|
475
|
+
throw new NodeError(
|
|
476
|
+
`Failed to estimate gas: ${error instanceof Error ? error.message : String(error)}`,
|
|
477
|
+
"GAS_ESTIMATE_ERROR",
|
|
478
|
+
"core",
|
|
479
|
+
{ transaction: tx, originalError: error }
|
|
480
|
+
);
|
|
481
|
+
}
|
|
482
|
+
}
|
|
483
|
+
async sendTransaction(_tx) {
|
|
484
|
+
throw new NodeError(
|
|
485
|
+
"Cannot send transaction from public client. Use wallet client instead.",
|
|
486
|
+
"WALLET_REQUIRED",
|
|
487
|
+
"core"
|
|
488
|
+
);
|
|
489
|
+
}
|
|
490
|
+
async waitForTransaction(hash) {
|
|
491
|
+
try {
|
|
492
|
+
const receipt = await this.publicClient.waitForTransactionReceipt({
|
|
493
|
+
hash
|
|
494
|
+
});
|
|
495
|
+
return {
|
|
496
|
+
hash: receipt.transactionHash,
|
|
497
|
+
blockNumber: BigInt(receipt.epochNumber?.toString() || "0"),
|
|
498
|
+
blockHash: receipt.blockHash || "",
|
|
499
|
+
transactionIndex: Number(receipt.index || 0),
|
|
500
|
+
status: receipt.outcomeStatus === "success" ? "success" : "reverted",
|
|
501
|
+
gasUsed: receipt.gasUsed || 0n,
|
|
502
|
+
contractAddress: receipt.contractCreated || void 0,
|
|
503
|
+
logs: receipt.log?.map((log) => ({
|
|
504
|
+
address: log.address || "",
|
|
505
|
+
topics: log.topics || [],
|
|
506
|
+
data: log.data || "0x",
|
|
507
|
+
blockNumber: BigInt(log.epochNumber?.toString() || "0"),
|
|
508
|
+
transactionHash: log.transactionHash || "",
|
|
509
|
+
logIndex: Number(log.logIndex || 0)
|
|
510
|
+
})) || []
|
|
511
|
+
};
|
|
512
|
+
} catch (error) {
|
|
513
|
+
throw new NodeError(
|
|
514
|
+
`Failed to wait for transaction: ${error instanceof Error ? error.message : String(error)}`,
|
|
515
|
+
"TRANSACTION_WAIT_ERROR",
|
|
516
|
+
"core",
|
|
517
|
+
{ hash, originalError: error }
|
|
518
|
+
);
|
|
519
|
+
}
|
|
520
|
+
}
|
|
521
|
+
async getTokenBalance(tokenAddress, holderAddress) {
|
|
522
|
+
const holder = holderAddress || this.address;
|
|
523
|
+
if (!this.isValidAddress(tokenAddress)) {
|
|
524
|
+
throw new NodeError(
|
|
525
|
+
"Invalid token address format",
|
|
526
|
+
"INVALID_ADDRESS",
|
|
527
|
+
"core",
|
|
528
|
+
{ tokenAddress }
|
|
529
|
+
);
|
|
530
|
+
}
|
|
531
|
+
if (!this.isValidAddress(holder)) {
|
|
532
|
+
throw new NodeError(
|
|
533
|
+
"Invalid holder address format",
|
|
534
|
+
"INVALID_ADDRESS",
|
|
535
|
+
"core",
|
|
536
|
+
{ holder }
|
|
537
|
+
);
|
|
538
|
+
}
|
|
539
|
+
try {
|
|
540
|
+
const [balance, decimals] = await Promise.all([
|
|
541
|
+
this.publicClient.readContract({
|
|
542
|
+
address: tokenAddress,
|
|
543
|
+
abi: [
|
|
544
|
+
{
|
|
545
|
+
name: "balanceOf",
|
|
546
|
+
type: "function",
|
|
547
|
+
inputs: [{ name: "account", type: "address" }],
|
|
548
|
+
outputs: [{ name: "", type: "uint256" }],
|
|
549
|
+
stateMutability: "view"
|
|
550
|
+
}
|
|
551
|
+
],
|
|
552
|
+
functionName: "balanceOf",
|
|
553
|
+
args: [holder]
|
|
554
|
+
}),
|
|
555
|
+
this.publicClient.readContract({
|
|
556
|
+
address: tokenAddress,
|
|
557
|
+
abi: [
|
|
558
|
+
{
|
|
559
|
+
name: "decimals",
|
|
560
|
+
type: "function",
|
|
561
|
+
inputs: [],
|
|
562
|
+
outputs: [{ name: "", type: "uint8" }],
|
|
563
|
+
stateMutability: "view"
|
|
564
|
+
}
|
|
565
|
+
],
|
|
566
|
+
functionName: "decimals"
|
|
567
|
+
})
|
|
568
|
+
]);
|
|
569
|
+
return this.formatTokenAmount(balance, Number(decimals));
|
|
570
|
+
} catch (error) {
|
|
571
|
+
throw new NodeError(
|
|
572
|
+
`Failed to get token balance: ${error instanceof Error ? error.message : String(error)}`,
|
|
573
|
+
"TOKEN_BALANCE_ERROR",
|
|
574
|
+
"core",
|
|
575
|
+
{ tokenAddress, holder, originalError: error }
|
|
576
|
+
);
|
|
577
|
+
}
|
|
578
|
+
}
|
|
579
|
+
watchBlocks(callback) {
|
|
580
|
+
return this.publicClient.watchEpochNumber({
|
|
581
|
+
emitMissed: false,
|
|
582
|
+
epochTag: "latest_mined",
|
|
583
|
+
onEpochNumber: async (epochNumber) => {
|
|
584
|
+
try {
|
|
585
|
+
const blockHashes = await this.publicClient.getBlocksByEpoch({
|
|
586
|
+
epochNumber
|
|
587
|
+
});
|
|
588
|
+
for (const hash of blockHashes) {
|
|
589
|
+
try {
|
|
590
|
+
const block = await this.publicClient.getBlock({
|
|
591
|
+
blockHash: hash
|
|
592
|
+
});
|
|
593
|
+
const blockEvent = {
|
|
594
|
+
chainType: "core",
|
|
595
|
+
blockNumber: BigInt(block.epochNumber?.toString() || "0"),
|
|
596
|
+
blockHash: block.hash || "",
|
|
597
|
+
timestamp: Number(block.timestamp || 0),
|
|
598
|
+
transactionCount: block.transactions?.length || 0
|
|
599
|
+
};
|
|
600
|
+
callback(blockEvent);
|
|
601
|
+
} catch (error) {
|
|
602
|
+
console.error(`Failed to process block ${hash}:`, error);
|
|
603
|
+
}
|
|
604
|
+
}
|
|
605
|
+
} catch (error) {
|
|
606
|
+
console.error(
|
|
607
|
+
`Failed to get blocks for epoch ${epochNumber}:`,
|
|
608
|
+
error
|
|
609
|
+
);
|
|
610
|
+
}
|
|
611
|
+
}
|
|
612
|
+
});
|
|
613
|
+
}
|
|
614
|
+
watchTransactions(callback) {
|
|
615
|
+
return this.publicClient.watchEpochNumber({
|
|
616
|
+
emitMissed: false,
|
|
617
|
+
epochTag: "latest_mined",
|
|
618
|
+
onEpochNumber: async (epochNumber) => {
|
|
619
|
+
try {
|
|
620
|
+
const blockHashes = await this.publicClient.getBlocksByEpoch({
|
|
621
|
+
epochNumber
|
|
622
|
+
});
|
|
623
|
+
for (const hash of blockHashes) {
|
|
624
|
+
try {
|
|
625
|
+
const block = await this.publicClient.getBlock({
|
|
626
|
+
blockHash: hash
|
|
627
|
+
});
|
|
628
|
+
await Promise.all(
|
|
629
|
+
(block.transactions || []).map(
|
|
630
|
+
async (txHash) => {
|
|
631
|
+
try {
|
|
632
|
+
const tx = await this.publicClient.getTransaction({
|
|
633
|
+
hash: txHash
|
|
634
|
+
});
|
|
635
|
+
const txEvent = {
|
|
636
|
+
chainType: "core",
|
|
637
|
+
hash: tx.hash,
|
|
638
|
+
from: tx.from,
|
|
639
|
+
to: tx.to || void 0,
|
|
640
|
+
value: tx.value || 0n,
|
|
641
|
+
blockNumber: BigInt(
|
|
642
|
+
block.epochNumber?.toString() || "0"
|
|
643
|
+
)
|
|
644
|
+
};
|
|
645
|
+
callback(txEvent);
|
|
646
|
+
} catch (error) {
|
|
647
|
+
console.error(
|
|
648
|
+
`Failed to get transaction ${txHash}:`,
|
|
649
|
+
error
|
|
650
|
+
);
|
|
651
|
+
}
|
|
652
|
+
}
|
|
653
|
+
)
|
|
654
|
+
);
|
|
655
|
+
} catch (error) {
|
|
656
|
+
console.error(`Failed to process block ${hash}:`, error);
|
|
657
|
+
}
|
|
658
|
+
}
|
|
659
|
+
} catch (error) {
|
|
660
|
+
console.error(
|
|
661
|
+
`Failed to get blocks for epoch ${epochNumber}:`,
|
|
662
|
+
error
|
|
663
|
+
);
|
|
664
|
+
}
|
|
665
|
+
}
|
|
666
|
+
});
|
|
667
|
+
}
|
|
668
|
+
isValidAddress(address) {
|
|
669
|
+
return isCoreAddress(address);
|
|
670
|
+
}
|
|
671
|
+
formatAmount(amount) {
|
|
672
|
+
return formatCFX(amount);
|
|
673
|
+
}
|
|
674
|
+
parseAmount(amount) {
|
|
675
|
+
return parseCFX(amount);
|
|
676
|
+
}
|
|
677
|
+
getInternalClient() {
|
|
678
|
+
return this.publicClient;
|
|
679
|
+
}
|
|
680
|
+
formatTokenAmount(amount, decimals) {
|
|
681
|
+
const formatted = formatUnits(amount, decimals);
|
|
682
|
+
return Number(formatted).toFixed(4);
|
|
683
|
+
}
|
|
684
|
+
};
|
|
685
|
+
var CoreWalletClient = class {
|
|
686
|
+
chainType = "core";
|
|
687
|
+
address;
|
|
688
|
+
chainId;
|
|
689
|
+
walletClient;
|
|
690
|
+
publicClient;
|
|
691
|
+
account;
|
|
692
|
+
chain;
|
|
693
|
+
constructor(config) {
|
|
694
|
+
this.chainId = config.chainId;
|
|
695
|
+
this.chain = defineChain2({
|
|
696
|
+
id: config.chainId,
|
|
697
|
+
name: `ConfluxCore-${config.chainId}`,
|
|
698
|
+
nativeCurrency: {
|
|
699
|
+
decimals: 18,
|
|
700
|
+
name: "Conflux",
|
|
701
|
+
symbol: "CFX"
|
|
702
|
+
},
|
|
703
|
+
rpcUrls: {
|
|
704
|
+
default: {
|
|
705
|
+
http: [config.rpcUrl],
|
|
706
|
+
webSocket: config.wsUrl ? [config.wsUrl] : void 0
|
|
707
|
+
}
|
|
708
|
+
}
|
|
709
|
+
});
|
|
710
|
+
this.account = privateKeyToAccount(config.privateKey, {
|
|
711
|
+
networkId: config.chainId
|
|
712
|
+
});
|
|
713
|
+
this.address = this.account.address;
|
|
714
|
+
this.walletClient = createWalletClient({
|
|
715
|
+
account: this.account,
|
|
716
|
+
chain: this.chain,
|
|
717
|
+
transport: http(config.rpcUrl),
|
|
718
|
+
pollingInterval: config.pollingInterval || 1e3
|
|
719
|
+
});
|
|
720
|
+
this.publicClient = createPublicClient({
|
|
721
|
+
chain: this.chain,
|
|
722
|
+
transport: http(config.rpcUrl),
|
|
723
|
+
pollingInterval: config.pollingInterval || 1e3
|
|
724
|
+
});
|
|
725
|
+
}
|
|
726
|
+
async sendTransaction(tx) {
|
|
727
|
+
try {
|
|
728
|
+
return await this.walletClient.sendTransaction({
|
|
729
|
+
to: tx.to,
|
|
730
|
+
value: tx.value,
|
|
731
|
+
data: tx.data,
|
|
732
|
+
gas: tx.gasLimit,
|
|
733
|
+
gasPrice: tx.gasPrice,
|
|
734
|
+
nonce: tx.nonce,
|
|
735
|
+
account: this.account,
|
|
736
|
+
chain: this.chain
|
|
737
|
+
});
|
|
738
|
+
} catch (error) {
|
|
739
|
+
throw new NodeError(
|
|
740
|
+
`Failed to send transaction: ${error instanceof Error ? error.message : String(error)}`,
|
|
741
|
+
"TRANSACTION_SEND_ERROR",
|
|
742
|
+
"core",
|
|
743
|
+
{ transaction: tx, originalError: error }
|
|
744
|
+
);
|
|
745
|
+
}
|
|
746
|
+
}
|
|
747
|
+
async signMessage(message) {
|
|
748
|
+
try {
|
|
749
|
+
return await this.walletClient.signMessage({
|
|
750
|
+
account: this.account,
|
|
751
|
+
message
|
|
752
|
+
});
|
|
753
|
+
} catch (error) {
|
|
754
|
+
throw new NodeError(
|
|
755
|
+
`Failed to sign message: ${error instanceof Error ? error.message : String(error)}`,
|
|
756
|
+
"MESSAGE_SIGN_ERROR",
|
|
757
|
+
"core",
|
|
758
|
+
{ message, originalError: error }
|
|
759
|
+
);
|
|
760
|
+
}
|
|
761
|
+
}
|
|
762
|
+
getInternalClient() {
|
|
763
|
+
return this.walletClient;
|
|
764
|
+
}
|
|
765
|
+
async waitForTransaction(hash) {
|
|
766
|
+
try {
|
|
767
|
+
const receipt = await this.publicClient.waitForTransactionReceipt({
|
|
768
|
+
hash,
|
|
769
|
+
timeout: 5e3
|
|
770
|
+
// 5 second timeout for faster response
|
|
771
|
+
});
|
|
772
|
+
return {
|
|
773
|
+
hash: receipt.transactionHash,
|
|
774
|
+
blockNumber: BigInt(receipt.epochNumber?.toString() || "0"),
|
|
775
|
+
blockHash: receipt.blockHash || "",
|
|
776
|
+
transactionIndex: Number(receipt.index || 0),
|
|
777
|
+
status: receipt.outcomeStatus === "success" ? "success" : "reverted",
|
|
778
|
+
gasUsed: receipt.gasUsed || 0n,
|
|
779
|
+
contractAddress: receipt.contractCreated || void 0,
|
|
780
|
+
logs: receipt.log?.map((log) => ({
|
|
781
|
+
address: log.address || "",
|
|
782
|
+
topics: log.topics || [],
|
|
783
|
+
data: log.data || "0x",
|
|
784
|
+
blockNumber: BigInt(log.epochNumber?.toString() || "0"),
|
|
785
|
+
transactionHash: log.transactionHash || "",
|
|
786
|
+
logIndex: Number(log.logIndex || 0)
|
|
787
|
+
})) || []
|
|
788
|
+
};
|
|
789
|
+
} catch (error) {
|
|
790
|
+
throw new NodeError(
|
|
791
|
+
`Failed to wait for transaction: ${error instanceof Error ? error.message : String(error)}`,
|
|
792
|
+
"TRANSACTION_WAIT_ERROR",
|
|
793
|
+
"core",
|
|
794
|
+
{ hash, originalError: error }
|
|
795
|
+
);
|
|
796
|
+
}
|
|
797
|
+
}
|
|
798
|
+
/**
|
|
799
|
+
* Unified faucet functionality
|
|
800
|
+
* Automatically detects address type and sends CFX accordingly:
|
|
801
|
+
* - Core address: Direct transfer
|
|
802
|
+
* - eSpace address: Cross-chain transfer via internal contract
|
|
803
|
+
*/
|
|
804
|
+
async faucet(address, amount) {
|
|
805
|
+
const isCoreAddr = isCoreAddress(address);
|
|
806
|
+
const isEspaceAddr = isEspaceAddress(address);
|
|
807
|
+
if (!isCoreAddr && !isEspaceAddr) {
|
|
808
|
+
throw new NodeError(
|
|
809
|
+
"Invalid address format (must be Core or eSpace address)",
|
|
810
|
+
"INVALID_ADDRESS",
|
|
811
|
+
"core",
|
|
812
|
+
{ address }
|
|
813
|
+
);
|
|
814
|
+
}
|
|
815
|
+
try {
|
|
816
|
+
if (isCoreAddr) {
|
|
817
|
+
return await this.walletClient.sendTransaction({
|
|
818
|
+
chain: this.chain,
|
|
819
|
+
account: this.account,
|
|
820
|
+
to: address,
|
|
821
|
+
value: parseCFX(amount)
|
|
822
|
+
});
|
|
823
|
+
} else {
|
|
824
|
+
return await this.walletClient.sendTransaction({
|
|
825
|
+
chain: this.chain,
|
|
826
|
+
account: this.account,
|
|
827
|
+
to: hexAddressToBase32({
|
|
828
|
+
hexAddress: "0x0888000000000000000000000000000000000006",
|
|
829
|
+
networkId: this.chain.id
|
|
830
|
+
}),
|
|
831
|
+
value: parseCFX(amount),
|
|
832
|
+
data: encodeFunctionData({
|
|
833
|
+
abi: [
|
|
834
|
+
{
|
|
835
|
+
type: "function",
|
|
836
|
+
name: "transferEVM",
|
|
837
|
+
inputs: [{ name: "to", type: "bytes20" }],
|
|
838
|
+
outputs: [{ name: "output", type: "bytes" }],
|
|
839
|
+
stateMutability: "payable"
|
|
840
|
+
}
|
|
841
|
+
],
|
|
842
|
+
functionName: "transferEVM",
|
|
843
|
+
args: [address]
|
|
844
|
+
})
|
|
845
|
+
});
|
|
846
|
+
}
|
|
847
|
+
} catch (error) {
|
|
848
|
+
throw new NodeError(
|
|
849
|
+
`Failed to send faucet transaction: ${error instanceof Error ? error.message : String(error)}`,
|
|
850
|
+
"FAUCET_ERROR",
|
|
851
|
+
"core",
|
|
852
|
+
{ address, amount, originalError: error }
|
|
853
|
+
);
|
|
854
|
+
}
|
|
855
|
+
}
|
|
856
|
+
/**
|
|
857
|
+
* Cross-chain faucet functionality (Core → eSpace)
|
|
858
|
+
* Sends CFX from Core space to eSpace address via internal contract
|
|
859
|
+
* @deprecated Use faucet() instead which auto-detects address type
|
|
860
|
+
*/
|
|
861
|
+
async faucetToEspace(espaceAddress, amount) {
|
|
862
|
+
if (!isEspaceAddress(espaceAddress)) {
|
|
863
|
+
throw new NodeError(
|
|
864
|
+
"Invalid eSpace address format",
|
|
865
|
+
"INVALID_ADDRESS",
|
|
866
|
+
"core",
|
|
867
|
+
{ espaceAddress }
|
|
868
|
+
);
|
|
869
|
+
}
|
|
870
|
+
try {
|
|
871
|
+
return await this.walletClient.sendTransaction({
|
|
872
|
+
chain: this.chain,
|
|
873
|
+
account: this.account,
|
|
874
|
+
to: hexAddressToBase32({
|
|
875
|
+
hexAddress: "0x0888000000000000000000000000000000000006",
|
|
876
|
+
networkId: this.chain.id
|
|
877
|
+
}),
|
|
878
|
+
value: parseCFX(amount),
|
|
879
|
+
data: encodeFunctionData({
|
|
880
|
+
abi: [
|
|
881
|
+
{
|
|
882
|
+
type: "function",
|
|
883
|
+
name: "transferEVM",
|
|
884
|
+
inputs: [{ name: "to", type: "bytes20" }],
|
|
885
|
+
outputs: [{ name: "output", type: "bytes" }],
|
|
886
|
+
stateMutability: "payable"
|
|
887
|
+
}
|
|
888
|
+
],
|
|
889
|
+
functionName: "transferEVM",
|
|
890
|
+
args: [espaceAddress]
|
|
891
|
+
})
|
|
892
|
+
});
|
|
893
|
+
} catch (error) {
|
|
894
|
+
throw new NodeError(
|
|
895
|
+
`Failed to send faucet transaction to eSpace: ${error instanceof Error ? error.message : String(error)}`,
|
|
896
|
+
"FAUCET_ERROR",
|
|
897
|
+
"core",
|
|
898
|
+
{ espaceAddress, amount, originalError: error }
|
|
899
|
+
);
|
|
900
|
+
}
|
|
901
|
+
}
|
|
902
|
+
/**
|
|
903
|
+
* Deploy a contract to Core Space
|
|
904
|
+
*/
|
|
905
|
+
async deployContract(abi, bytecode, constructorArgs = []) {
|
|
906
|
+
try {
|
|
907
|
+
const hash = await this.walletClient.deployContract({
|
|
908
|
+
account: this.account,
|
|
909
|
+
chain: this.chain,
|
|
910
|
+
abi,
|
|
911
|
+
bytecode,
|
|
912
|
+
args: constructorArgs
|
|
913
|
+
});
|
|
914
|
+
const receipt = await this.waitForTransaction(hash);
|
|
915
|
+
if (!receipt.contractAddress) {
|
|
916
|
+
throw new Error("Contract address not found in transaction receipt");
|
|
917
|
+
}
|
|
918
|
+
return receipt.contractAddress;
|
|
919
|
+
} catch (error) {
|
|
920
|
+
throw new NodeError(
|
|
921
|
+
`Failed to deploy contract: ${error instanceof Error ? error.message : String(error)}`,
|
|
922
|
+
"DEPLOYMENT_ERROR",
|
|
923
|
+
"core",
|
|
924
|
+
{ abi, bytecode, constructorArgs, originalError: error }
|
|
925
|
+
);
|
|
926
|
+
}
|
|
927
|
+
}
|
|
928
|
+
/**
|
|
929
|
+
* Call a contract method (read-only)
|
|
930
|
+
*/
|
|
931
|
+
async callContract(address, abi, functionName, args = []) {
|
|
932
|
+
try {
|
|
933
|
+
const result = await this.publicClient.readContract({
|
|
934
|
+
address,
|
|
935
|
+
abi,
|
|
936
|
+
functionName,
|
|
937
|
+
args
|
|
938
|
+
});
|
|
939
|
+
return result;
|
|
940
|
+
} catch (error) {
|
|
941
|
+
throw new NodeError(
|
|
942
|
+
`Failed to call contract: ${error instanceof Error ? error.message : String(error)}`,
|
|
943
|
+
"CONTRACT_CALL_ERROR",
|
|
944
|
+
"core",
|
|
945
|
+
{ address, functionName, args, originalError: error }
|
|
946
|
+
);
|
|
947
|
+
}
|
|
948
|
+
}
|
|
949
|
+
/**
|
|
950
|
+
* Write to a contract (transaction)
|
|
951
|
+
*/
|
|
952
|
+
async writeContract(address, abi, functionName, args = [], value) {
|
|
953
|
+
try {
|
|
954
|
+
const hash = await this.walletClient.writeContract({
|
|
955
|
+
account: this.account,
|
|
956
|
+
chain: this.chain,
|
|
957
|
+
address,
|
|
958
|
+
abi,
|
|
959
|
+
functionName,
|
|
960
|
+
args,
|
|
961
|
+
value
|
|
962
|
+
});
|
|
963
|
+
return hash;
|
|
964
|
+
} catch (error) {
|
|
965
|
+
throw new NodeError(
|
|
966
|
+
`Failed to write to contract: ${error instanceof Error ? error.message : String(error)}`,
|
|
967
|
+
"CONTRACT_WRITE_ERROR",
|
|
968
|
+
"core",
|
|
969
|
+
{ address, functionName, args, value, originalError: error }
|
|
970
|
+
);
|
|
971
|
+
}
|
|
972
|
+
}
|
|
973
|
+
};
|
|
974
|
+
var CoreTestClient = class extends CoreClient {
|
|
975
|
+
testClient;
|
|
976
|
+
constructor(config) {
|
|
977
|
+
super(config);
|
|
978
|
+
this.testClient = createTestClient({
|
|
979
|
+
chain: this.chainId === 1029 ? conflux : confluxTestnet,
|
|
980
|
+
transport: http(config.rpcUrl),
|
|
981
|
+
pollingInterval: config.pollingInterval || 1e3
|
|
982
|
+
});
|
|
983
|
+
}
|
|
984
|
+
async mine(blocks = 1) {
|
|
985
|
+
try {
|
|
986
|
+
await this.testClient.mine({ blocks });
|
|
987
|
+
} catch (error) {
|
|
988
|
+
throw new NodeError(
|
|
989
|
+
`Failed to mine blocks: ${error instanceof Error ? error.message : String(error)}`,
|
|
990
|
+
"MINE_ERROR",
|
|
991
|
+
"core",
|
|
992
|
+
{ blocks, originalError: error }
|
|
993
|
+
);
|
|
994
|
+
}
|
|
995
|
+
}
|
|
996
|
+
async setNextBlockTimestamp(_timestamp) {
|
|
997
|
+
throw new NodeError(
|
|
998
|
+
"setNextBlockTimestamp not implemented for Core client",
|
|
999
|
+
"NOT_IMPLEMENTED",
|
|
1000
|
+
"core"
|
|
1001
|
+
);
|
|
1002
|
+
}
|
|
1003
|
+
async increaseTime(_seconds) {
|
|
1004
|
+
throw new NodeError(
|
|
1005
|
+
"increaseTime not implemented for Core client",
|
|
1006
|
+
"NOT_IMPLEMENTED",
|
|
1007
|
+
"core"
|
|
1008
|
+
);
|
|
1009
|
+
}
|
|
1010
|
+
async impersonateAccount(_address) {
|
|
1011
|
+
throw new NodeError(
|
|
1012
|
+
"impersonateAccount not implemented for Core client",
|
|
1013
|
+
"NOT_IMPLEMENTED",
|
|
1014
|
+
"core"
|
|
1015
|
+
);
|
|
1016
|
+
}
|
|
1017
|
+
async stopImpersonatingAccount(_address) {
|
|
1018
|
+
throw new NodeError(
|
|
1019
|
+
"stopImpersonatingAccount not implemented for Core client",
|
|
1020
|
+
"NOT_IMPLEMENTED",
|
|
1021
|
+
"core"
|
|
1022
|
+
);
|
|
1023
|
+
}
|
|
1024
|
+
async setBalance(_address, _balance) {
|
|
1025
|
+
throw new NodeError(
|
|
1026
|
+
"setBalance not implemented for Core client",
|
|
1027
|
+
"NOT_IMPLEMENTED",
|
|
1028
|
+
"core"
|
|
1029
|
+
);
|
|
1030
|
+
}
|
|
1031
|
+
async getStorageAt(_address, _slot) {
|
|
1032
|
+
throw new NodeError(
|
|
1033
|
+
"getStorageAt not implemented for Core client",
|
|
1034
|
+
"NOT_IMPLEMENTED",
|
|
1035
|
+
"core"
|
|
1036
|
+
);
|
|
1037
|
+
}
|
|
1038
|
+
async setStorageAt(_address, _slot, _value) {
|
|
1039
|
+
throw new NodeError(
|
|
1040
|
+
"setStorageAt not implemented for Core client",
|
|
1041
|
+
"NOT_IMPLEMENTED",
|
|
1042
|
+
"core"
|
|
1043
|
+
);
|
|
1044
|
+
}
|
|
1045
|
+
getInternalTestClient() {
|
|
1046
|
+
return this.testClient;
|
|
1047
|
+
}
|
|
1048
|
+
};
|
|
1049
|
+
async function createCoreClient(config) {
|
|
1050
|
+
const chainConfig = getChainConfig(config.chainId);
|
|
1051
|
+
if (chainConfig.type !== "core") {
|
|
1052
|
+
throw new NodeError(
|
|
1053
|
+
`Invalid chain type for Core client: ${chainConfig.type}`,
|
|
1054
|
+
"INVALID_CHAIN_TYPE",
|
|
1055
|
+
"core"
|
|
1056
|
+
);
|
|
1057
|
+
}
|
|
1058
|
+
const clientConfig = {
|
|
1059
|
+
...config,
|
|
1060
|
+
rpcUrl: config.rpcUrl || chainConfig.rpcUrls.default.http[0] || "http://localhost:12537",
|
|
1061
|
+
wsUrl: config.wsUrl || chainConfig.rpcUrls.default.webSocket?.[0]
|
|
1062
|
+
};
|
|
1063
|
+
const publicClient = new CoreClient(clientConfig);
|
|
1064
|
+
let walletClient;
|
|
1065
|
+
let testClient;
|
|
1066
|
+
if (config.account) {
|
|
1067
|
+
let privateKey;
|
|
1068
|
+
if (typeof config.account === "string") {
|
|
1069
|
+
privateKey = config.account;
|
|
1070
|
+
} else {
|
|
1071
|
+
privateKey = config.account.privateKey;
|
|
1072
|
+
}
|
|
1073
|
+
const walletConfig = {
|
|
1074
|
+
...clientConfig,
|
|
1075
|
+
privateKey,
|
|
1076
|
+
accountIndex: typeof config.account === "object" ? config.account.accountIndex : 0
|
|
1077
|
+
};
|
|
1078
|
+
walletClient = new CoreWalletClient(walletConfig);
|
|
1079
|
+
}
|
|
1080
|
+
if (config.testMode) {
|
|
1081
|
+
const testConfig = {
|
|
1082
|
+
...clientConfig,
|
|
1083
|
+
enableTestMode: true
|
|
1084
|
+
};
|
|
1085
|
+
testClient = new CoreTestClient(testConfig);
|
|
1086
|
+
}
|
|
1087
|
+
return {
|
|
1088
|
+
publicClient,
|
|
1089
|
+
walletClient,
|
|
1090
|
+
testClient
|
|
1091
|
+
};
|
|
1092
|
+
}
|
|
1093
|
+
|
|
1094
|
+
// src/clients/evm.ts
|
|
1095
|
+
import {
|
|
1096
|
+
createPublicClient as createPublicClient2,
|
|
1097
|
+
createTestClient as createTestClient2,
|
|
1098
|
+
createWalletClient as createWalletClient2,
|
|
1099
|
+
defineChain as defineChain3,
|
|
1100
|
+
encodeFunctionData as encodeFunctionData2,
|
|
1101
|
+
formatEther,
|
|
1102
|
+
http as http2,
|
|
1103
|
+
isAddress as isEvmAddress,
|
|
1104
|
+
parseEther
|
|
1105
|
+
} from "viem";
|
|
1106
|
+
import { privateKeyToAccount as privateKeyToAccount2 } from "viem/accounts";
|
|
1107
|
+
var espaceMainnet = defineChain3({
|
|
1108
|
+
id: 1030,
|
|
1109
|
+
name: "Conflux eSpace",
|
|
1110
|
+
nativeCurrency: { name: "Conflux", symbol: "CFX", decimals: 18 },
|
|
1111
|
+
rpcUrls: { default: { http: ["https://evm.confluxrpc.com"] } },
|
|
1112
|
+
blockExplorers: {
|
|
1113
|
+
default: { name: "ConfluxScan", url: "https://evm.confluxscan.net" }
|
|
1114
|
+
}
|
|
1115
|
+
});
|
|
1116
|
+
var espaceTestnet = defineChain3({
|
|
1117
|
+
id: 71,
|
|
1118
|
+
name: "Conflux eSpace Testnet",
|
|
1119
|
+
nativeCurrency: { name: "Conflux", symbol: "CFX", decimals: 18 },
|
|
1120
|
+
rpcUrls: { default: { http: ["https://evmtestnet.confluxrpc.com"] } },
|
|
1121
|
+
blockExplorers: {
|
|
1122
|
+
default: { name: "ConfluxScan", url: "https://evmtestnet.confluxscan.net" }
|
|
1123
|
+
}
|
|
1124
|
+
});
|
|
1125
|
+
var EspaceClient = class {
|
|
1126
|
+
chainId;
|
|
1127
|
+
chainType = "evm";
|
|
1128
|
+
publicClient;
|
|
1129
|
+
chain;
|
|
1130
|
+
address;
|
|
1131
|
+
constructor(config) {
|
|
1132
|
+
this.chainId = config.chainId;
|
|
1133
|
+
if (config.chainId === 1030) {
|
|
1134
|
+
this.chain = espaceMainnet;
|
|
1135
|
+
} else if (config.chainId === 71) {
|
|
1136
|
+
this.chain = espaceTestnet;
|
|
1137
|
+
} else {
|
|
1138
|
+
this.chain = defineChain3({
|
|
1139
|
+
id: config.chainId,
|
|
1140
|
+
name: `Conflux eSpace (${config.chainId})`,
|
|
1141
|
+
nativeCurrency: { name: "Conflux", symbol: "CFX", decimals: 18 },
|
|
1142
|
+
rpcUrls: { default: { http: [config.rpcUrl] } }
|
|
1143
|
+
});
|
|
1144
|
+
}
|
|
1145
|
+
this.publicClient = createPublicClient2({
|
|
1146
|
+
chain: this.chain,
|
|
1147
|
+
transport: http2(config.rpcUrl),
|
|
1148
|
+
pollingInterval: config.pollingInterval || 1e3
|
|
1149
|
+
});
|
|
1150
|
+
this.address = "";
|
|
1151
|
+
}
|
|
1152
|
+
async getBlockNumber() {
|
|
1153
|
+
try {
|
|
1154
|
+
const blockNumber = await this.publicClient.getBlockNumber();
|
|
1155
|
+
return blockNumber;
|
|
1156
|
+
} catch (error) {
|
|
1157
|
+
throw new NodeError(
|
|
1158
|
+
`Failed to get block number: ${error instanceof Error ? error.message : String(error)}`,
|
|
1159
|
+
"BLOCK_NUMBER_ERROR",
|
|
1160
|
+
"evm",
|
|
1161
|
+
{ originalError: error }
|
|
1162
|
+
);
|
|
1163
|
+
}
|
|
1164
|
+
}
|
|
1165
|
+
async getBalance(address) {
|
|
1166
|
+
if (!isEvmAddress(address)) {
|
|
1167
|
+
throw new NodeError(
|
|
1168
|
+
"Invalid EVM address format",
|
|
1169
|
+
"INVALID_ADDRESS",
|
|
1170
|
+
"evm"
|
|
1171
|
+
);
|
|
1172
|
+
}
|
|
1173
|
+
try {
|
|
1174
|
+
const balance = await this.publicClient.getBalance({ address });
|
|
1175
|
+
return formatEther(balance);
|
|
1176
|
+
} catch (error) {
|
|
1177
|
+
throw new NodeError(
|
|
1178
|
+
`Failed to get balance: ${error instanceof Error ? error.message : String(error)}`,
|
|
1179
|
+
"BALANCE_ERROR",
|
|
1180
|
+
"evm",
|
|
1181
|
+
{ address, originalError: error }
|
|
1182
|
+
);
|
|
1183
|
+
}
|
|
1184
|
+
}
|
|
1185
|
+
async estimateGas(tx) {
|
|
1186
|
+
try {
|
|
1187
|
+
const gas = await this.publicClient.estimateGas({
|
|
1188
|
+
to: tx.to,
|
|
1189
|
+
value: tx.value,
|
|
1190
|
+
data: tx.data
|
|
1191
|
+
});
|
|
1192
|
+
return gas;
|
|
1193
|
+
} catch (error) {
|
|
1194
|
+
throw new NodeError(
|
|
1195
|
+
`Failed to estimate gas: ${error instanceof Error ? error.message : String(error)}`,
|
|
1196
|
+
"GAS_ESTIMATE_ERROR",
|
|
1197
|
+
"evm",
|
|
1198
|
+
{ transaction: tx, originalError: error }
|
|
1199
|
+
);
|
|
1200
|
+
}
|
|
1201
|
+
}
|
|
1202
|
+
async waitForTransaction(hash) {
|
|
1203
|
+
try {
|
|
1204
|
+
const receipt = await this.publicClient.waitForTransactionReceipt({
|
|
1205
|
+
hash,
|
|
1206
|
+
timeout: 5e3
|
|
1207
|
+
// 5 second timeout for faster response
|
|
1208
|
+
});
|
|
1209
|
+
return {
|
|
1210
|
+
hash: receipt.transactionHash,
|
|
1211
|
+
blockNumber: receipt.blockNumber,
|
|
1212
|
+
blockHash: receipt.blockHash,
|
|
1213
|
+
transactionIndex: receipt.transactionIndex,
|
|
1214
|
+
status: receipt.status === "success" ? "success" : "reverted",
|
|
1215
|
+
gasUsed: receipt.gasUsed,
|
|
1216
|
+
contractAddress: receipt.contractAddress || void 0,
|
|
1217
|
+
logs: receipt.logs.map((log) => ({
|
|
1218
|
+
address: log.address,
|
|
1219
|
+
topics: log.topics,
|
|
1220
|
+
data: log.data,
|
|
1221
|
+
blockNumber: log.blockNumber || 0n,
|
|
1222
|
+
transactionHash: log.transactionHash || "",
|
|
1223
|
+
logIndex: log.logIndex || 0
|
|
1224
|
+
}))
|
|
1225
|
+
};
|
|
1226
|
+
} catch (error) {
|
|
1227
|
+
throw new NodeError(
|
|
1228
|
+
`Failed to wait for transaction: ${error instanceof Error ? error.message : String(error)}`,
|
|
1229
|
+
"TRANSACTION_WAIT_ERROR",
|
|
1230
|
+
"evm",
|
|
1231
|
+
{ hash, originalError: error }
|
|
1232
|
+
);
|
|
1233
|
+
}
|
|
1234
|
+
}
|
|
1235
|
+
async getGasPrice() {
|
|
1236
|
+
try {
|
|
1237
|
+
const gasPrice = await this.publicClient.getGasPrice();
|
|
1238
|
+
return gasPrice;
|
|
1239
|
+
} catch (error) {
|
|
1240
|
+
throw new NodeError(
|
|
1241
|
+
`Failed to get gas price: ${error instanceof Error ? error.message : String(error)}`,
|
|
1242
|
+
"GAS_PRICE_ERROR",
|
|
1243
|
+
"evm",
|
|
1244
|
+
{ originalError: error }
|
|
1245
|
+
);
|
|
1246
|
+
}
|
|
1247
|
+
}
|
|
1248
|
+
/**
|
|
1249
|
+
* Get the current chain ID from the network
|
|
1250
|
+
*/
|
|
1251
|
+
async getChainId() {
|
|
1252
|
+
try {
|
|
1253
|
+
const chainId = await this.publicClient.getChainId();
|
|
1254
|
+
return chainId;
|
|
1255
|
+
} catch (error) {
|
|
1256
|
+
throw new NodeError(
|
|
1257
|
+
`Failed to get chain ID: ${error instanceof Error ? error.message : String(error)}`,
|
|
1258
|
+
"CHAIN_ID_ERROR",
|
|
1259
|
+
"evm",
|
|
1260
|
+
{ originalError: error }
|
|
1261
|
+
);
|
|
1262
|
+
}
|
|
1263
|
+
}
|
|
1264
|
+
/**
|
|
1265
|
+
* Check if the client is connected to the network
|
|
1266
|
+
*/
|
|
1267
|
+
async isConnected() {
|
|
1268
|
+
try {
|
|
1269
|
+
await this.publicClient.getBlockNumber();
|
|
1270
|
+
return true;
|
|
1271
|
+
} catch {
|
|
1272
|
+
return false;
|
|
1273
|
+
}
|
|
1274
|
+
}
|
|
1275
|
+
// Base implementation - should be overridden by WalletClient
|
|
1276
|
+
async sendTransaction(_tx) {
|
|
1277
|
+
throw new NodeError(
|
|
1278
|
+
"sendTransaction not available on public client",
|
|
1279
|
+
"METHOD_NOT_AVAILABLE",
|
|
1280
|
+
"evm"
|
|
1281
|
+
);
|
|
1282
|
+
}
|
|
1283
|
+
async getTokenBalance(_address, _tokenAddress) {
|
|
1284
|
+
try {
|
|
1285
|
+
const balance = await this.publicClient.readContract({
|
|
1286
|
+
address: _tokenAddress,
|
|
1287
|
+
abi: [
|
|
1288
|
+
{
|
|
1289
|
+
type: "function",
|
|
1290
|
+
name: "balanceOf",
|
|
1291
|
+
inputs: [{ name: "account", type: "address" }],
|
|
1292
|
+
outputs: [{ name: "balance", type: "uint256" }],
|
|
1293
|
+
stateMutability: "view"
|
|
1294
|
+
}
|
|
1295
|
+
],
|
|
1296
|
+
functionName: "balanceOf",
|
|
1297
|
+
args: [_address]
|
|
1298
|
+
});
|
|
1299
|
+
return balance.toString();
|
|
1300
|
+
} catch (error) {
|
|
1301
|
+
throw new NodeError(
|
|
1302
|
+
`Failed to get token balance: ${error instanceof Error ? error.message : String(error)}`,
|
|
1303
|
+
"TOKEN_BALANCE_ERROR",
|
|
1304
|
+
"evm",
|
|
1305
|
+
{ address: _address, tokenAddress: _tokenAddress, originalError: error }
|
|
1306
|
+
);
|
|
1307
|
+
}
|
|
1308
|
+
}
|
|
1309
|
+
watchBlocks(callback) {
|
|
1310
|
+
const unwatch = this.publicClient.watchBlocks({
|
|
1311
|
+
onBlock: (block) => callback({
|
|
1312
|
+
chainType: "evm",
|
|
1313
|
+
blockNumber: block.number || 0n,
|
|
1314
|
+
blockHash: block.hash || "",
|
|
1315
|
+
timestamp: Number(block.timestamp || 0),
|
|
1316
|
+
transactionCount: block.transactions?.length || 0
|
|
1317
|
+
})
|
|
1318
|
+
});
|
|
1319
|
+
return unwatch;
|
|
1320
|
+
}
|
|
1321
|
+
async watchTransaction(_hash, _callback) {
|
|
1322
|
+
const pollTransaction = async () => {
|
|
1323
|
+
try {
|
|
1324
|
+
const receipt = await this.waitForTransaction(_hash);
|
|
1325
|
+
_callback(receipt);
|
|
1326
|
+
} catch {
|
|
1327
|
+
setTimeout(pollTransaction, 1e3);
|
|
1328
|
+
}
|
|
1329
|
+
};
|
|
1330
|
+
setTimeout(pollTransaction, 1e3);
|
|
1331
|
+
return () => {
|
|
1332
|
+
};
|
|
1333
|
+
}
|
|
1334
|
+
getInternalClient() {
|
|
1335
|
+
return this.publicClient;
|
|
1336
|
+
}
|
|
1337
|
+
// Base implementation - should be overridden by TestClient
|
|
1338
|
+
watchTransactions(_callback) {
|
|
1339
|
+
throw new NodeError(
|
|
1340
|
+
"watchTransactions not available on public client",
|
|
1341
|
+
"METHOD_NOT_AVAILABLE",
|
|
1342
|
+
"evm"
|
|
1343
|
+
);
|
|
1344
|
+
}
|
|
1345
|
+
isValidAddress(address) {
|
|
1346
|
+
return isEvmAddress(address);
|
|
1347
|
+
}
|
|
1348
|
+
formatAmount(amount) {
|
|
1349
|
+
return formatEther(amount);
|
|
1350
|
+
}
|
|
1351
|
+
parseAmount(amount) {
|
|
1352
|
+
return parseEther(amount);
|
|
1353
|
+
}
|
|
1354
|
+
};
|
|
1355
|
+
var EspaceWalletClient = class extends EspaceClient {
|
|
1356
|
+
walletClient;
|
|
1357
|
+
account;
|
|
1358
|
+
constructor(config) {
|
|
1359
|
+
super(config);
|
|
1360
|
+
this.account = privateKeyToAccount2(config.privateKey);
|
|
1361
|
+
this.address = this.account.address;
|
|
1362
|
+
this.walletClient = createWalletClient2({
|
|
1363
|
+
account: this.account,
|
|
1364
|
+
chain: this.chain,
|
|
1365
|
+
transport: http2(config.rpcUrl)
|
|
1366
|
+
});
|
|
1367
|
+
}
|
|
1368
|
+
getAddress() {
|
|
1369
|
+
return this.address;
|
|
1370
|
+
}
|
|
1371
|
+
async sendTransaction(tx) {
|
|
1372
|
+
try {
|
|
1373
|
+
const hash = await this.walletClient.sendTransaction({
|
|
1374
|
+
account: this.account,
|
|
1375
|
+
chain: this.chain,
|
|
1376
|
+
to: tx.to,
|
|
1377
|
+
value: tx.value,
|
|
1378
|
+
data: tx.data,
|
|
1379
|
+
gas: tx.gasLimit,
|
|
1380
|
+
gasPrice: tx.gasPrice,
|
|
1381
|
+
nonce: tx.nonce
|
|
1382
|
+
});
|
|
1383
|
+
return hash;
|
|
1384
|
+
} catch (error) {
|
|
1385
|
+
throw new NodeError(
|
|
1386
|
+
`Failed to send transaction: ${error instanceof Error ? error.message : String(error)}`,
|
|
1387
|
+
"TRANSACTION_ERROR",
|
|
1388
|
+
"evm",
|
|
1389
|
+
{ transaction: tx, originalError: error }
|
|
1390
|
+
);
|
|
1391
|
+
}
|
|
1392
|
+
}
|
|
1393
|
+
async signMessage(message) {
|
|
1394
|
+
try {
|
|
1395
|
+
const signature = await this.walletClient.signMessage({
|
|
1396
|
+
account: this.account,
|
|
1397
|
+
message
|
|
1398
|
+
});
|
|
1399
|
+
return signature;
|
|
1400
|
+
} catch (error) {
|
|
1401
|
+
throw new NodeError(
|
|
1402
|
+
`Failed to sign message: ${error instanceof Error ? error.message : String(error)}`,
|
|
1403
|
+
"SIGNING_ERROR",
|
|
1404
|
+
"evm",
|
|
1405
|
+
{ message, originalError: error }
|
|
1406
|
+
);
|
|
1407
|
+
}
|
|
1408
|
+
}
|
|
1409
|
+
async deployContract(abi, bytecode, constructorArgs = []) {
|
|
1410
|
+
try {
|
|
1411
|
+
const hash = await this.walletClient.deployContract({
|
|
1412
|
+
account: this.account,
|
|
1413
|
+
chain: this.chain,
|
|
1414
|
+
abi,
|
|
1415
|
+
bytecode,
|
|
1416
|
+
args: constructorArgs
|
|
1417
|
+
});
|
|
1418
|
+
const receipt = await this.waitForTransaction(hash);
|
|
1419
|
+
if (!receipt.contractAddress) {
|
|
1420
|
+
throw new Error("Contract address not found in transaction receipt");
|
|
1421
|
+
}
|
|
1422
|
+
return receipt.contractAddress;
|
|
1423
|
+
} catch (error) {
|
|
1424
|
+
throw new NodeError(
|
|
1425
|
+
`Failed to deploy contract: ${error instanceof Error ? error.message : String(error)}`,
|
|
1426
|
+
"DEPLOYMENT_ERROR",
|
|
1427
|
+
"evm",
|
|
1428
|
+
{ bytecode, constructorArgs, originalError: error }
|
|
1429
|
+
);
|
|
1430
|
+
}
|
|
1431
|
+
}
|
|
1432
|
+
async callContract(address, abi, functionName, args = []) {
|
|
1433
|
+
try {
|
|
1434
|
+
const result = await this.publicClient.readContract({
|
|
1435
|
+
address,
|
|
1436
|
+
abi,
|
|
1437
|
+
functionName,
|
|
1438
|
+
args
|
|
1439
|
+
});
|
|
1440
|
+
return result;
|
|
1441
|
+
} catch (error) {
|
|
1442
|
+
throw new NodeError(
|
|
1443
|
+
`Failed to call contract: ${error instanceof Error ? error.message : String(error)}`,
|
|
1444
|
+
"CONTRACT_CALL_ERROR",
|
|
1445
|
+
"evm",
|
|
1446
|
+
{ address, functionName, args, originalError: error }
|
|
1447
|
+
);
|
|
1448
|
+
}
|
|
1449
|
+
}
|
|
1450
|
+
async writeContract(address, abi, functionName, args = [], value) {
|
|
1451
|
+
try {
|
|
1452
|
+
const hash = await this.walletClient.writeContract({
|
|
1453
|
+
account: this.account,
|
|
1454
|
+
chain: this.chain,
|
|
1455
|
+
address,
|
|
1456
|
+
abi,
|
|
1457
|
+
functionName,
|
|
1458
|
+
args,
|
|
1459
|
+
value
|
|
1460
|
+
});
|
|
1461
|
+
return hash;
|
|
1462
|
+
} catch (error) {
|
|
1463
|
+
throw new NodeError(
|
|
1464
|
+
`Failed to write contract: ${error instanceof Error ? error.message : String(error)}`,
|
|
1465
|
+
"CONTRACT_WRITE_ERROR",
|
|
1466
|
+
"evm",
|
|
1467
|
+
{ address, functionName, args, value, originalError: error }
|
|
1468
|
+
);
|
|
1469
|
+
}
|
|
1470
|
+
}
|
|
1471
|
+
/**
|
|
1472
|
+
* Transfer CFX from eSpace to Core Space
|
|
1473
|
+
* Uses the built-in withdrawal mechanism
|
|
1474
|
+
*/
|
|
1475
|
+
async faucetToCore(coreAddress, amount) {
|
|
1476
|
+
if (!coreAddress.startsWith("cfx:") || coreAddress.length < 30) {
|
|
1477
|
+
throw new NodeError(
|
|
1478
|
+
"Invalid Core address format",
|
|
1479
|
+
"INVALID_ADDRESS",
|
|
1480
|
+
"evm",
|
|
1481
|
+
{ coreAddress }
|
|
1482
|
+
);
|
|
1483
|
+
}
|
|
1484
|
+
try {
|
|
1485
|
+
const hash = await this.walletClient.sendTransaction({
|
|
1486
|
+
account: this.account,
|
|
1487
|
+
chain: this.chain,
|
|
1488
|
+
to: "0x0888000000000000000000000000000000000006",
|
|
1489
|
+
// CrossSpaceCall precompiled address
|
|
1490
|
+
value: parseEther(amount),
|
|
1491
|
+
data: encodeFunctionData2({
|
|
1492
|
+
abi: [
|
|
1493
|
+
{
|
|
1494
|
+
type: "function",
|
|
1495
|
+
name: "withdrawFromMapped",
|
|
1496
|
+
inputs: [{ name: "value", type: "uint256" }],
|
|
1497
|
+
outputs: [],
|
|
1498
|
+
stateMutability: "payable"
|
|
1499
|
+
}
|
|
1500
|
+
],
|
|
1501
|
+
functionName: "withdrawFromMapped",
|
|
1502
|
+
args: [parseEther(amount)]
|
|
1503
|
+
})
|
|
1504
|
+
});
|
|
1505
|
+
return hash;
|
|
1506
|
+
} catch (error) {
|
|
1507
|
+
throw new NodeError(
|
|
1508
|
+
`Failed to send faucet transaction to Core: ${error instanceof Error ? error.message : String(error)}`,
|
|
1509
|
+
"FAUCET_ERROR",
|
|
1510
|
+
"evm",
|
|
1511
|
+
{ coreAddress, amount, originalError: error }
|
|
1512
|
+
);
|
|
1513
|
+
}
|
|
1514
|
+
}
|
|
1515
|
+
getInternalClient() {
|
|
1516
|
+
return this.walletClient;
|
|
1517
|
+
}
|
|
1518
|
+
};
|
|
1519
|
+
var EspaceTestClient = class extends EspaceWalletClient {
|
|
1520
|
+
testClient;
|
|
1521
|
+
constructor(config) {
|
|
1522
|
+
super(config);
|
|
1523
|
+
this.testClient = createTestClient2({
|
|
1524
|
+
mode: "anvil",
|
|
1525
|
+
chain: this.chainId === 1030 ? espaceMainnet : espaceTestnet,
|
|
1526
|
+
transport: http2(config.rpcUrl),
|
|
1527
|
+
pollingInterval: config.pollingInterval || 1e3
|
|
1528
|
+
});
|
|
1529
|
+
}
|
|
1530
|
+
async mine(blocks = 1) {
|
|
1531
|
+
try {
|
|
1532
|
+
await this.testClient.mine({ blocks });
|
|
1533
|
+
} catch (error) {
|
|
1534
|
+
throw new NodeError(
|
|
1535
|
+
`Failed to mine blocks: ${error instanceof Error ? error.message : String(error)}`,
|
|
1536
|
+
"MINING_ERROR",
|
|
1537
|
+
"evm",
|
|
1538
|
+
{ blocks, originalError: error }
|
|
1539
|
+
);
|
|
1540
|
+
}
|
|
1541
|
+
}
|
|
1542
|
+
async setNextBlockTimestamp(timestamp) {
|
|
1543
|
+
try {
|
|
1544
|
+
await this.testClient.setNextBlockTimestamp({
|
|
1545
|
+
timestamp: BigInt(timestamp)
|
|
1546
|
+
});
|
|
1547
|
+
} catch (error) {
|
|
1548
|
+
throw new NodeError(
|
|
1549
|
+
`Failed to set next block timestamp: ${error instanceof Error ? error.message : String(error)}`,
|
|
1550
|
+
"TIMESTAMP_ERROR",
|
|
1551
|
+
"evm",
|
|
1552
|
+
{ timestamp, originalError: error }
|
|
1553
|
+
);
|
|
1554
|
+
}
|
|
1555
|
+
}
|
|
1556
|
+
async increaseTime(seconds) {
|
|
1557
|
+
try {
|
|
1558
|
+
await this.testClient.increaseTime({ seconds });
|
|
1559
|
+
} catch (error) {
|
|
1560
|
+
throw new NodeError(
|
|
1561
|
+
`Failed to increase time: ${error instanceof Error ? error.message : String(error)}`,
|
|
1562
|
+
"TIME_INCREASE_ERROR",
|
|
1563
|
+
"evm",
|
|
1564
|
+
{ seconds, originalError: error }
|
|
1565
|
+
);
|
|
1566
|
+
}
|
|
1567
|
+
}
|
|
1568
|
+
async impersonateAccount(address) {
|
|
1569
|
+
try {
|
|
1570
|
+
await this.testClient.impersonateAccount({ address });
|
|
1571
|
+
} catch (error) {
|
|
1572
|
+
throw new NodeError(
|
|
1573
|
+
`Failed to impersonate account: ${error instanceof Error ? error.message : String(error)}`,
|
|
1574
|
+
"IMPERSONATION_ERROR",
|
|
1575
|
+
"evm",
|
|
1576
|
+
{ address, originalError: error }
|
|
1577
|
+
);
|
|
1578
|
+
}
|
|
1579
|
+
}
|
|
1580
|
+
async stopImpersonatingAccount(address) {
|
|
1581
|
+
try {
|
|
1582
|
+
await this.testClient.stopImpersonatingAccount({
|
|
1583
|
+
address
|
|
1584
|
+
});
|
|
1585
|
+
} catch (error) {
|
|
1586
|
+
throw new NodeError(
|
|
1587
|
+
`Failed to stop impersonating account: ${error instanceof Error ? error.message : String(error)}`,
|
|
1588
|
+
"IMPERSONATION_STOP_ERROR",
|
|
1589
|
+
"evm",
|
|
1590
|
+
{ address, originalError: error }
|
|
1591
|
+
);
|
|
1592
|
+
}
|
|
1593
|
+
}
|
|
1594
|
+
async setBalance(address, balance) {
|
|
1595
|
+
try {
|
|
1596
|
+
await this.testClient.setBalance({
|
|
1597
|
+
address,
|
|
1598
|
+
value: balance
|
|
1599
|
+
});
|
|
1600
|
+
} catch (error) {
|
|
1601
|
+
throw new NodeError(
|
|
1602
|
+
`Failed to set balance: ${error instanceof Error ? error.message : String(error)}`,
|
|
1603
|
+
"BALANCE_SET_ERROR",
|
|
1604
|
+
"evm",
|
|
1605
|
+
{ address, balance, originalError: error }
|
|
1606
|
+
);
|
|
1607
|
+
}
|
|
1608
|
+
}
|
|
1609
|
+
async snapshot() {
|
|
1610
|
+
try {
|
|
1611
|
+
const snapshotId = await this.testClient.snapshot();
|
|
1612
|
+
return snapshotId;
|
|
1613
|
+
} catch (error) {
|
|
1614
|
+
throw new NodeError(
|
|
1615
|
+
`Failed to create snapshot: ${error instanceof Error ? error.message : String(error)}`,
|
|
1616
|
+
"SNAPSHOT_ERROR",
|
|
1617
|
+
"evm",
|
|
1618
|
+
{ originalError: error }
|
|
1619
|
+
);
|
|
1620
|
+
}
|
|
1621
|
+
}
|
|
1622
|
+
async revert(snapshotId) {
|
|
1623
|
+
try {
|
|
1624
|
+
await this.testClient.revert({ id: snapshotId });
|
|
1625
|
+
} catch (error) {
|
|
1626
|
+
throw new NodeError(
|
|
1627
|
+
`Failed to revert to snapshot: ${error instanceof Error ? error.message : String(error)}`,
|
|
1628
|
+
"REVERT_ERROR",
|
|
1629
|
+
"evm",
|
|
1630
|
+
{ snapshotId, originalError: error }
|
|
1631
|
+
);
|
|
1632
|
+
}
|
|
1633
|
+
}
|
|
1634
|
+
async getStorageAt(address, slot) {
|
|
1635
|
+
try {
|
|
1636
|
+
const value = await this.publicClient.getStorageAt({
|
|
1637
|
+
address,
|
|
1638
|
+
slot
|
|
1639
|
+
});
|
|
1640
|
+
return value || "0x";
|
|
1641
|
+
} catch (error) {
|
|
1642
|
+
throw new NodeError(
|
|
1643
|
+
`Failed to get storage: ${error instanceof Error ? error.message : String(error)}`,
|
|
1644
|
+
"STORAGE_GET_ERROR",
|
|
1645
|
+
"evm",
|
|
1646
|
+
{ address, slot, originalError: error }
|
|
1647
|
+
);
|
|
1648
|
+
}
|
|
1649
|
+
}
|
|
1650
|
+
async setStorageAt(address, slot, value) {
|
|
1651
|
+
try {
|
|
1652
|
+
await this.testClient.setStorageAt({
|
|
1653
|
+
address,
|
|
1654
|
+
index: slot,
|
|
1655
|
+
value
|
|
1656
|
+
});
|
|
1657
|
+
} catch (error) {
|
|
1658
|
+
throw new NodeError(
|
|
1659
|
+
`Failed to set storage: ${error instanceof Error ? error.message : String(error)}`,
|
|
1660
|
+
"STORAGE_SET_ERROR",
|
|
1661
|
+
"evm",
|
|
1662
|
+
{ address, slot, value, originalError: error }
|
|
1663
|
+
);
|
|
1664
|
+
}
|
|
1665
|
+
}
|
|
1666
|
+
watchTransactions(callback) {
|
|
1667
|
+
const unwatch = this.publicClient.watchPendingTransactions({
|
|
1668
|
+
onTransactions: (hashes) => {
|
|
1669
|
+
for (const hash of hashes) {
|
|
1670
|
+
callback({
|
|
1671
|
+
chainType: "evm",
|
|
1672
|
+
hash,
|
|
1673
|
+
from: "",
|
|
1674
|
+
// Would need to fetch transaction details
|
|
1675
|
+
to: "",
|
|
1676
|
+
// Would need to fetch transaction details
|
|
1677
|
+
value: 0n,
|
|
1678
|
+
// Would need to fetch transaction details
|
|
1679
|
+
blockNumber: 0n
|
|
1680
|
+
// Would need to fetch transaction details
|
|
1681
|
+
});
|
|
1682
|
+
}
|
|
1683
|
+
}
|
|
1684
|
+
});
|
|
1685
|
+
return unwatch;
|
|
1686
|
+
}
|
|
1687
|
+
isValidAddress(address) {
|
|
1688
|
+
return isEvmAddress(address);
|
|
1689
|
+
}
|
|
1690
|
+
async getCurrentEpoch() {
|
|
1691
|
+
return await this.getBlockNumber();
|
|
1692
|
+
}
|
|
1693
|
+
async generateAccounts(count) {
|
|
1694
|
+
const accounts = [];
|
|
1695
|
+
for (let i = 0; i < count; i++) {
|
|
1696
|
+
const privateKey = `0x${"0".repeat(64 - 2)}${i.toString(16).padStart(2, "0")}${"0".repeat(60)}`;
|
|
1697
|
+
const account = privateKeyToAccount2(privateKey);
|
|
1698
|
+
accounts.push(account.address);
|
|
1699
|
+
}
|
|
1700
|
+
return accounts;
|
|
1701
|
+
}
|
|
1702
|
+
};
|
|
1703
|
+
async function createEspaceClient(config) {
|
|
1704
|
+
const chainConfig = getChainConfig(config.chainId);
|
|
1705
|
+
if (chainConfig.type !== "evm") {
|
|
1706
|
+
throw new NodeError(
|
|
1707
|
+
`Invalid chain type for eSpace client: ${chainConfig.type}`,
|
|
1708
|
+
"INVALID_CHAIN_TYPE",
|
|
1709
|
+
"evm"
|
|
1710
|
+
);
|
|
1711
|
+
}
|
|
1712
|
+
const clientConfig = {
|
|
1713
|
+
...config,
|
|
1714
|
+
rpcUrl: config.rpcUrl || chainConfig.rpcUrls.default.http[0] || "http://localhost:8545"
|
|
1715
|
+
};
|
|
1716
|
+
const publicClient = new EspaceClient(clientConfig);
|
|
1717
|
+
let walletClient;
|
|
1718
|
+
let testClient;
|
|
1719
|
+
if (config.account) {
|
|
1720
|
+
let privateKey;
|
|
1721
|
+
if (typeof config.account === "string") {
|
|
1722
|
+
privateKey = config.account;
|
|
1723
|
+
} else {
|
|
1724
|
+
privateKey = config.account.privateKey;
|
|
1725
|
+
}
|
|
1726
|
+
const walletConfig = {
|
|
1727
|
+
...clientConfig,
|
|
1728
|
+
privateKey,
|
|
1729
|
+
accountIndex: typeof config.account === "object" ? config.account.accountIndex : 0
|
|
1730
|
+
};
|
|
1731
|
+
walletClient = new EspaceWalletClient(walletConfig);
|
|
1732
|
+
}
|
|
1733
|
+
if (config.testMode) {
|
|
1734
|
+
let privateKey;
|
|
1735
|
+
if (config.account) {
|
|
1736
|
+
if (typeof config.account === "string") {
|
|
1737
|
+
privateKey = config.account;
|
|
1738
|
+
} else {
|
|
1739
|
+
privateKey = config.account.privateKey;
|
|
1740
|
+
}
|
|
1741
|
+
} else {
|
|
1742
|
+
privateKey = "0x0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef";
|
|
1743
|
+
}
|
|
1744
|
+
const testConfig = {
|
|
1745
|
+
...clientConfig,
|
|
1746
|
+
enableTestMode: true,
|
|
1747
|
+
privateKey
|
|
1748
|
+
};
|
|
1749
|
+
testClient = new EspaceTestClient(testConfig);
|
|
1750
|
+
}
|
|
1751
|
+
return {
|
|
1752
|
+
publicClient,
|
|
1753
|
+
walletClient,
|
|
1754
|
+
testClient
|
|
1755
|
+
};
|
|
1756
|
+
}
|
|
1757
|
+
|
|
1758
|
+
// src/clients/manager.ts
|
|
1759
|
+
import { EventEmitter } from "events";
|
|
1760
|
+
var HEALTH_CHECK_INTERVAL = 3e4;
|
|
1761
|
+
var HEALTH_CHECK_TIMEOUT = 5e3;
|
|
1762
|
+
var ClientManager = class extends EventEmitter {
|
|
1763
|
+
config;
|
|
1764
|
+
coreClient = null;
|
|
1765
|
+
evmClient = null;
|
|
1766
|
+
healthCheckInterval = null;
|
|
1767
|
+
networkSelectorUnsubscribe = null;
|
|
1768
|
+
nodeRunningUnsubscribe = null;
|
|
1769
|
+
initialized = false;
|
|
1770
|
+
constructor(config) {
|
|
1771
|
+
super();
|
|
1772
|
+
this.config = {
|
|
1773
|
+
enableHealthMonitoring: true,
|
|
1774
|
+
healthCheckInterval: HEALTH_CHECK_INTERVAL,
|
|
1775
|
+
healthCheckTimeout: HEALTH_CHECK_TIMEOUT,
|
|
1776
|
+
...config
|
|
1777
|
+
};
|
|
1778
|
+
}
|
|
1779
|
+
/**
|
|
1780
|
+
* Initialize the Client Manager
|
|
1781
|
+
* Sets up clients, server, and monitoring
|
|
1782
|
+
*/
|
|
1783
|
+
async initialize() {
|
|
1784
|
+
if (this.initialized) {
|
|
1785
|
+
return;
|
|
1786
|
+
}
|
|
1787
|
+
try {
|
|
1788
|
+
this.setupNetworkListeners();
|
|
1789
|
+
await this.initializeClients();
|
|
1790
|
+
if (this.config.enableHealthMonitoring) {
|
|
1791
|
+
this.startHealthMonitoring();
|
|
1792
|
+
}
|
|
1793
|
+
this.initialized = true;
|
|
1794
|
+
this.emit("manager:ready");
|
|
1795
|
+
} catch (error) {
|
|
1796
|
+
const managerError = error instanceof Error ? error : new Error(String(error));
|
|
1797
|
+
this.emit("manager:error", { error: managerError });
|
|
1798
|
+
throw managerError;
|
|
1799
|
+
}
|
|
1800
|
+
}
|
|
1801
|
+
/**
|
|
1802
|
+
* Gracefully shutdown the Client Manager
|
|
1803
|
+
*/
|
|
1804
|
+
async shutdown() {
|
|
1805
|
+
if (!this.initialized) {
|
|
1806
|
+
return;
|
|
1807
|
+
}
|
|
1808
|
+
try {
|
|
1809
|
+
this.stopHealthMonitoring();
|
|
1810
|
+
if (this.networkSelectorUnsubscribe) {
|
|
1811
|
+
this.networkSelectorUnsubscribe();
|
|
1812
|
+
this.networkSelectorUnsubscribe = null;
|
|
1813
|
+
}
|
|
1814
|
+
if (this.nodeRunningUnsubscribe) {
|
|
1815
|
+
this.nodeRunningUnsubscribe();
|
|
1816
|
+
this.nodeRunningUnsubscribe = null;
|
|
1817
|
+
}
|
|
1818
|
+
this.coreClient = null;
|
|
1819
|
+
this.evmClient = null;
|
|
1820
|
+
this.initialized = false;
|
|
1821
|
+
this.removeAllListeners();
|
|
1822
|
+
} catch (error) {
|
|
1823
|
+
const shutdownError = error instanceof Error ? error : new Error(String(error));
|
|
1824
|
+
this.emit("manager:error", { error: shutdownError });
|
|
1825
|
+
throw shutdownError;
|
|
1826
|
+
}
|
|
1827
|
+
}
|
|
1828
|
+
/**
|
|
1829
|
+
* Get current Core Space client
|
|
1830
|
+
*/
|
|
1831
|
+
getCoreClient() {
|
|
1832
|
+
return this.coreClient;
|
|
1833
|
+
}
|
|
1834
|
+
/**
|
|
1835
|
+
* Get current eSpace client
|
|
1836
|
+
*/
|
|
1837
|
+
getEvmClient() {
|
|
1838
|
+
return this.evmClient;
|
|
1839
|
+
}
|
|
1840
|
+
/**
|
|
1841
|
+
* Get comprehensive status
|
|
1842
|
+
*/
|
|
1843
|
+
getStatus() {
|
|
1844
|
+
const currentChainId = defaultNetworkSelector.getCurrentChainId();
|
|
1845
|
+
const currentChain = getChainConfig(currentChainId);
|
|
1846
|
+
return {
|
|
1847
|
+
initialized: this.initialized,
|
|
1848
|
+
coreClient: {
|
|
1849
|
+
connected: !!this.coreClient,
|
|
1850
|
+
chainId: currentChain.type === "core" ? currentChainId : defaultNetworkSelector.getCorrespondingChainId() || 1,
|
|
1851
|
+
health: "unknown",
|
|
1852
|
+
// TODO: Implement health status tracking
|
|
1853
|
+
lastHealthCheck: void 0
|
|
1854
|
+
},
|
|
1855
|
+
evmClient: {
|
|
1856
|
+
connected: !!this.evmClient,
|
|
1857
|
+
chainId: currentChain.type === "evm" ? currentChainId : defaultNetworkSelector.getCorrespondingChainId() || 71,
|
|
1858
|
+
health: "unknown",
|
|
1859
|
+
// TODO: Implement health status tracking
|
|
1860
|
+
lastHealthCheck: void 0
|
|
1861
|
+
},
|
|
1862
|
+
networkSelector: {
|
|
1863
|
+
currentChain: currentChainId,
|
|
1864
|
+
isLocalNode: defaultNetworkSelector.getNodeRunningStatus(),
|
|
1865
|
+
lockedToLocal: defaultNetworkSelector.isLockedToLocal()
|
|
1866
|
+
}
|
|
1867
|
+
};
|
|
1868
|
+
}
|
|
1869
|
+
/**
|
|
1870
|
+
* Switch to a specific network
|
|
1871
|
+
* @param chainId - Target chain ID
|
|
1872
|
+
* @param force - Force switch even if node is running (for wallet operations)
|
|
1873
|
+
*/
|
|
1874
|
+
async switchNetwork(chainId, force = false) {
|
|
1875
|
+
if (!isValidChainId(chainId)) {
|
|
1876
|
+
throw new Error(`Invalid chain ID: ${chainId}`);
|
|
1877
|
+
}
|
|
1878
|
+
const previousChainId = defaultNetworkSelector.getCurrentChainId();
|
|
1879
|
+
if (previousChainId === chainId) {
|
|
1880
|
+
return;
|
|
1881
|
+
}
|
|
1882
|
+
defaultNetworkSelector.switchChain(chainId, force);
|
|
1883
|
+
if (defaultNetworkSelector.getCurrentChainId() === chainId) {
|
|
1884
|
+
await this.initializeClients();
|
|
1885
|
+
this.emit("network:switched", { from: previousChainId, to: chainId });
|
|
1886
|
+
}
|
|
1887
|
+
}
|
|
1888
|
+
/**
|
|
1889
|
+
* Initialize or reinitialize client instances based on current network
|
|
1890
|
+
*/
|
|
1891
|
+
async initializeClients() {
|
|
1892
|
+
const currentChainId = defaultNetworkSelector.getCurrentChainId();
|
|
1893
|
+
const currentChain = getChainConfig(currentChainId);
|
|
1894
|
+
try {
|
|
1895
|
+
if (currentChain.type === "core") {
|
|
1896
|
+
const evmChainId = defaultNetworkSelector.getCorrespondingChainId() || 71;
|
|
1897
|
+
this.coreClient = await createCoreClient({
|
|
1898
|
+
...this.config.core,
|
|
1899
|
+
chainId: currentChainId
|
|
1900
|
+
});
|
|
1901
|
+
this.evmClient = await createEspaceClient({
|
|
1902
|
+
...this.config.evm,
|
|
1903
|
+
chainId: evmChainId
|
|
1904
|
+
});
|
|
1905
|
+
} else {
|
|
1906
|
+
const coreChainId = defaultNetworkSelector.getCorrespondingChainId() || 1;
|
|
1907
|
+
this.coreClient = await createCoreClient({
|
|
1908
|
+
...this.config.core,
|
|
1909
|
+
chainId: coreChainId
|
|
1910
|
+
});
|
|
1911
|
+
this.evmClient = await createEspaceClient({
|
|
1912
|
+
...this.config.evm,
|
|
1913
|
+
chainId: currentChainId
|
|
1914
|
+
});
|
|
1915
|
+
}
|
|
1916
|
+
this.emit("client:ready", {
|
|
1917
|
+
type: "core",
|
|
1918
|
+
chainId: this.coreClient.publicClient.chainId
|
|
1919
|
+
});
|
|
1920
|
+
this.emit("client:ready", {
|
|
1921
|
+
type: "evm",
|
|
1922
|
+
chainId: this.evmClient.publicClient.chainId
|
|
1923
|
+
});
|
|
1924
|
+
} catch (error) {
|
|
1925
|
+
const clientError = error instanceof Error ? error : new Error(String(error));
|
|
1926
|
+
this.emit("manager:error", { error: clientError });
|
|
1927
|
+
throw clientError;
|
|
1928
|
+
}
|
|
1929
|
+
}
|
|
1930
|
+
/**
|
|
1931
|
+
* Set up network selector event listeners
|
|
1932
|
+
*/
|
|
1933
|
+
setupNetworkListeners() {
|
|
1934
|
+
this.networkSelectorUnsubscribe = defaultNetworkSelector.onChainChange(
|
|
1935
|
+
async (_chainId) => {
|
|
1936
|
+
try {
|
|
1937
|
+
await this.initializeClients();
|
|
1938
|
+
} catch (error) {
|
|
1939
|
+
const networkError = error instanceof Error ? error : new Error(String(error));
|
|
1940
|
+
this.emit("manager:error", { error: networkError });
|
|
1941
|
+
}
|
|
1942
|
+
}
|
|
1943
|
+
);
|
|
1944
|
+
this.nodeRunningUnsubscribe = defaultNetworkSelector.onNodeRunningChange(
|
|
1945
|
+
async (isRunning) => {
|
|
1946
|
+
if (isRunning) {
|
|
1947
|
+
} else {
|
|
1948
|
+
}
|
|
1949
|
+
}
|
|
1950
|
+
);
|
|
1951
|
+
}
|
|
1952
|
+
/**
|
|
1953
|
+
* Start health monitoring for all clients
|
|
1954
|
+
*/
|
|
1955
|
+
startHealthMonitoring() {
|
|
1956
|
+
if (this.healthCheckInterval) {
|
|
1957
|
+
return;
|
|
1958
|
+
}
|
|
1959
|
+
this.healthCheckInterval = setInterval(async () => {
|
|
1960
|
+
await this.performHealthChecks();
|
|
1961
|
+
}, this.config.healthCheckInterval || HEALTH_CHECK_INTERVAL);
|
|
1962
|
+
setTimeout(() => this.performHealthChecks(), 1e3);
|
|
1963
|
+
}
|
|
1964
|
+
/**
|
|
1965
|
+
* Stop health monitoring
|
|
1966
|
+
*/
|
|
1967
|
+
stopHealthMonitoring() {
|
|
1968
|
+
if (this.healthCheckInterval) {
|
|
1969
|
+
clearInterval(this.healthCheckInterval);
|
|
1970
|
+
this.healthCheckInterval = null;
|
|
1971
|
+
}
|
|
1972
|
+
}
|
|
1973
|
+
/**
|
|
1974
|
+
* Perform health checks on all clients
|
|
1975
|
+
*/
|
|
1976
|
+
async performHealthChecks() {
|
|
1977
|
+
const timeout = this.config.healthCheckTimeout || HEALTH_CHECK_TIMEOUT;
|
|
1978
|
+
if (this.coreClient) {
|
|
1979
|
+
try {
|
|
1980
|
+
const healthPromise = this.checkCoreClientHealth();
|
|
1981
|
+
const result = await Promise.race([
|
|
1982
|
+
healthPromise,
|
|
1983
|
+
new Promise(
|
|
1984
|
+
(_, reject) => setTimeout(() => reject(new Error("Health check timeout")), timeout)
|
|
1985
|
+
)
|
|
1986
|
+
]);
|
|
1987
|
+
this.emit("client:health", {
|
|
1988
|
+
type: "core",
|
|
1989
|
+
chainId: this.coreClient.publicClient.chainId,
|
|
1990
|
+
status: result
|
|
1991
|
+
});
|
|
1992
|
+
} catch (error) {
|
|
1993
|
+
this.emit("client:error", {
|
|
1994
|
+
type: "core",
|
|
1995
|
+
chainId: this.coreClient.publicClient.chainId,
|
|
1996
|
+
error: error instanceof Error ? error : new Error(String(error))
|
|
1997
|
+
});
|
|
1998
|
+
}
|
|
1999
|
+
}
|
|
2000
|
+
if (this.evmClient) {
|
|
2001
|
+
try {
|
|
2002
|
+
const healthPromise = this.checkEvmClientHealth();
|
|
2003
|
+
const result = await Promise.race([
|
|
2004
|
+
healthPromise,
|
|
2005
|
+
new Promise(
|
|
2006
|
+
(_, reject) => setTimeout(() => reject(new Error("Health check timeout")), timeout)
|
|
2007
|
+
)
|
|
2008
|
+
]);
|
|
2009
|
+
this.emit("client:health", {
|
|
2010
|
+
type: "evm",
|
|
2011
|
+
chainId: this.evmClient.publicClient.chainId,
|
|
2012
|
+
status: result
|
|
2013
|
+
});
|
|
2014
|
+
} catch (error) {
|
|
2015
|
+
this.emit("client:error", {
|
|
2016
|
+
type: "evm",
|
|
2017
|
+
chainId: this.evmClient.publicClient.chainId,
|
|
2018
|
+
error: error instanceof Error ? error : new Error(String(error))
|
|
2019
|
+
});
|
|
2020
|
+
}
|
|
2021
|
+
}
|
|
2022
|
+
}
|
|
2023
|
+
/**
|
|
2024
|
+
* Check Core client health
|
|
2025
|
+
*/
|
|
2026
|
+
async checkCoreClientHealth() {
|
|
2027
|
+
if (!this.coreClient) {
|
|
2028
|
+
return "disconnected";
|
|
2029
|
+
}
|
|
2030
|
+
try {
|
|
2031
|
+
await this.coreClient.publicClient.getBlockNumber();
|
|
2032
|
+
return "healthy";
|
|
2033
|
+
} catch (_error) {
|
|
2034
|
+
return "unhealthy";
|
|
2035
|
+
}
|
|
2036
|
+
}
|
|
2037
|
+
/**
|
|
2038
|
+
* Check eSpace client health
|
|
2039
|
+
*/
|
|
2040
|
+
async checkEvmClientHealth() {
|
|
2041
|
+
if (!this.evmClient) {
|
|
2042
|
+
return "disconnected";
|
|
2043
|
+
}
|
|
2044
|
+
try {
|
|
2045
|
+
await this.evmClient.publicClient.getBlockNumber();
|
|
2046
|
+
return "healthy";
|
|
2047
|
+
} catch (_error) {
|
|
2048
|
+
return "unhealthy";
|
|
2049
|
+
}
|
|
2050
|
+
}
|
|
2051
|
+
};
|
|
2052
|
+
|
|
2053
|
+
// src/contracts/abis/erc20.ts
|
|
2054
|
+
var ERC20_ABI = [
|
|
2055
|
+
// Read functions
|
|
2056
|
+
{
|
|
2057
|
+
inputs: [],
|
|
2058
|
+
name: "name",
|
|
2059
|
+
outputs: [{ name: "", type: "string" }],
|
|
2060
|
+
stateMutability: "view",
|
|
2061
|
+
type: "function"
|
|
2062
|
+
},
|
|
2063
|
+
{
|
|
2064
|
+
inputs: [],
|
|
2065
|
+
name: "symbol",
|
|
2066
|
+
outputs: [{ name: "", type: "string" }],
|
|
2067
|
+
stateMutability: "view",
|
|
2068
|
+
type: "function"
|
|
2069
|
+
},
|
|
2070
|
+
{
|
|
2071
|
+
inputs: [],
|
|
2072
|
+
name: "decimals",
|
|
2073
|
+
outputs: [{ name: "", type: "uint8" }],
|
|
2074
|
+
stateMutability: "view",
|
|
2075
|
+
type: "function"
|
|
2076
|
+
},
|
|
2077
|
+
{
|
|
2078
|
+
inputs: [],
|
|
2079
|
+
name: "totalSupply",
|
|
2080
|
+
outputs: [{ name: "", type: "uint256" }],
|
|
2081
|
+
stateMutability: "view",
|
|
2082
|
+
type: "function"
|
|
2083
|
+
},
|
|
2084
|
+
{
|
|
2085
|
+
inputs: [{ name: "account", type: "address" }],
|
|
2086
|
+
name: "balanceOf",
|
|
2087
|
+
outputs: [{ name: "", type: "uint256" }],
|
|
2088
|
+
stateMutability: "view",
|
|
2089
|
+
type: "function"
|
|
2090
|
+
},
|
|
2091
|
+
{
|
|
2092
|
+
inputs: [
|
|
2093
|
+
{ name: "owner", type: "address" },
|
|
2094
|
+
{ name: "spender", type: "address" }
|
|
2095
|
+
],
|
|
2096
|
+
name: "allowance",
|
|
2097
|
+
outputs: [{ name: "", type: "uint256" }],
|
|
2098
|
+
stateMutability: "view",
|
|
2099
|
+
type: "function"
|
|
2100
|
+
},
|
|
2101
|
+
// Write functions
|
|
2102
|
+
{
|
|
2103
|
+
inputs: [
|
|
2104
|
+
{ name: "to", type: "address" },
|
|
2105
|
+
{ name: "amount", type: "uint256" }
|
|
2106
|
+
],
|
|
2107
|
+
name: "transfer",
|
|
2108
|
+
outputs: [{ name: "", type: "bool" }],
|
|
2109
|
+
stateMutability: "nonpayable",
|
|
2110
|
+
type: "function"
|
|
2111
|
+
},
|
|
2112
|
+
{
|
|
2113
|
+
inputs: [
|
|
2114
|
+
{ name: "spender", type: "address" },
|
|
2115
|
+
{ name: "amount", type: "uint256" }
|
|
2116
|
+
],
|
|
2117
|
+
name: "approve",
|
|
2118
|
+
outputs: [{ name: "", type: "bool" }],
|
|
2119
|
+
stateMutability: "nonpayable",
|
|
2120
|
+
type: "function"
|
|
2121
|
+
},
|
|
2122
|
+
{
|
|
2123
|
+
inputs: [
|
|
2124
|
+
{ name: "from", type: "address" },
|
|
2125
|
+
{ name: "to", type: "address" },
|
|
2126
|
+
{ name: "amount", type: "uint256" }
|
|
2127
|
+
],
|
|
2128
|
+
name: "transferFrom",
|
|
2129
|
+
outputs: [{ name: "", type: "bool" }],
|
|
2130
|
+
stateMutability: "nonpayable",
|
|
2131
|
+
type: "function"
|
|
2132
|
+
},
|
|
2133
|
+
// Events
|
|
2134
|
+
{
|
|
2135
|
+
anonymous: false,
|
|
2136
|
+
inputs: [
|
|
2137
|
+
{ indexed: true, name: "from", type: "address" },
|
|
2138
|
+
{ indexed: true, name: "to", type: "address" },
|
|
2139
|
+
{ indexed: false, name: "value", type: "uint256" }
|
|
2140
|
+
],
|
|
2141
|
+
name: "Transfer",
|
|
2142
|
+
type: "event"
|
|
2143
|
+
},
|
|
2144
|
+
{
|
|
2145
|
+
anonymous: false,
|
|
2146
|
+
inputs: [
|
|
2147
|
+
{ indexed: true, name: "owner", type: "address" },
|
|
2148
|
+
{ indexed: true, name: "spender", type: "address" },
|
|
2149
|
+
{ indexed: false, name: "value", type: "uint256" }
|
|
2150
|
+
],
|
|
2151
|
+
name: "Approval",
|
|
2152
|
+
type: "event"
|
|
2153
|
+
}
|
|
2154
|
+
];
|
|
2155
|
+
|
|
2156
|
+
// src/contracts/abis/erc721.ts
|
|
2157
|
+
var ERC721_ABI = [
|
|
2158
|
+
// Read functions
|
|
2159
|
+
{
|
|
2160
|
+
inputs: [],
|
|
2161
|
+
name: "name",
|
|
2162
|
+
outputs: [{ name: "", type: "string" }],
|
|
2163
|
+
stateMutability: "view",
|
|
2164
|
+
type: "function"
|
|
2165
|
+
},
|
|
2166
|
+
{
|
|
2167
|
+
inputs: [],
|
|
2168
|
+
name: "symbol",
|
|
2169
|
+
outputs: [{ name: "", type: "string" }],
|
|
2170
|
+
stateMutability: "view",
|
|
2171
|
+
type: "function"
|
|
2172
|
+
},
|
|
2173
|
+
{
|
|
2174
|
+
inputs: [{ name: "tokenId", type: "uint256" }],
|
|
2175
|
+
name: "tokenURI",
|
|
2176
|
+
outputs: [{ name: "", type: "string" }],
|
|
2177
|
+
stateMutability: "view",
|
|
2178
|
+
type: "function"
|
|
2179
|
+
},
|
|
2180
|
+
{
|
|
2181
|
+
inputs: [{ name: "owner", type: "address" }],
|
|
2182
|
+
name: "balanceOf",
|
|
2183
|
+
outputs: [{ name: "", type: "uint256" }],
|
|
2184
|
+
stateMutability: "view",
|
|
2185
|
+
type: "function"
|
|
2186
|
+
},
|
|
2187
|
+
{
|
|
2188
|
+
inputs: [{ name: "tokenId", type: "uint256" }],
|
|
2189
|
+
name: "ownerOf",
|
|
2190
|
+
outputs: [{ name: "", type: "address" }],
|
|
2191
|
+
stateMutability: "view",
|
|
2192
|
+
type: "function"
|
|
2193
|
+
},
|
|
2194
|
+
{
|
|
2195
|
+
inputs: [
|
|
2196
|
+
{ name: "owner", type: "address" },
|
|
2197
|
+
{ name: "operator", type: "address" }
|
|
2198
|
+
],
|
|
2199
|
+
name: "isApprovedForAll",
|
|
2200
|
+
outputs: [{ name: "", type: "bool" }],
|
|
2201
|
+
stateMutability: "view",
|
|
2202
|
+
type: "function"
|
|
2203
|
+
},
|
|
2204
|
+
{
|
|
2205
|
+
inputs: [{ name: "tokenId", type: "uint256" }],
|
|
2206
|
+
name: "getApproved",
|
|
2207
|
+
outputs: [{ name: "", type: "address" }],
|
|
2208
|
+
stateMutability: "view",
|
|
2209
|
+
type: "function"
|
|
2210
|
+
},
|
|
2211
|
+
{
|
|
2212
|
+
inputs: [{ name: "interfaceId", type: "bytes4" }],
|
|
2213
|
+
name: "supportsInterface",
|
|
2214
|
+
outputs: [{ name: "", type: "bool" }],
|
|
2215
|
+
stateMutability: "view",
|
|
2216
|
+
type: "function"
|
|
2217
|
+
},
|
|
2218
|
+
// Write functions
|
|
2219
|
+
{
|
|
2220
|
+
inputs: [
|
|
2221
|
+
{ name: "from", type: "address" },
|
|
2222
|
+
{ name: "to", type: "address" },
|
|
2223
|
+
{ name: "tokenId", type: "uint256" }
|
|
2224
|
+
],
|
|
2225
|
+
name: "transferFrom",
|
|
2226
|
+
outputs: [],
|
|
2227
|
+
stateMutability: "nonpayable",
|
|
2228
|
+
type: "function"
|
|
2229
|
+
},
|
|
2230
|
+
{
|
|
2231
|
+
inputs: [
|
|
2232
|
+
{ name: "from", type: "address" },
|
|
2233
|
+
{ name: "to", type: "address" },
|
|
2234
|
+
{ name: "tokenId", type: "uint256" }
|
|
2235
|
+
],
|
|
2236
|
+
name: "safeTransferFrom",
|
|
2237
|
+
outputs: [],
|
|
2238
|
+
stateMutability: "nonpayable",
|
|
2239
|
+
type: "function"
|
|
2240
|
+
},
|
|
2241
|
+
{
|
|
2242
|
+
inputs: [
|
|
2243
|
+
{ name: "from", type: "address" },
|
|
2244
|
+
{ name: "to", type: "address" },
|
|
2245
|
+
{ name: "tokenId", type: "uint256" },
|
|
2246
|
+
{ name: "data", type: "bytes" }
|
|
2247
|
+
],
|
|
2248
|
+
name: "safeTransferFrom",
|
|
2249
|
+
outputs: [],
|
|
2250
|
+
stateMutability: "nonpayable",
|
|
2251
|
+
type: "function"
|
|
2252
|
+
},
|
|
2253
|
+
{
|
|
2254
|
+
inputs: [
|
|
2255
|
+
{ name: "to", type: "address" },
|
|
2256
|
+
{ name: "tokenId", type: "uint256" }
|
|
2257
|
+
],
|
|
2258
|
+
name: "approve",
|
|
2259
|
+
outputs: [],
|
|
2260
|
+
stateMutability: "nonpayable",
|
|
2261
|
+
type: "function"
|
|
2262
|
+
},
|
|
2263
|
+
{
|
|
2264
|
+
inputs: [
|
|
2265
|
+
{ name: "operator", type: "address" },
|
|
2266
|
+
{ name: "approved", type: "bool" }
|
|
2267
|
+
],
|
|
2268
|
+
name: "setApprovalForAll",
|
|
2269
|
+
outputs: [],
|
|
2270
|
+
stateMutability: "nonpayable",
|
|
2271
|
+
type: "function"
|
|
2272
|
+
},
|
|
2273
|
+
// Events
|
|
2274
|
+
{
|
|
2275
|
+
anonymous: false,
|
|
2276
|
+
inputs: [
|
|
2277
|
+
{ indexed: true, name: "from", type: "address" },
|
|
2278
|
+
{ indexed: true, name: "to", type: "address" },
|
|
2279
|
+
{ indexed: true, name: "tokenId", type: "uint256" }
|
|
2280
|
+
],
|
|
2281
|
+
name: "Transfer",
|
|
2282
|
+
type: "event"
|
|
2283
|
+
},
|
|
2284
|
+
{
|
|
2285
|
+
anonymous: false,
|
|
2286
|
+
inputs: [
|
|
2287
|
+
{ indexed: true, name: "owner", type: "address" },
|
|
2288
|
+
{ indexed: true, name: "approved", type: "address" },
|
|
2289
|
+
{ indexed: true, name: "tokenId", type: "uint256" }
|
|
2290
|
+
],
|
|
2291
|
+
name: "Approval",
|
|
2292
|
+
type: "event"
|
|
2293
|
+
},
|
|
2294
|
+
{
|
|
2295
|
+
anonymous: false,
|
|
2296
|
+
inputs: [
|
|
2297
|
+
{ indexed: true, name: "owner", type: "address" },
|
|
2298
|
+
{ indexed: true, name: "operator", type: "address" },
|
|
2299
|
+
{ indexed: false, name: "approved", type: "bool" }
|
|
2300
|
+
],
|
|
2301
|
+
name: "ApprovalForAll",
|
|
2302
|
+
type: "event"
|
|
2303
|
+
}
|
|
2304
|
+
];
|
|
2305
|
+
|
|
2306
|
+
// src/contracts/abis/erc1155.ts
|
|
2307
|
+
var ERC1155_ABI = [
|
|
2308
|
+
// Read functions
|
|
2309
|
+
{
|
|
2310
|
+
inputs: [{ name: "id", type: "uint256" }],
|
|
2311
|
+
name: "uri",
|
|
2312
|
+
outputs: [{ name: "", type: "string" }],
|
|
2313
|
+
stateMutability: "view",
|
|
2314
|
+
type: "function"
|
|
2315
|
+
},
|
|
2316
|
+
{
|
|
2317
|
+
inputs: [
|
|
2318
|
+
{ name: "account", type: "address" },
|
|
2319
|
+
{ name: "id", type: "uint256" }
|
|
2320
|
+
],
|
|
2321
|
+
name: "balanceOf",
|
|
2322
|
+
outputs: [{ name: "", type: "uint256" }],
|
|
2323
|
+
stateMutability: "view",
|
|
2324
|
+
type: "function"
|
|
2325
|
+
},
|
|
2326
|
+
{
|
|
2327
|
+
inputs: [
|
|
2328
|
+
{ name: "accounts", type: "address[]" },
|
|
2329
|
+
{ name: "ids", type: "uint256[]" }
|
|
2330
|
+
],
|
|
2331
|
+
name: "balanceOfBatch",
|
|
2332
|
+
outputs: [{ name: "", type: "uint256[]" }],
|
|
2333
|
+
stateMutability: "view",
|
|
2334
|
+
type: "function"
|
|
2335
|
+
},
|
|
2336
|
+
{
|
|
2337
|
+
inputs: [
|
|
2338
|
+
{ name: "account", type: "address" },
|
|
2339
|
+
{ name: "operator", type: "address" }
|
|
2340
|
+
],
|
|
2341
|
+
name: "isApprovedForAll",
|
|
2342
|
+
outputs: [{ name: "", type: "bool" }],
|
|
2343
|
+
stateMutability: "view",
|
|
2344
|
+
type: "function"
|
|
2345
|
+
},
|
|
2346
|
+
{
|
|
2347
|
+
inputs: [{ name: "interfaceId", type: "bytes4" }],
|
|
2348
|
+
name: "supportsInterface",
|
|
2349
|
+
outputs: [{ name: "", type: "bool" }],
|
|
2350
|
+
stateMutability: "view",
|
|
2351
|
+
type: "function"
|
|
2352
|
+
},
|
|
2353
|
+
// Write functions
|
|
2354
|
+
{
|
|
2355
|
+
inputs: [
|
|
2356
|
+
{ name: "operator", type: "address" },
|
|
2357
|
+
{ name: "approved", type: "bool" }
|
|
2358
|
+
],
|
|
2359
|
+
name: "setApprovalForAll",
|
|
2360
|
+
outputs: [],
|
|
2361
|
+
stateMutability: "nonpayable",
|
|
2362
|
+
type: "function"
|
|
2363
|
+
},
|
|
2364
|
+
{
|
|
2365
|
+
inputs: [
|
|
2366
|
+
{ name: "from", type: "address" },
|
|
2367
|
+
{ name: "to", type: "address" },
|
|
2368
|
+
{ name: "id", type: "uint256" },
|
|
2369
|
+
{ name: "amount", type: "uint256" },
|
|
2370
|
+
{ name: "data", type: "bytes" }
|
|
2371
|
+
],
|
|
2372
|
+
name: "safeTransferFrom",
|
|
2373
|
+
outputs: [],
|
|
2374
|
+
stateMutability: "nonpayable",
|
|
2375
|
+
type: "function"
|
|
2376
|
+
},
|
|
2377
|
+
{
|
|
2378
|
+
inputs: [
|
|
2379
|
+
{ name: "from", type: "address" },
|
|
2380
|
+
{ name: "to", type: "address" },
|
|
2381
|
+
{ name: "ids", type: "uint256[]" },
|
|
2382
|
+
{ name: "amounts", type: "uint256[]" },
|
|
2383
|
+
{ name: "data", type: "bytes" }
|
|
2384
|
+
],
|
|
2385
|
+
name: "safeBatchTransferFrom",
|
|
2386
|
+
outputs: [],
|
|
2387
|
+
stateMutability: "nonpayable",
|
|
2388
|
+
type: "function"
|
|
2389
|
+
},
|
|
2390
|
+
// Events
|
|
2391
|
+
{
|
|
2392
|
+
anonymous: false,
|
|
2393
|
+
inputs: [
|
|
2394
|
+
{ indexed: true, name: "operator", type: "address" },
|
|
2395
|
+
{ indexed: true, name: "from", type: "address" },
|
|
2396
|
+
{ indexed: true, name: "to", type: "address" },
|
|
2397
|
+
{ indexed: false, name: "id", type: "uint256" },
|
|
2398
|
+
{ indexed: false, name: "value", type: "uint256" }
|
|
2399
|
+
],
|
|
2400
|
+
name: "TransferSingle",
|
|
2401
|
+
type: "event"
|
|
2402
|
+
},
|
|
2403
|
+
{
|
|
2404
|
+
anonymous: false,
|
|
2405
|
+
inputs: [
|
|
2406
|
+
{ indexed: true, name: "operator", type: "address" },
|
|
2407
|
+
{ indexed: true, name: "from", type: "address" },
|
|
2408
|
+
{ indexed: true, name: "to", type: "address" },
|
|
2409
|
+
{ indexed: false, name: "ids", type: "uint256[]" },
|
|
2410
|
+
{ indexed: false, name: "values", type: "uint256[]" }
|
|
2411
|
+
],
|
|
2412
|
+
name: "TransferBatch",
|
|
2413
|
+
type: "event"
|
|
2414
|
+
},
|
|
2415
|
+
{
|
|
2416
|
+
anonymous: false,
|
|
2417
|
+
inputs: [
|
|
2418
|
+
{ indexed: true, name: "account", type: "address" },
|
|
2419
|
+
{ indexed: true, name: "operator", type: "address" },
|
|
2420
|
+
{ indexed: false, name: "approved", type: "bool" }
|
|
2421
|
+
],
|
|
2422
|
+
name: "ApprovalForAll",
|
|
2423
|
+
type: "event"
|
|
2424
|
+
},
|
|
2425
|
+
{
|
|
2426
|
+
anonymous: false,
|
|
2427
|
+
inputs: [
|
|
2428
|
+
{ indexed: false, name: "value", type: "string" },
|
|
2429
|
+
{ indexed: true, name: "id", type: "uint256" }
|
|
2430
|
+
],
|
|
2431
|
+
name: "URI",
|
|
2432
|
+
type: "event"
|
|
2433
|
+
}
|
|
2434
|
+
];
|
|
2435
|
+
|
|
2436
|
+
// src/contracts/types/index.ts
|
|
2437
|
+
var ContractError = class extends Error {
|
|
2438
|
+
constructor(message, code, context) {
|
|
2439
|
+
super(message);
|
|
2440
|
+
this.code = code;
|
|
2441
|
+
this.context = context;
|
|
2442
|
+
this.name = "ContractError";
|
|
2443
|
+
}
|
|
2444
|
+
};
|
|
2445
|
+
var DeploymentError = class extends ContractError {
|
|
2446
|
+
constructor(message, context) {
|
|
2447
|
+
super(message, "DEPLOYMENT_ERROR", context);
|
|
2448
|
+
this.name = "DeploymentError";
|
|
2449
|
+
}
|
|
2450
|
+
};
|
|
2451
|
+
var InteractionError = class extends ContractError {
|
|
2452
|
+
constructor(message, context) {
|
|
2453
|
+
super(message, "INTERACTION_ERROR", context);
|
|
2454
|
+
this.name = "InteractionError";
|
|
2455
|
+
}
|
|
2456
|
+
};
|
|
2457
|
+
|
|
2458
|
+
// src/contracts/deployer/deploy.ts
|
|
2459
|
+
var ContractDeployer = class {
|
|
2460
|
+
// biome-ignore lint/correctness/noUnusedPrivateClassMembers: reserved for production implementation
|
|
2461
|
+
constructor(clientManager) {
|
|
2462
|
+
this.clientManager = clientManager;
|
|
2463
|
+
}
|
|
2464
|
+
/**
|
|
2465
|
+
* Deploy contract to a single chain
|
|
2466
|
+
*
|
|
2467
|
+
* @param options - Deployment configuration
|
|
2468
|
+
* @returns Deployment result
|
|
2469
|
+
*/
|
|
2470
|
+
async deploy(options) {
|
|
2471
|
+
try {
|
|
2472
|
+
if (options.chain === "core") {
|
|
2473
|
+
return await this.deployToCore(options);
|
|
2474
|
+
}
|
|
2475
|
+
return await this.deployToEvm(options);
|
|
2476
|
+
} catch (error) {
|
|
2477
|
+
throw new DeploymentError(
|
|
2478
|
+
`Failed to deploy contract to ${options.chain}`,
|
|
2479
|
+
{
|
|
2480
|
+
chain: options.chain,
|
|
2481
|
+
error: error instanceof Error ? error.message : "Unknown error"
|
|
2482
|
+
}
|
|
2483
|
+
);
|
|
2484
|
+
}
|
|
2485
|
+
}
|
|
2486
|
+
/**
|
|
2487
|
+
* Deploy contract to multiple chains
|
|
2488
|
+
*
|
|
2489
|
+
* @param options - Multi-chain deployment configuration
|
|
2490
|
+
* @returns Multi-chain deployment results
|
|
2491
|
+
*/
|
|
2492
|
+
async deployToMultipleChains(options) {
|
|
2493
|
+
const results = {
|
|
2494
|
+
successCount: 0,
|
|
2495
|
+
failureCount: 0
|
|
2496
|
+
};
|
|
2497
|
+
for (const chain of options.chains) {
|
|
2498
|
+
try {
|
|
2499
|
+
const result = await this.deploy({
|
|
2500
|
+
bytecode: options.bytecode,
|
|
2501
|
+
abi: options.abi,
|
|
2502
|
+
args: options.args,
|
|
2503
|
+
chain,
|
|
2504
|
+
value: options.value
|
|
2505
|
+
});
|
|
2506
|
+
if (chain === "core") {
|
|
2507
|
+
results.core = result;
|
|
2508
|
+
} else {
|
|
2509
|
+
results.evm = result;
|
|
2510
|
+
}
|
|
2511
|
+
results.successCount++;
|
|
2512
|
+
} catch (error) {
|
|
2513
|
+
results.failureCount++;
|
|
2514
|
+
console.error(`Failed to deploy to ${chain}:`, error);
|
|
2515
|
+
}
|
|
2516
|
+
}
|
|
2517
|
+
return results;
|
|
2518
|
+
}
|
|
2519
|
+
/**
|
|
2520
|
+
* Deploy contract to Core Space (Conflux native)
|
|
2521
|
+
*/
|
|
2522
|
+
async deployToCore(_options) {
|
|
2523
|
+
const address = `cfx:${Array.from(
|
|
2524
|
+
{ length: 40 },
|
|
2525
|
+
() => Math.floor(Math.random() * 16).toString(16)
|
|
2526
|
+
).join("")}`;
|
|
2527
|
+
const transactionHash = `0x${Array.from(
|
|
2528
|
+
{ length: 64 },
|
|
2529
|
+
() => Math.floor(Math.random() * 16).toString(16)
|
|
2530
|
+
).join("")}`;
|
|
2531
|
+
return {
|
|
2532
|
+
address,
|
|
2533
|
+
transactionHash,
|
|
2534
|
+
blockNumber: 1000n,
|
|
2535
|
+
deployer: "cfx:deployer...",
|
|
2536
|
+
chain: "core",
|
|
2537
|
+
deployedAt: /* @__PURE__ */ new Date(),
|
|
2538
|
+
gasUsed: 500000n
|
|
2539
|
+
};
|
|
2540
|
+
}
|
|
2541
|
+
/**
|
|
2542
|
+
* Deploy contract to eSpace (EVM-compatible)
|
|
2543
|
+
*/
|
|
2544
|
+
async deployToEvm(_options) {
|
|
2545
|
+
const address = `0x${Array.from(
|
|
2546
|
+
{ length: 40 },
|
|
2547
|
+
() => Math.floor(Math.random() * 16).toString(16)
|
|
2548
|
+
).join("")}`;
|
|
2549
|
+
const transactionHash = `0x${Array.from(
|
|
2550
|
+
{ length: 64 },
|
|
2551
|
+
() => Math.floor(Math.random() * 16).toString(16)
|
|
2552
|
+
).join("")}`;
|
|
2553
|
+
return {
|
|
2554
|
+
address,
|
|
2555
|
+
transactionHash,
|
|
2556
|
+
blockNumber: 2000n,
|
|
2557
|
+
deployer: "0xdeployer...",
|
|
2558
|
+
chain: "evm",
|
|
2559
|
+
deployedAt: /* @__PURE__ */ new Date(),
|
|
2560
|
+
gasUsed: 450000n
|
|
2561
|
+
};
|
|
2562
|
+
}
|
|
2563
|
+
/**
|
|
2564
|
+
* Estimate deployment gas
|
|
2565
|
+
*
|
|
2566
|
+
* @param options - Deployment configuration
|
|
2567
|
+
* @returns Estimated gas
|
|
2568
|
+
*/
|
|
2569
|
+
async estimateDeploymentGas(options) {
|
|
2570
|
+
const baseGas = 21000n;
|
|
2571
|
+
const bytecodeGas = BigInt(options.bytecode.length / 2) * 200n;
|
|
2572
|
+
const argsGas = BigInt((options.args?.length || 0) * 1e4);
|
|
2573
|
+
return baseGas + bytecodeGas + argsGas;
|
|
2574
|
+
}
|
|
2575
|
+
/**
|
|
2576
|
+
* Verify contract bytecode matches deployed contract
|
|
2577
|
+
*
|
|
2578
|
+
* @param address - Contract address
|
|
2579
|
+
* @param expectedBytecode - Expected bytecode
|
|
2580
|
+
* @param chain - Chain type
|
|
2581
|
+
* @returns true if verified
|
|
2582
|
+
*/
|
|
2583
|
+
async verifyBytecode(_address, _expectedBytecode, _chain) {
|
|
2584
|
+
try {
|
|
2585
|
+
return true;
|
|
2586
|
+
} catch (_error) {
|
|
2587
|
+
return false;
|
|
2588
|
+
}
|
|
2589
|
+
}
|
|
2590
|
+
};
|
|
2591
|
+
|
|
2592
|
+
// src/contracts/interaction/reader.ts
|
|
2593
|
+
var ContractReader = class {
|
|
2594
|
+
constructor(clientManager) {
|
|
2595
|
+
this.clientManager = clientManager;
|
|
2596
|
+
}
|
|
2597
|
+
/**
|
|
2598
|
+
* Read data from contract
|
|
2599
|
+
*
|
|
2600
|
+
* @param options - Read configuration
|
|
2601
|
+
* @returns Function return value
|
|
2602
|
+
*/
|
|
2603
|
+
async read(options) {
|
|
2604
|
+
try {
|
|
2605
|
+
if (options.chain === "core") {
|
|
2606
|
+
return await this.readFromCore(options);
|
|
2607
|
+
}
|
|
2608
|
+
return await this.readFromEvm(options);
|
|
2609
|
+
} catch (error) {
|
|
2610
|
+
throw new InteractionError(
|
|
2611
|
+
`Failed to read from contract on ${options.chain}`,
|
|
2612
|
+
{
|
|
2613
|
+
address: options.address,
|
|
2614
|
+
functionName: options.functionName,
|
|
2615
|
+
chain: options.chain,
|
|
2616
|
+
error: error instanceof Error ? error.message : "Unknown error"
|
|
2617
|
+
}
|
|
2618
|
+
);
|
|
2619
|
+
}
|
|
2620
|
+
}
|
|
2621
|
+
/**
|
|
2622
|
+
* Batch read multiple values from same contract
|
|
2623
|
+
*
|
|
2624
|
+
* @param address - Contract address
|
|
2625
|
+
* @param abi - Contract ABI
|
|
2626
|
+
* @param calls - Array of function calls
|
|
2627
|
+
* @param chain - Chain type
|
|
2628
|
+
* @returns Array of results
|
|
2629
|
+
*/
|
|
2630
|
+
async batchRead(address, abi, calls, chain) {
|
|
2631
|
+
const results = [];
|
|
2632
|
+
for (const call of calls) {
|
|
2633
|
+
const result = await this.read({
|
|
2634
|
+
address,
|
|
2635
|
+
abi,
|
|
2636
|
+
functionName: call.functionName,
|
|
2637
|
+
args: call.args,
|
|
2638
|
+
chain
|
|
2639
|
+
});
|
|
2640
|
+
results.push(result);
|
|
2641
|
+
}
|
|
2642
|
+
return results;
|
|
2643
|
+
}
|
|
2644
|
+
/**
|
|
2645
|
+
* Get contract information
|
|
2646
|
+
*
|
|
2647
|
+
* @param address - Contract address
|
|
2648
|
+
* @param chain - Chain type
|
|
2649
|
+
* @returns Contract info
|
|
2650
|
+
*/
|
|
2651
|
+
async getContractInfo(address, chain) {
|
|
2652
|
+
return {
|
|
2653
|
+
address,
|
|
2654
|
+
bytecode: "0x...",
|
|
2655
|
+
chain,
|
|
2656
|
+
isVerified: false
|
|
2657
|
+
};
|
|
2658
|
+
}
|
|
2659
|
+
/**
|
|
2660
|
+
* Check if address is a contract
|
|
2661
|
+
*
|
|
2662
|
+
* @param address - Address to check
|
|
2663
|
+
* @param chain - Chain type
|
|
2664
|
+
* @returns true if contract exists
|
|
2665
|
+
*/
|
|
2666
|
+
async isContract(_address, _chain) {
|
|
2667
|
+
try {
|
|
2668
|
+
return true;
|
|
2669
|
+
} catch {
|
|
2670
|
+
return false;
|
|
2671
|
+
}
|
|
2672
|
+
}
|
|
2673
|
+
/**
|
|
2674
|
+
* Read from Core Space contract
|
|
2675
|
+
*/
|
|
2676
|
+
async readFromCore(_options) {
|
|
2677
|
+
const _coreClient = this.clientManager.getCoreClient();
|
|
2678
|
+
return {};
|
|
2679
|
+
}
|
|
2680
|
+
/**
|
|
2681
|
+
* Read from eSpace contract
|
|
2682
|
+
*/
|
|
2683
|
+
async readFromEvm(_options) {
|
|
2684
|
+
const _evmClient = this.clientManager.getEvmClient();
|
|
2685
|
+
return {};
|
|
2686
|
+
}
|
|
2687
|
+
};
|
|
2688
|
+
|
|
2689
|
+
// src/contracts/interaction/writer.ts
|
|
2690
|
+
var ContractWriter = class {
|
|
2691
|
+
constructor(clientManager) {
|
|
2692
|
+
this.clientManager = clientManager;
|
|
2693
|
+
}
|
|
2694
|
+
/**
|
|
2695
|
+
* Write to contract (state-changing operation)
|
|
2696
|
+
*
|
|
2697
|
+
* @param options - Write configuration
|
|
2698
|
+
* @returns Write result with transaction info
|
|
2699
|
+
*/
|
|
2700
|
+
async write(options) {
|
|
2701
|
+
try {
|
|
2702
|
+
if (options.chain === "core") {
|
|
2703
|
+
return await this.writeToCore(options);
|
|
2704
|
+
}
|
|
2705
|
+
return await this.writeToEvm(options);
|
|
2706
|
+
} catch (error) {
|
|
2707
|
+
throw new InteractionError(
|
|
2708
|
+
`Failed to write to contract on ${options.chain}`,
|
|
2709
|
+
{
|
|
2710
|
+
address: options.address,
|
|
2711
|
+
functionName: options.functionName,
|
|
2712
|
+
chain: options.chain,
|
|
2713
|
+
error: error instanceof Error ? error.message : "Unknown error"
|
|
2714
|
+
}
|
|
2715
|
+
);
|
|
2716
|
+
}
|
|
2717
|
+
}
|
|
2718
|
+
/**
|
|
2719
|
+
* Estimate gas for contract write
|
|
2720
|
+
*
|
|
2721
|
+
* @param options - Write configuration
|
|
2722
|
+
* @returns Estimated gas
|
|
2723
|
+
*/
|
|
2724
|
+
async estimateGas(options) {
|
|
2725
|
+
try {
|
|
2726
|
+
if (options.chain === "core") {
|
|
2727
|
+
return 100000n;
|
|
2728
|
+
}
|
|
2729
|
+
return 80000n;
|
|
2730
|
+
} catch (error) {
|
|
2731
|
+
throw new InteractionError("Failed to estimate gas", {
|
|
2732
|
+
address: options.address,
|
|
2733
|
+
functionName: options.functionName,
|
|
2734
|
+
chain: options.chain,
|
|
2735
|
+
error: error instanceof Error ? error.message : "Unknown error"
|
|
2736
|
+
});
|
|
2737
|
+
}
|
|
2738
|
+
}
|
|
2739
|
+
/**
|
|
2740
|
+
* Simulate contract write without sending transaction
|
|
2741
|
+
*
|
|
2742
|
+
* @param options - Write configuration
|
|
2743
|
+
* @returns Simulation result
|
|
2744
|
+
*/
|
|
2745
|
+
async simulate(options) {
|
|
2746
|
+
try {
|
|
2747
|
+
if (options.chain === "core") {
|
|
2748
|
+
return await this.simulateCore(options);
|
|
2749
|
+
}
|
|
2750
|
+
return await this.simulateEvm(options);
|
|
2751
|
+
} catch (error) {
|
|
2752
|
+
throw new InteractionError("Failed to simulate transaction", {
|
|
2753
|
+
address: options.address,
|
|
2754
|
+
functionName: options.functionName,
|
|
2755
|
+
chain: options.chain,
|
|
2756
|
+
error: error instanceof Error ? error.message : "Unknown error"
|
|
2757
|
+
});
|
|
2758
|
+
}
|
|
2759
|
+
}
|
|
2760
|
+
/**
|
|
2761
|
+
* Write to Core Space contract
|
|
2762
|
+
*/
|
|
2763
|
+
async writeToCore(options) {
|
|
2764
|
+
const _coreClient = this.clientManager.getCoreClient();
|
|
2765
|
+
const hash = `0x${Array.from(
|
|
2766
|
+
{ length: 64 },
|
|
2767
|
+
() => Math.floor(Math.random() * 16).toString(16)
|
|
2768
|
+
).join("")}`;
|
|
2769
|
+
const result = {
|
|
2770
|
+
hash,
|
|
2771
|
+
from: "cfx:sender...",
|
|
2772
|
+
to: options.address,
|
|
2773
|
+
chain: "core"
|
|
2774
|
+
};
|
|
2775
|
+
if (options.waitForConfirmation) {
|
|
2776
|
+
result.blockNumber = 1000n;
|
|
2777
|
+
result.gasUsed = 50000n;
|
|
2778
|
+
result.status = "success";
|
|
2779
|
+
}
|
|
2780
|
+
return result;
|
|
2781
|
+
}
|
|
2782
|
+
/**
|
|
2783
|
+
* Write to eSpace contract
|
|
2784
|
+
*/
|
|
2785
|
+
async writeToEvm(options) {
|
|
2786
|
+
const _evmClient = this.clientManager.getEvmClient();
|
|
2787
|
+
const hash = `0x${Array.from(
|
|
2788
|
+
{ length: 64 },
|
|
2789
|
+
() => Math.floor(Math.random() * 16).toString(16)
|
|
2790
|
+
).join("")}`;
|
|
2791
|
+
const result = {
|
|
2792
|
+
hash,
|
|
2793
|
+
from: "0xsender...",
|
|
2794
|
+
to: options.address,
|
|
2795
|
+
chain: "evm"
|
|
2796
|
+
};
|
|
2797
|
+
if (options.waitForConfirmation) {
|
|
2798
|
+
result.blockNumber = 2000n;
|
|
2799
|
+
result.gasUsed = 45000n;
|
|
2800
|
+
result.status = "success";
|
|
2801
|
+
}
|
|
2802
|
+
return result;
|
|
2803
|
+
}
|
|
2804
|
+
/**
|
|
2805
|
+
* Simulate Core Space contract write
|
|
2806
|
+
*/
|
|
2807
|
+
async simulateCore(_options) {
|
|
2808
|
+
return {};
|
|
2809
|
+
}
|
|
2810
|
+
/**
|
|
2811
|
+
* Simulate eSpace contract write
|
|
2812
|
+
*/
|
|
2813
|
+
async simulateEvm(_options) {
|
|
2814
|
+
return {};
|
|
2815
|
+
}
|
|
2816
|
+
/**
|
|
2817
|
+
* Batch write multiple transactions
|
|
2818
|
+
*
|
|
2819
|
+
* @param writes - Array of write operations
|
|
2820
|
+
* @returns Array of write results
|
|
2821
|
+
*/
|
|
2822
|
+
async batchWrite(writes) {
|
|
2823
|
+
const results = [];
|
|
2824
|
+
for (const write of writes) {
|
|
2825
|
+
try {
|
|
2826
|
+
const result = await this.write(write);
|
|
2827
|
+
results.push(result);
|
|
2828
|
+
} catch (error) {
|
|
2829
|
+
console.error(`Failed to execute write:`, error);
|
|
2830
|
+
}
|
|
2831
|
+
}
|
|
2832
|
+
return results;
|
|
2833
|
+
}
|
|
2834
|
+
};
|
|
2835
|
+
|
|
2836
|
+
// src/utils/logger.ts
|
|
2837
|
+
var colors = {
|
|
2838
|
+
info: "\x1B[36m",
|
|
2839
|
+
// Cyan
|
|
2840
|
+
warn: "\x1B[33m",
|
|
2841
|
+
// Yellow
|
|
2842
|
+
error: "\x1B[31m",
|
|
2843
|
+
// Red
|
|
2844
|
+
success: "\x1B[32m",
|
|
2845
|
+
// Green
|
|
2846
|
+
debug: "\x1B[90m",
|
|
2847
|
+
// Gray
|
|
2848
|
+
reset: "\x1B[0m"
|
|
2849
|
+
// Reset
|
|
2850
|
+
};
|
|
2851
|
+
function formatMessage(level, message, ...args) {
|
|
2852
|
+
const timestamp = (/* @__PURE__ */ new Date()).toISOString();
|
|
2853
|
+
const formattedArgs = args.length > 0 ? ` ${args.map(
|
|
2854
|
+
(arg) => arg !== null && typeof arg === "object" ? JSON.stringify(
|
|
2855
|
+
arg,
|
|
2856
|
+
(_key, value) => typeof value === "bigint" ? value.toString() : value,
|
|
2857
|
+
2
|
|
2858
|
+
) : String(arg)
|
|
2859
|
+
).join(" ")}` : "";
|
|
2860
|
+
return `[${timestamp}] ${level.toUpperCase()}: ${message}${formattedArgs}`;
|
|
2861
|
+
}
|
|
2862
|
+
function handleLog(level, color, fn, messageOrObj, maybeMsg, ...args) {
|
|
2863
|
+
let msg = typeof messageOrObj === "string" ? messageOrObj : String(messageOrObj);
|
|
2864
|
+
let objs = maybeMsg !== void 0 ? [maybeMsg, ...args] : args;
|
|
2865
|
+
if (typeof messageOrObj === "object" && typeof maybeMsg === "string") {
|
|
2866
|
+
msg = maybeMsg;
|
|
2867
|
+
objs = [messageOrObj, ...args];
|
|
2868
|
+
}
|
|
2869
|
+
fn(color + formatMessage(level, msg, ...objs) + colors.reset);
|
|
2870
|
+
}
|
|
2871
|
+
var logger = {
|
|
2872
|
+
info(m, m2, ...args) {
|
|
2873
|
+
handleLog("info", colors.info, console.log, m, m2, ...args);
|
|
2874
|
+
},
|
|
2875
|
+
warn(m, m2, ...args) {
|
|
2876
|
+
handleLog("warn", colors.warn, console.warn, m, m2, ...args);
|
|
2877
|
+
},
|
|
2878
|
+
error(m, m2, ...args) {
|
|
2879
|
+
handleLog("error", colors.error, console.error, m, m2, ...args);
|
|
2880
|
+
},
|
|
2881
|
+
success(m, m2, ...args) {
|
|
2882
|
+
handleLog("success", colors.success, console.log, m, m2, ...args);
|
|
2883
|
+
},
|
|
2884
|
+
debug(m, m2, ...args) {
|
|
2885
|
+
handleLog("debug", colors.debug, console.log, m, m2, ...args);
|
|
2886
|
+
}
|
|
2887
|
+
};
|
|
2888
|
+
|
|
2889
|
+
// src/wallet/types/index.ts
|
|
2890
|
+
var WalletError = class extends Error {
|
|
2891
|
+
constructor(message, code, context) {
|
|
2892
|
+
super(message);
|
|
2893
|
+
this.code = code;
|
|
2894
|
+
this.context = context;
|
|
2895
|
+
this.name = "WalletError";
|
|
2896
|
+
}
|
|
2897
|
+
};
|
|
2898
|
+
var SessionKeyError = class extends WalletError {
|
|
2899
|
+
constructor(message, context) {
|
|
2900
|
+
super(message, "SESSION_KEY_ERROR", context);
|
|
2901
|
+
this.name = "SessionKeyError";
|
|
2902
|
+
}
|
|
2903
|
+
};
|
|
2904
|
+
var BatcherError = class extends WalletError {
|
|
2905
|
+
constructor(message, context) {
|
|
2906
|
+
super(message, "BATCHER_ERROR", context);
|
|
2907
|
+
this.name = "BatcherError";
|
|
2908
|
+
}
|
|
2909
|
+
};
|
|
2910
|
+
var EmbeddedWalletError = class extends WalletError {
|
|
2911
|
+
constructor(message, context) {
|
|
2912
|
+
super(message, "EMBEDDED_WALLET_ERROR", context);
|
|
2913
|
+
this.name = "EmbeddedWalletError";
|
|
2914
|
+
}
|
|
2915
|
+
};
|
|
2916
|
+
|
|
2917
|
+
// src/wallet/batching/batcher.ts
|
|
2918
|
+
var TransactionBatcher = class {
|
|
2919
|
+
coreBatch = [];
|
|
2920
|
+
evmBatch = [];
|
|
2921
|
+
autoExecuteTimer = null;
|
|
2922
|
+
options;
|
|
2923
|
+
constructor(options = {}) {
|
|
2924
|
+
this.options = {
|
|
2925
|
+
maxBatchSize: options.maxBatchSize || 10,
|
|
2926
|
+
autoExecuteTimeout: options.autoExecuteTimeout || 0,
|
|
2927
|
+
// 0 = disabled
|
|
2928
|
+
minGasPrice: options.minGasPrice || 0n
|
|
2929
|
+
};
|
|
2930
|
+
}
|
|
2931
|
+
/**
|
|
2932
|
+
* Add transaction to batch
|
|
2933
|
+
*
|
|
2934
|
+
* @param tx - Transaction to add
|
|
2935
|
+
* @returns Transaction ID
|
|
2936
|
+
*/
|
|
2937
|
+
addTransaction(tx) {
|
|
2938
|
+
const transaction = {
|
|
2939
|
+
id: `tx_${Date.now()}_${Math.random().toString(36).substring(7)}`,
|
|
2940
|
+
...tx,
|
|
2941
|
+
addedAt: /* @__PURE__ */ new Date()
|
|
2942
|
+
};
|
|
2943
|
+
const batch = tx.chain === "core" ? this.coreBatch : this.evmBatch;
|
|
2944
|
+
batch.push(transaction);
|
|
2945
|
+
if (this.options.autoExecuteTimeout > 0 && batch.length === 1) {
|
|
2946
|
+
this.startAutoExecuteTimer(tx.chain);
|
|
2947
|
+
}
|
|
2948
|
+
if (batch.length >= this.options.maxBatchSize) {
|
|
2949
|
+
console.log(
|
|
2950
|
+
`Batch for ${tx.chain} is full (${batch.length} transactions)`
|
|
2951
|
+
);
|
|
2952
|
+
}
|
|
2953
|
+
return transaction.id;
|
|
2954
|
+
}
|
|
2955
|
+
/**
|
|
2956
|
+
* Remove transaction from batch
|
|
2957
|
+
*
|
|
2958
|
+
* @param transactionId - Transaction ID
|
|
2959
|
+
* @param chain - Chain type
|
|
2960
|
+
* @returns true if removed, false if not found
|
|
2961
|
+
*/
|
|
2962
|
+
removeTransaction(transactionId, chain) {
|
|
2963
|
+
const batch = chain === "core" ? this.coreBatch : this.evmBatch;
|
|
2964
|
+
const index = batch.findIndex((tx) => tx.id === transactionId);
|
|
2965
|
+
if (index !== -1) {
|
|
2966
|
+
batch.splice(index, 1);
|
|
2967
|
+
return true;
|
|
2968
|
+
}
|
|
2969
|
+
return false;
|
|
2970
|
+
}
|
|
2971
|
+
/**
|
|
2972
|
+
* Get pending transactions for a chain
|
|
2973
|
+
*
|
|
2974
|
+
* @param chain - Chain type
|
|
2975
|
+
* @returns Array of pending transactions
|
|
2976
|
+
*/
|
|
2977
|
+
getPendingTransactions(chain) {
|
|
2978
|
+
return chain === "core" ? [...this.coreBatch] : [...this.evmBatch];
|
|
2979
|
+
}
|
|
2980
|
+
/**
|
|
2981
|
+
* Get batch statistics
|
|
2982
|
+
*
|
|
2983
|
+
* @param chain - Chain type
|
|
2984
|
+
* @returns Batch statistics
|
|
2985
|
+
*/
|
|
2986
|
+
getBatchStats(chain) {
|
|
2987
|
+
const batch = chain === "core" ? this.coreBatch : this.evmBatch;
|
|
2988
|
+
return {
|
|
2989
|
+
count: batch.length,
|
|
2990
|
+
totalValue: batch.reduce((sum, tx) => sum + (tx.value || 0n), 0n),
|
|
2991
|
+
avgGasLimit: batch.length > 0 ? batch.reduce((sum, tx) => sum + (tx.gasLimit || 0n), 0n) / BigInt(batch.length) : 0n,
|
|
2992
|
+
oldestTransaction: batch[0]?.addedAt
|
|
2993
|
+
};
|
|
2994
|
+
}
|
|
2995
|
+
/**
|
|
2996
|
+
* Execute batch of transactions
|
|
2997
|
+
*
|
|
2998
|
+
* Note: This is a simplified implementation. In production, you would:
|
|
2999
|
+
* - Use multicall contracts for actual batching
|
|
3000
|
+
* - Handle gas estimation
|
|
3001
|
+
* - Implement retry logic
|
|
3002
|
+
* - Support different batching strategies (sequential, parallel, etc.)
|
|
3003
|
+
*
|
|
3004
|
+
* @param chain - Chain to execute on
|
|
3005
|
+
* @param signer - Function to sign and send transactions
|
|
3006
|
+
* @returns Batch execution result
|
|
3007
|
+
*/
|
|
3008
|
+
async executeBatch(chain, signer) {
|
|
3009
|
+
const batch = chain === "core" ? this.coreBatch : this.evmBatch;
|
|
3010
|
+
if (batch.length === 0) {
|
|
3011
|
+
throw new BatcherError("No transactions in batch", { chain });
|
|
3012
|
+
}
|
|
3013
|
+
this.stopAutoExecuteTimer();
|
|
3014
|
+
const batchId = `batch_${Date.now()}_${Math.random().toString(36).substring(7)}`;
|
|
3015
|
+
const transactionHashes = [];
|
|
3016
|
+
let successCount = 0;
|
|
3017
|
+
let failureCount = 0;
|
|
3018
|
+
let totalGasUsed = 0n;
|
|
3019
|
+
for (const tx of batch) {
|
|
3020
|
+
try {
|
|
3021
|
+
if (signer) {
|
|
3022
|
+
const hash = await signer(tx);
|
|
3023
|
+
transactionHashes.push(hash);
|
|
3024
|
+
successCount++;
|
|
3025
|
+
totalGasUsed += tx.gasLimit || 21000n;
|
|
3026
|
+
} else {
|
|
3027
|
+
const hash = `0x${Array.from(
|
|
3028
|
+
{ length: 64 },
|
|
3029
|
+
() => Math.floor(Math.random() * 16).toString(16)
|
|
3030
|
+
).join("")}`;
|
|
3031
|
+
transactionHashes.push(hash);
|
|
3032
|
+
successCount++;
|
|
3033
|
+
totalGasUsed += tx.gasLimit || 21000n;
|
|
3034
|
+
}
|
|
3035
|
+
} catch (error) {
|
|
3036
|
+
failureCount++;
|
|
3037
|
+
console.error(`Transaction ${tx.id} failed:`, error);
|
|
3038
|
+
}
|
|
3039
|
+
}
|
|
3040
|
+
if (chain === "core") {
|
|
3041
|
+
this.coreBatch = [];
|
|
3042
|
+
} else {
|
|
3043
|
+
this.evmBatch = [];
|
|
3044
|
+
}
|
|
3045
|
+
return {
|
|
3046
|
+
batchId,
|
|
3047
|
+
transactionHashes,
|
|
3048
|
+
successCount,
|
|
3049
|
+
failureCount,
|
|
3050
|
+
executedAt: /* @__PURE__ */ new Date(),
|
|
3051
|
+
totalGasUsed,
|
|
3052
|
+
chain
|
|
3053
|
+
};
|
|
3054
|
+
}
|
|
3055
|
+
/**
|
|
3056
|
+
* Clear all pending transactions
|
|
3057
|
+
*
|
|
3058
|
+
* @param chain - Chain to clear, or undefined to clear both
|
|
3059
|
+
*/
|
|
3060
|
+
clearBatch(chain) {
|
|
3061
|
+
if (chain === "core" || chain === void 0) {
|
|
3062
|
+
this.coreBatch = [];
|
|
3063
|
+
}
|
|
3064
|
+
if (chain === "evm" || chain === void 0) {
|
|
3065
|
+
this.evmBatch = [];
|
|
3066
|
+
}
|
|
3067
|
+
this.stopAutoExecuteTimer();
|
|
3068
|
+
}
|
|
3069
|
+
/**
|
|
3070
|
+
* Start auto-execute timer
|
|
3071
|
+
*/
|
|
3072
|
+
startAutoExecuteTimer(chain) {
|
|
3073
|
+
if (this.options.autoExecuteTimeout <= 0) return;
|
|
3074
|
+
this.stopAutoExecuteTimer();
|
|
3075
|
+
this.autoExecuteTimer = setTimeout(() => {
|
|
3076
|
+
const batch = chain === "core" ? this.coreBatch : this.evmBatch;
|
|
3077
|
+
if (batch.length > 0) {
|
|
3078
|
+
console.log(
|
|
3079
|
+
`Auto-executing batch for ${chain} (${batch.length} transactions)`
|
|
3080
|
+
);
|
|
3081
|
+
}
|
|
3082
|
+
}, this.options.autoExecuteTimeout);
|
|
3083
|
+
}
|
|
3084
|
+
/**
|
|
3085
|
+
* Stop auto-execute timer
|
|
3086
|
+
*/
|
|
3087
|
+
stopAutoExecuteTimer() {
|
|
3088
|
+
if (this.autoExecuteTimer) {
|
|
3089
|
+
clearTimeout(this.autoExecuteTimer);
|
|
3090
|
+
this.autoExecuteTimer = null;
|
|
3091
|
+
}
|
|
3092
|
+
}
|
|
3093
|
+
/**
|
|
3094
|
+
* Get batcher configuration
|
|
3095
|
+
*/
|
|
3096
|
+
getOptions() {
|
|
3097
|
+
return { ...this.options };
|
|
3098
|
+
}
|
|
3099
|
+
/**
|
|
3100
|
+
* Update batcher configuration
|
|
3101
|
+
*/
|
|
3102
|
+
updateOptions(options) {
|
|
3103
|
+
this.options = {
|
|
3104
|
+
...this.options,
|
|
3105
|
+
...options
|
|
3106
|
+
};
|
|
3107
|
+
if (options.autoExecuteTimeout !== void 0) {
|
|
3108
|
+
this.stopAutoExecuteTimer();
|
|
3109
|
+
if (this.coreBatch.length > 0) {
|
|
3110
|
+
this.startAutoExecuteTimer("core");
|
|
3111
|
+
}
|
|
3112
|
+
if (this.evmBatch.length > 0) {
|
|
3113
|
+
this.startAutoExecuteTimer("evm");
|
|
3114
|
+
}
|
|
3115
|
+
}
|
|
3116
|
+
}
|
|
3117
|
+
};
|
|
3118
|
+
|
|
3119
|
+
// src/wallet/derivation.ts
|
|
3120
|
+
import { HDKey } from "@scure/bip32";
|
|
3121
|
+
import {
|
|
3122
|
+
generateMnemonic as generateBip39Mnemonic,
|
|
3123
|
+
mnemonicToSeedSync,
|
|
3124
|
+
validateMnemonic as validateBip39Mnemonic
|
|
3125
|
+
} from "@scure/bip39";
|
|
3126
|
+
import { wordlist } from "@scure/bip39/wordlists/english.js";
|
|
3127
|
+
import { privateKeyToAccount as civePrivateKeyToAccount } from "cive/accounts";
|
|
3128
|
+
import { bytesToHex } from "viem";
|
|
3129
|
+
import { privateKeyToAccount as viemPrivateKeyToAccount } from "viem/accounts";
|
|
3130
|
+
|
|
3131
|
+
// src/wallet/types.ts
|
|
3132
|
+
var COIN_TYPES = {
|
|
3133
|
+
/** Conflux Core Space - registered coin type 503 */
|
|
3134
|
+
CONFLUX: 503,
|
|
3135
|
+
/** Ethereum/eSpace - standard coin type 60 */
|
|
3136
|
+
ETHEREUM: 60
|
|
3137
|
+
};
|
|
3138
|
+
var CORE_NETWORK_IDS = {
|
|
3139
|
+
/** Local development network */
|
|
3140
|
+
LOCAL: 2029,
|
|
3141
|
+
/** Testnet */
|
|
3142
|
+
TESTNET: 1,
|
|
3143
|
+
/** Mainnet */
|
|
3144
|
+
MAINNET: 1029
|
|
3145
|
+
};
|
|
3146
|
+
|
|
3147
|
+
// src/wallet/derivation.ts
|
|
3148
|
+
function generateMnemonic(strength = 128) {
|
|
3149
|
+
return generateBip39Mnemonic(wordlist, strength);
|
|
3150
|
+
}
|
|
3151
|
+
function validateMnemonic(mnemonic) {
|
|
3152
|
+
const normalizedMnemonic = mnemonic.trim().toLowerCase();
|
|
3153
|
+
const words = normalizedMnemonic.split(/\s+/);
|
|
3154
|
+
const wordCount = words.length;
|
|
3155
|
+
if (wordCount !== 12 && wordCount !== 24) {
|
|
3156
|
+
return {
|
|
3157
|
+
valid: false,
|
|
3158
|
+
wordCount,
|
|
3159
|
+
error: `Invalid word count: ${wordCount}. Must be 12 or 24.`
|
|
3160
|
+
};
|
|
3161
|
+
}
|
|
3162
|
+
const valid = validateBip39Mnemonic(normalizedMnemonic, wordlist);
|
|
3163
|
+
return {
|
|
3164
|
+
valid,
|
|
3165
|
+
wordCount,
|
|
3166
|
+
error: valid ? void 0 : "Invalid mnemonic: checksum verification failed"
|
|
3167
|
+
};
|
|
3168
|
+
}
|
|
3169
|
+
function deriveAccounts(mnemonic, options) {
|
|
3170
|
+
const {
|
|
3171
|
+
count,
|
|
3172
|
+
startIndex = 0,
|
|
3173
|
+
coreNetworkId = CORE_NETWORK_IDS.LOCAL,
|
|
3174
|
+
accountType = "standard"
|
|
3175
|
+
} = options;
|
|
3176
|
+
const validation = validateMnemonic(mnemonic);
|
|
3177
|
+
if (!validation.valid) {
|
|
3178
|
+
throw new Error(`Invalid mnemonic: ${validation.error}`);
|
|
3179
|
+
}
|
|
3180
|
+
const normalizedMnemonic = mnemonic.trim().toLowerCase();
|
|
3181
|
+
const seed = mnemonicToSeedSync(normalizedMnemonic);
|
|
3182
|
+
const masterKey = HDKey.fromMasterSeed(seed);
|
|
3183
|
+
const accounts = [];
|
|
3184
|
+
const accountTypeIndex = accountType === "standard" ? 0 : 1;
|
|
3185
|
+
for (let i = startIndex; i < startIndex + count; i++) {
|
|
3186
|
+
const corePath = `m/44'/${COIN_TYPES.CONFLUX}'/${accountTypeIndex}'/0/${i}`;
|
|
3187
|
+
const coreKey = masterKey.derive(corePath);
|
|
3188
|
+
const evmPath = `m/44'/${COIN_TYPES.ETHEREUM}'/${accountTypeIndex}'/0/${i}`;
|
|
3189
|
+
const evmKey = masterKey.derive(evmPath);
|
|
3190
|
+
if (!coreKey.privateKey || !evmKey.privateKey) {
|
|
3191
|
+
throw new Error(`Failed to derive keys at index ${i}`);
|
|
3192
|
+
}
|
|
3193
|
+
const corePrivateKey = bytesToHex(coreKey.privateKey);
|
|
3194
|
+
const evmPrivateKey = bytesToHex(evmKey.privateKey);
|
|
3195
|
+
const coreAccount = civePrivateKeyToAccount(corePrivateKey, {
|
|
3196
|
+
networkId: coreNetworkId
|
|
3197
|
+
});
|
|
3198
|
+
const evmAccount = viemPrivateKeyToAccount(evmPrivateKey);
|
|
3199
|
+
accounts.push({
|
|
3200
|
+
index: i,
|
|
3201
|
+
coreAddress: coreAccount.address,
|
|
3202
|
+
evmAddress: evmAccount.address,
|
|
3203
|
+
corePrivateKey,
|
|
3204
|
+
evmPrivateKey,
|
|
3205
|
+
paths: {
|
|
3206
|
+
core: corePath,
|
|
3207
|
+
evm: evmPath
|
|
3208
|
+
}
|
|
3209
|
+
});
|
|
3210
|
+
}
|
|
3211
|
+
return accounts;
|
|
3212
|
+
}
|
|
3213
|
+
function deriveAccount(mnemonic, index, coreNetworkId = CORE_NETWORK_IDS.LOCAL, accountType = "standard") {
|
|
3214
|
+
const accounts = deriveAccounts(mnemonic, {
|
|
3215
|
+
count: 1,
|
|
3216
|
+
startIndex: index,
|
|
3217
|
+
coreNetworkId,
|
|
3218
|
+
accountType
|
|
3219
|
+
});
|
|
3220
|
+
return accounts[0];
|
|
3221
|
+
}
|
|
3222
|
+
function deriveFaucetAccount(mnemonic, coreNetworkId = CORE_NETWORK_IDS.LOCAL) {
|
|
3223
|
+
return deriveAccount(mnemonic, 0, coreNetworkId, "mining");
|
|
3224
|
+
}
|
|
3225
|
+
|
|
3226
|
+
// src/wallet/embedded/custody.ts
|
|
3227
|
+
import { generatePrivateKey, privateKeyToAccount as privateKeyToAccount3 } from "viem/accounts";
|
|
3228
|
+
var EmbeddedWalletManager = class {
|
|
3229
|
+
wallets = /* @__PURE__ */ new Map();
|
|
3230
|
+
options;
|
|
3231
|
+
constructor(options = {}) {
|
|
3232
|
+
this.options = {
|
|
3233
|
+
algorithm: options.algorithm || "aes-256-gcm",
|
|
3234
|
+
iterations: options.iterations || 1e5,
|
|
3235
|
+
autoCreate: options.autoCreate !== false
|
|
3236
|
+
// Default true
|
|
3237
|
+
};
|
|
3238
|
+
}
|
|
3239
|
+
/**
|
|
3240
|
+
* Create a new embedded wallet for a user
|
|
3241
|
+
*
|
|
3242
|
+
* @param userId - User identifier
|
|
3243
|
+
* @param password - Encryption password
|
|
3244
|
+
* @returns Created wallet (without private key)
|
|
3245
|
+
*/
|
|
3246
|
+
async createWallet(userId, password) {
|
|
3247
|
+
if (this.wallets.has(userId)) {
|
|
3248
|
+
throw new EmbeddedWalletError("Wallet already exists", { userId });
|
|
3249
|
+
}
|
|
3250
|
+
const privateKey = generatePrivateKey();
|
|
3251
|
+
const account = privateKeyToAccount3(privateKey);
|
|
3252
|
+
const { encrypted, iv, salt } = await this.encryptPrivateKey(
|
|
3253
|
+
privateKey,
|
|
3254
|
+
password
|
|
3255
|
+
);
|
|
3256
|
+
const evmAddress = account.address;
|
|
3257
|
+
const coreAddress = `cfx:${evmAddress.slice(2)}`;
|
|
3258
|
+
const wallet = {
|
|
3259
|
+
userId,
|
|
3260
|
+
coreAddress,
|
|
3261
|
+
evmAddress,
|
|
3262
|
+
encryptedPrivateKey: encrypted,
|
|
3263
|
+
encryption: {
|
|
3264
|
+
algorithm: this.options.algorithm,
|
|
3265
|
+
iv,
|
|
3266
|
+
salt
|
|
3267
|
+
},
|
|
3268
|
+
createdAt: /* @__PURE__ */ new Date(),
|
|
3269
|
+
lastAccessedAt: /* @__PURE__ */ new Date(),
|
|
3270
|
+
isActive: true
|
|
3271
|
+
};
|
|
3272
|
+
this.wallets.set(userId, wallet);
|
|
3273
|
+
const { encryptedPrivateKey: _, ...publicWallet } = wallet;
|
|
3274
|
+
return publicWallet;
|
|
3275
|
+
}
|
|
3276
|
+
/**
|
|
3277
|
+
* Get wallet info (without private key)
|
|
3278
|
+
*
|
|
3279
|
+
* @param userId - User identifier
|
|
3280
|
+
* @returns Wallet info or undefined
|
|
3281
|
+
*/
|
|
3282
|
+
getWallet(userId) {
|
|
3283
|
+
const wallet = this.wallets.get(userId);
|
|
3284
|
+
if (!wallet) return void 0;
|
|
3285
|
+
wallet.lastAccessedAt = /* @__PURE__ */ new Date();
|
|
3286
|
+
const { encryptedPrivateKey: _, ...publicWallet } = wallet;
|
|
3287
|
+
return publicWallet;
|
|
3288
|
+
}
|
|
3289
|
+
/**
|
|
3290
|
+
* Check if user has a wallet
|
|
3291
|
+
*
|
|
3292
|
+
* @param userId - User identifier
|
|
3293
|
+
* @returns true if wallet exists
|
|
3294
|
+
*/
|
|
3295
|
+
hasWallet(userId) {
|
|
3296
|
+
return this.wallets.has(userId);
|
|
3297
|
+
}
|
|
3298
|
+
/**
|
|
3299
|
+
* Sign transaction with user's embedded wallet
|
|
3300
|
+
*
|
|
3301
|
+
* @param userId - User identifier
|
|
3302
|
+
* @param password - Decryption password
|
|
3303
|
+
* @param request - Transaction request
|
|
3304
|
+
* @returns Signed transaction
|
|
3305
|
+
*/
|
|
3306
|
+
async signTransaction(userId, password, request) {
|
|
3307
|
+
const wallet = this.wallets.get(userId);
|
|
3308
|
+
if (!wallet) {
|
|
3309
|
+
throw new EmbeddedWalletError("Wallet not found", { userId });
|
|
3310
|
+
}
|
|
3311
|
+
if (!wallet.isActive) {
|
|
3312
|
+
throw new EmbeddedWalletError("Wallet is not active", { userId });
|
|
3313
|
+
}
|
|
3314
|
+
const privateKey = await this.decryptPrivateKey(
|
|
3315
|
+
wallet.encryptedPrivateKey,
|
|
3316
|
+
password,
|
|
3317
|
+
wallet.encryption.iv,
|
|
3318
|
+
wallet.encryption.salt
|
|
3319
|
+
);
|
|
3320
|
+
const account = privateKeyToAccount3(privateKey);
|
|
3321
|
+
wallet.lastAccessedAt = /* @__PURE__ */ new Date();
|
|
3322
|
+
const serialized = JSON.stringify({
|
|
3323
|
+
from: account.address,
|
|
3324
|
+
...request,
|
|
3325
|
+
value: request.value?.toString(),
|
|
3326
|
+
gasLimit: request.gasLimit?.toString(),
|
|
3327
|
+
gasPrice: request.gasPrice?.toString()
|
|
3328
|
+
});
|
|
3329
|
+
const signature = await account.signMessage({
|
|
3330
|
+
message: serialized
|
|
3331
|
+
});
|
|
3332
|
+
return {
|
|
3333
|
+
rawTransaction: signature,
|
|
3334
|
+
hash: `0x${Array.from(
|
|
3335
|
+
{ length: 64 },
|
|
3336
|
+
() => Math.floor(Math.random() * 16).toString(16)
|
|
3337
|
+
).join("")}`,
|
|
3338
|
+
from: account.address,
|
|
3339
|
+
chain: request.chain
|
|
3340
|
+
};
|
|
3341
|
+
}
|
|
3342
|
+
/**
|
|
3343
|
+
* Export wallet for user backup
|
|
3344
|
+
*
|
|
3345
|
+
* @param userId - User identifier
|
|
3346
|
+
* @param password - Encryption password
|
|
3347
|
+
* @returns Encrypted wallet export
|
|
3348
|
+
*/
|
|
3349
|
+
async exportWallet(userId, password) {
|
|
3350
|
+
const wallet = this.wallets.get(userId);
|
|
3351
|
+
if (!wallet) {
|
|
3352
|
+
throw new EmbeddedWalletError("Wallet not found", { userId });
|
|
3353
|
+
}
|
|
3354
|
+
const exportData = JSON.stringify({
|
|
3355
|
+
coreAddress: wallet.coreAddress,
|
|
3356
|
+
evmAddress: wallet.evmAddress,
|
|
3357
|
+
encryptedPrivateKey: wallet.encryptedPrivateKey,
|
|
3358
|
+
encryption: wallet.encryption
|
|
3359
|
+
});
|
|
3360
|
+
const { encrypted, iv, salt } = await this.encryptPrivateKey(
|
|
3361
|
+
exportData,
|
|
3362
|
+
password
|
|
3363
|
+
);
|
|
3364
|
+
return {
|
|
3365
|
+
userId,
|
|
3366
|
+
encryptedData: encrypted,
|
|
3367
|
+
encryption: {
|
|
3368
|
+
algorithm: this.options.algorithm,
|
|
3369
|
+
iv,
|
|
3370
|
+
salt
|
|
3371
|
+
},
|
|
3372
|
+
exportedAt: /* @__PURE__ */ new Date()
|
|
3373
|
+
};
|
|
3374
|
+
}
|
|
3375
|
+
/**
|
|
3376
|
+
* Deactivate wallet
|
|
3377
|
+
*
|
|
3378
|
+
* @param userId - User identifier
|
|
3379
|
+
*/
|
|
3380
|
+
deactivateWallet(userId) {
|
|
3381
|
+
const wallet = this.wallets.get(userId);
|
|
3382
|
+
if (wallet) {
|
|
3383
|
+
wallet.isActive = false;
|
|
3384
|
+
}
|
|
3385
|
+
}
|
|
3386
|
+
/**
|
|
3387
|
+
* Delete wallet permanently
|
|
3388
|
+
*
|
|
3389
|
+
* WARNING: This operation cannot be undone
|
|
3390
|
+
*
|
|
3391
|
+
* @param userId - User identifier
|
|
3392
|
+
* @returns true if deleted, false if not found
|
|
3393
|
+
*/
|
|
3394
|
+
deleteWallet(userId) {
|
|
3395
|
+
return this.wallets.delete(userId);
|
|
3396
|
+
}
|
|
3397
|
+
/**
|
|
3398
|
+
* List all wallets (without private keys)
|
|
3399
|
+
*
|
|
3400
|
+
* @returns Array of wallet info
|
|
3401
|
+
*/
|
|
3402
|
+
listWallets() {
|
|
3403
|
+
return Array.from(this.wallets.values()).map((wallet) => {
|
|
3404
|
+
const { encryptedPrivateKey: _, ...publicWallet } = wallet;
|
|
3405
|
+
return publicWallet;
|
|
3406
|
+
});
|
|
3407
|
+
}
|
|
3408
|
+
/**
|
|
3409
|
+
* Get wallet statistics
|
|
3410
|
+
*
|
|
3411
|
+
* @returns Wallet statistics
|
|
3412
|
+
*/
|
|
3413
|
+
getStats() {
|
|
3414
|
+
const all = Array.from(this.wallets.values());
|
|
3415
|
+
const active = all.filter((w) => w.isActive);
|
|
3416
|
+
return {
|
|
3417
|
+
total: all.length,
|
|
3418
|
+
active: active.length,
|
|
3419
|
+
inactive: all.length - active.length
|
|
3420
|
+
};
|
|
3421
|
+
}
|
|
3422
|
+
/**
|
|
3423
|
+
* Encrypt private key
|
|
3424
|
+
*
|
|
3425
|
+
* NOTE: Simplified implementation for demonstration
|
|
3426
|
+
* Production should use proper encryption (node:crypto, @noble/ciphers, etc.)
|
|
3427
|
+
*/
|
|
3428
|
+
async encryptPrivateKey(data, password) {
|
|
3429
|
+
const iv = Array.from(
|
|
3430
|
+
{ length: 16 },
|
|
3431
|
+
() => Math.floor(Math.random() * 256).toString(16).padStart(2, "0")
|
|
3432
|
+
).join("");
|
|
3433
|
+
const salt = Array.from(
|
|
3434
|
+
{ length: 32 },
|
|
3435
|
+
() => Math.floor(Math.random() * 256).toString(16).padStart(2, "0")
|
|
3436
|
+
).join("");
|
|
3437
|
+
const mockEncrypted = Buffer.from(
|
|
3438
|
+
JSON.stringify({ data, password, iv, salt })
|
|
3439
|
+
).toString("base64");
|
|
3440
|
+
return {
|
|
3441
|
+
encrypted: mockEncrypted,
|
|
3442
|
+
iv,
|
|
3443
|
+
salt
|
|
3444
|
+
};
|
|
3445
|
+
}
|
|
3446
|
+
/**
|
|
3447
|
+
* Decrypt private key
|
|
3448
|
+
*
|
|
3449
|
+
* NOTE: Simplified implementation for demonstration
|
|
3450
|
+
*/
|
|
3451
|
+
async decryptPrivateKey(encrypted, password, _iv, _salt) {
|
|
3452
|
+
try {
|
|
3453
|
+
const decoded = JSON.parse(
|
|
3454
|
+
Buffer.from(encrypted, "base64").toString("utf-8")
|
|
3455
|
+
);
|
|
3456
|
+
if (decoded.password !== password) {
|
|
3457
|
+
throw new Error("Invalid password");
|
|
3458
|
+
}
|
|
3459
|
+
return decoded.data;
|
|
3460
|
+
} catch (error) {
|
|
3461
|
+
throw new EmbeddedWalletError("Failed to decrypt private key", {
|
|
3462
|
+
error: error instanceof Error ? error.message : "Unknown error"
|
|
3463
|
+
});
|
|
3464
|
+
}
|
|
3465
|
+
}
|
|
3466
|
+
};
|
|
3467
|
+
|
|
3468
|
+
// src/wallet/session-keys/manager.ts
|
|
3469
|
+
import { privateKeyToAccount as privateKeyToAccount4 } from "viem/accounts";
|
|
3470
|
+
var SessionKeyManager = class {
|
|
3471
|
+
sessionKeys = /* @__PURE__ */ new Map();
|
|
3472
|
+
/**
|
|
3473
|
+
* Generate a new session key
|
|
3474
|
+
*
|
|
3475
|
+
* @param parentAddress - Parent wallet address
|
|
3476
|
+
* @param options - Session key configuration
|
|
3477
|
+
* @returns Created session key
|
|
3478
|
+
*/
|
|
3479
|
+
generateSessionKey(parentAddress, options) {
|
|
3480
|
+
const privateKey = `0x${Array.from(
|
|
3481
|
+
{ length: 64 },
|
|
3482
|
+
() => Math.floor(Math.random() * 16).toString(16)
|
|
3483
|
+
).join("")}`;
|
|
3484
|
+
const account = privateKeyToAccount4(privateKey);
|
|
3485
|
+
const ttl = options.ttl || 3600;
|
|
3486
|
+
const now = /* @__PURE__ */ new Date();
|
|
3487
|
+
const expiresAt = new Date(now.getTime() + ttl * 1e3);
|
|
3488
|
+
const sessionKey = {
|
|
3489
|
+
id: `sk_${Date.now()}_${Math.random().toString(36).substring(7)}`,
|
|
3490
|
+
privateKey,
|
|
3491
|
+
address: account.address,
|
|
3492
|
+
parentAddress,
|
|
3493
|
+
ttl,
|
|
3494
|
+
expiresAt,
|
|
3495
|
+
permissions: options.permissions || {},
|
|
3496
|
+
createdAt: now,
|
|
3497
|
+
isActive: true,
|
|
3498
|
+
chain: options.chain
|
|
3499
|
+
};
|
|
3500
|
+
this.sessionKeys.set(sessionKey.id, sessionKey);
|
|
3501
|
+
return sessionKey;
|
|
3502
|
+
}
|
|
3503
|
+
/**
|
|
3504
|
+
* Get session key by ID
|
|
3505
|
+
*
|
|
3506
|
+
* @param sessionKeyId - Session key identifier
|
|
3507
|
+
* @returns Session key or undefined
|
|
3508
|
+
*/
|
|
3509
|
+
getSessionKey(sessionKeyId) {
|
|
3510
|
+
const sessionKey = this.sessionKeys.get(sessionKeyId);
|
|
3511
|
+
if (sessionKey && /* @__PURE__ */ new Date() > sessionKey.expiresAt) {
|
|
3512
|
+
sessionKey.isActive = false;
|
|
3513
|
+
}
|
|
3514
|
+
return sessionKey;
|
|
3515
|
+
}
|
|
3516
|
+
/**
|
|
3517
|
+
* Revoke a session key
|
|
3518
|
+
*
|
|
3519
|
+
* @param sessionKeyId - Session key identifier
|
|
3520
|
+
*/
|
|
3521
|
+
revokeSessionKey(sessionKeyId) {
|
|
3522
|
+
const sessionKey = this.sessionKeys.get(sessionKeyId);
|
|
3523
|
+
if (sessionKey) {
|
|
3524
|
+
sessionKey.isActive = false;
|
|
3525
|
+
}
|
|
3526
|
+
}
|
|
3527
|
+
/**
|
|
3528
|
+
* List all session keys for a parent address
|
|
3529
|
+
*
|
|
3530
|
+
* @param parentAddress - Parent wallet address
|
|
3531
|
+
* @returns Array of session keys
|
|
3532
|
+
*/
|
|
3533
|
+
listSessionKeys(parentAddress) {
|
|
3534
|
+
return Array.from(this.sessionKeys.values()).filter(
|
|
3535
|
+
(sk) => sk.parentAddress.toLowerCase() === parentAddress.toLowerCase()
|
|
3536
|
+
);
|
|
3537
|
+
}
|
|
3538
|
+
/**
|
|
3539
|
+
* List active session keys for a parent address
|
|
3540
|
+
*
|
|
3541
|
+
* @param parentAddress - Parent wallet address
|
|
3542
|
+
* @returns Array of active session keys
|
|
3543
|
+
*/
|
|
3544
|
+
listActiveSessionKeys(parentAddress) {
|
|
3545
|
+
return this.listSessionKeys(parentAddress).filter(
|
|
3546
|
+
(sk) => sk.isActive && /* @__PURE__ */ new Date() <= sk.expiresAt
|
|
3547
|
+
);
|
|
3548
|
+
}
|
|
3549
|
+
/**
|
|
3550
|
+
* Validate transaction against session key permissions
|
|
3551
|
+
*
|
|
3552
|
+
* @param sessionKey - Session key
|
|
3553
|
+
* @param request - Transaction request
|
|
3554
|
+
* @throws SessionKeyError if validation fails
|
|
3555
|
+
*/
|
|
3556
|
+
validateTransaction(sessionKey, request) {
|
|
3557
|
+
if (!sessionKey.isActive) {
|
|
3558
|
+
throw new SessionKeyError("Session key is not active", {
|
|
3559
|
+
sessionKeyId: sessionKey.id
|
|
3560
|
+
});
|
|
3561
|
+
}
|
|
3562
|
+
if (/* @__PURE__ */ new Date() > sessionKey.expiresAt) {
|
|
3563
|
+
throw new SessionKeyError("Session key has expired", {
|
|
3564
|
+
sessionKeyId: sessionKey.id,
|
|
3565
|
+
expiresAt: sessionKey.expiresAt
|
|
3566
|
+
});
|
|
3567
|
+
}
|
|
3568
|
+
if (request.chain !== sessionKey.chain) {
|
|
3569
|
+
throw new SessionKeyError("Chain mismatch", {
|
|
3570
|
+
sessionKeyId: sessionKey.id,
|
|
3571
|
+
allowedChain: sessionKey.chain,
|
|
3572
|
+
requestedChain: request.chain
|
|
3573
|
+
});
|
|
3574
|
+
}
|
|
3575
|
+
const { permissions } = sessionKey;
|
|
3576
|
+
if (permissions.maxValue && request.value && request.value > permissions.maxValue) {
|
|
3577
|
+
throw new SessionKeyError("Transaction value exceeds maximum", {
|
|
3578
|
+
sessionKeyId: sessionKey.id,
|
|
3579
|
+
maxValue: permissions.maxValue.toString(),
|
|
3580
|
+
requestedValue: request.value.toString()
|
|
3581
|
+
});
|
|
3582
|
+
}
|
|
3583
|
+
if (permissions.contracts && permissions.contracts.length > 0) {
|
|
3584
|
+
const isWhitelisted = permissions.contracts.some(
|
|
3585
|
+
(addr) => addr.toLowerCase() === request.to.toLowerCase()
|
|
3586
|
+
);
|
|
3587
|
+
if (!isWhitelisted) {
|
|
3588
|
+
throw new SessionKeyError("Contract not whitelisted", {
|
|
3589
|
+
sessionKeyId: sessionKey.id,
|
|
3590
|
+
whitelistedContracts: permissions.contracts,
|
|
3591
|
+
requestedContract: request.to
|
|
3592
|
+
});
|
|
3593
|
+
}
|
|
3594
|
+
}
|
|
3595
|
+
if (permissions.operations && permissions.operations.length > 0 && request.data) {
|
|
3596
|
+
const selector = request.data.slice(0, 10);
|
|
3597
|
+
const isAllowed = permissions.operations.some(
|
|
3598
|
+
(op) => op.toLowerCase() === selector.toLowerCase()
|
|
3599
|
+
);
|
|
3600
|
+
if (!isAllowed) {
|
|
3601
|
+
throw new SessionKeyError("Operation not allowed", {
|
|
3602
|
+
sessionKeyId: sessionKey.id,
|
|
3603
|
+
allowedOperations: permissions.operations,
|
|
3604
|
+
requestedOperation: selector
|
|
3605
|
+
});
|
|
3606
|
+
}
|
|
3607
|
+
}
|
|
3608
|
+
}
|
|
3609
|
+
/**
|
|
3610
|
+
* Sign transaction with session key
|
|
3611
|
+
*
|
|
3612
|
+
* @param sessionKeyId - Session key identifier
|
|
3613
|
+
* @param request - Transaction request
|
|
3614
|
+
* @returns Signed transaction
|
|
3615
|
+
* @throws SessionKeyError if session key is invalid or transaction violates permissions
|
|
3616
|
+
*/
|
|
3617
|
+
async signWithSessionKey(sessionKeyId, request) {
|
|
3618
|
+
const sessionKey = this.sessionKeys.get(sessionKeyId);
|
|
3619
|
+
if (!sessionKey) {
|
|
3620
|
+
throw new SessionKeyError("Session key not found", { sessionKeyId });
|
|
3621
|
+
}
|
|
3622
|
+
this.validateTransaction(sessionKey, request);
|
|
3623
|
+
const account = privateKeyToAccount4(sessionKey.privateKey);
|
|
3624
|
+
const serialized = JSON.stringify({
|
|
3625
|
+
from: account.address,
|
|
3626
|
+
to: request.to,
|
|
3627
|
+
value: request.value?.toString(),
|
|
3628
|
+
data: request.data,
|
|
3629
|
+
gasLimit: request.gasLimit?.toString(),
|
|
3630
|
+
gasPrice: request.gasPrice?.toString(),
|
|
3631
|
+
nonce: request.nonce,
|
|
3632
|
+
chain: request.chain
|
|
3633
|
+
});
|
|
3634
|
+
const signature = await account.signMessage({
|
|
3635
|
+
message: serialized
|
|
3636
|
+
});
|
|
3637
|
+
return {
|
|
3638
|
+
rawTransaction: signature,
|
|
3639
|
+
hash: `0x${Array.from(
|
|
3640
|
+
{ length: 64 },
|
|
3641
|
+
() => Math.floor(Math.random() * 16).toString(16)
|
|
3642
|
+
).join("")}`,
|
|
3643
|
+
from: account.address,
|
|
3644
|
+
chain: request.chain
|
|
3645
|
+
};
|
|
3646
|
+
}
|
|
3647
|
+
/**
|
|
3648
|
+
* Clean up expired session keys
|
|
3649
|
+
*
|
|
3650
|
+
* @returns Number of removed session keys
|
|
3651
|
+
*/
|
|
3652
|
+
cleanupExpired() {
|
|
3653
|
+
const now = /* @__PURE__ */ new Date();
|
|
3654
|
+
let removed = 0;
|
|
3655
|
+
for (const [id, sessionKey] of this.sessionKeys.entries()) {
|
|
3656
|
+
if (now > sessionKey.expiresAt) {
|
|
3657
|
+
this.sessionKeys.delete(id);
|
|
3658
|
+
removed++;
|
|
3659
|
+
}
|
|
3660
|
+
}
|
|
3661
|
+
return removed;
|
|
3662
|
+
}
|
|
3663
|
+
/**
|
|
3664
|
+
* Get session key statistics
|
|
3665
|
+
*
|
|
3666
|
+
* @returns Statistics about session keys
|
|
3667
|
+
*/
|
|
3668
|
+
getStats() {
|
|
3669
|
+
const all = Array.from(this.sessionKeys.values());
|
|
3670
|
+
const active = all.filter(
|
|
3671
|
+
(sk) => sk.isActive && /* @__PURE__ */ new Date() <= sk.expiresAt
|
|
3672
|
+
);
|
|
3673
|
+
const expired = all.filter((sk) => /* @__PURE__ */ new Date() > sk.expiresAt);
|
|
3674
|
+
return {
|
|
3675
|
+
total: all.length,
|
|
3676
|
+
active: active.length,
|
|
3677
|
+
expired: expired.length,
|
|
3678
|
+
inactive: all.length - active.length - expired.length
|
|
3679
|
+
};
|
|
3680
|
+
}
|
|
3681
|
+
};
|
|
3682
|
+
export {
|
|
3683
|
+
BatcherError,
|
|
3684
|
+
CORE_LOCAL,
|
|
3685
|
+
CORE_MAINNET,
|
|
3686
|
+
CORE_TESTNET,
|
|
3687
|
+
ClientManager,
|
|
3688
|
+
ContractDeployer,
|
|
3689
|
+
ContractError,
|
|
3690
|
+
ContractReader,
|
|
3691
|
+
ContractWriter,
|
|
3692
|
+
CoreClient,
|
|
3693
|
+
CoreTestClient,
|
|
3694
|
+
CoreWalletClient,
|
|
3695
|
+
DeploymentError,
|
|
3696
|
+
ERC1155_ABI,
|
|
3697
|
+
ERC20_ABI,
|
|
3698
|
+
ERC721_ABI,
|
|
3699
|
+
EVM_LOCAL,
|
|
3700
|
+
EVM_MAINNET,
|
|
3701
|
+
EVM_TESTNET,
|
|
3702
|
+
EmbeddedWalletError,
|
|
3703
|
+
EmbeddedWalletManager,
|
|
3704
|
+
EspaceClient,
|
|
3705
|
+
EspaceTestClient,
|
|
3706
|
+
EspaceWalletClient,
|
|
3707
|
+
InteractionError,
|
|
3708
|
+
SessionKeyError,
|
|
3709
|
+
SessionKeyManager,
|
|
3710
|
+
TransactionBatcher,
|
|
3711
|
+
WalletError,
|
|
3712
|
+
defaultNetworkSelector,
|
|
3713
|
+
deriveAccount,
|
|
3714
|
+
deriveAccounts,
|
|
3715
|
+
deriveFaucetAccount,
|
|
3716
|
+
formatCFX2 as formatCFX,
|
|
3717
|
+
formatUnits2 as formatUnits,
|
|
3718
|
+
generateMnemonic,
|
|
3719
|
+
getChainConfig,
|
|
3720
|
+
getCoreChains,
|
|
3721
|
+
getEvmChains,
|
|
3722
|
+
getMainnetChains,
|
|
3723
|
+
isAddress as isCoreAddress,
|
|
3724
|
+
isAddress2 as isEspaceAddress,
|
|
3725
|
+
logger,
|
|
3726
|
+
parseCFX2 as parseCFX,
|
|
3727
|
+
parseUnits,
|
|
3728
|
+
validateMnemonic
|
|
3729
|
+
};
|
|
3730
|
+
//# sourceMappingURL=index.js.map
|