@cfxdevkit/core 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +35 -0
- package/LICENSE +72 -0
- package/README.md +257 -0
- package/dist/clients/index.cjs +2053 -0
- package/dist/clients/index.cjs.map +1 -0
- package/dist/clients/index.d.cts +7 -0
- package/dist/clients/index.d.ts +7 -0
- package/dist/clients/index.js +2043 -0
- package/dist/clients/index.js.map +1 -0
- package/dist/config/index.cjs +423 -0
- package/dist/config/index.cjs.map +1 -0
- package/dist/config/index.d.cts +99 -0
- package/dist/config/index.d.ts +99 -0
- package/dist/config/index.js +380 -0
- package/dist/config/index.js.map +1 -0
- package/dist/config-BMtaWM0X.d.cts +165 -0
- package/dist/config-BMtaWM0X.d.ts +165 -0
- package/dist/core-C5qe16RS.d.ts +352 -0
- package/dist/core-RZA4aKwj.d.cts +352 -0
- package/dist/index-BhCpy6Fz.d.cts +165 -0
- package/dist/index-Qz84U9Oq.d.ts +165 -0
- package/dist/index.cjs +3773 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +945 -0
- package/dist/index.d.ts +945 -0
- package/dist/index.js +3730 -0
- package/dist/index.js.map +1 -0
- package/dist/types/index.cjs +44 -0
- package/dist/types/index.cjs.map +1 -0
- package/dist/types/index.d.cts +5 -0
- package/dist/types/index.d.ts +5 -0
- package/dist/types/index.js +17 -0
- package/dist/types/index.js.map +1 -0
- package/dist/utils/index.cjs +83 -0
- package/dist/utils/index.cjs.map +1 -0
- package/dist/utils/index.d.cts +11 -0
- package/dist/utils/index.d.ts +11 -0
- package/dist/utils/index.js +56 -0
- package/dist/utils/index.js.map +1 -0
- package/dist/wallet/index.cjs +852 -0
- package/dist/wallet/index.cjs.map +1 -0
- package/dist/wallet/index.d.cts +726 -0
- package/dist/wallet/index.d.ts +726 -0
- package/dist/wallet/index.js +815 -0
- package/dist/wallet/index.js.map +1 -0
- package/package.json +119 -0
|
@@ -0,0 +1,815 @@
|
|
|
1
|
+
// src/wallet/types/index.ts
|
|
2
|
+
var WalletError = class extends Error {
|
|
3
|
+
constructor(message, code, context) {
|
|
4
|
+
super(message);
|
|
5
|
+
this.code = code;
|
|
6
|
+
this.context = context;
|
|
7
|
+
this.name = "WalletError";
|
|
8
|
+
}
|
|
9
|
+
};
|
|
10
|
+
var SessionKeyError = class extends WalletError {
|
|
11
|
+
constructor(message, context) {
|
|
12
|
+
super(message, "SESSION_KEY_ERROR", context);
|
|
13
|
+
this.name = "SessionKeyError";
|
|
14
|
+
}
|
|
15
|
+
};
|
|
16
|
+
var BatcherError = class extends WalletError {
|
|
17
|
+
constructor(message, context) {
|
|
18
|
+
super(message, "BATCHER_ERROR", context);
|
|
19
|
+
this.name = "BatcherError";
|
|
20
|
+
}
|
|
21
|
+
};
|
|
22
|
+
var EmbeddedWalletError = class extends WalletError {
|
|
23
|
+
constructor(message, context) {
|
|
24
|
+
super(message, "EMBEDDED_WALLET_ERROR", context);
|
|
25
|
+
this.name = "EmbeddedWalletError";
|
|
26
|
+
}
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
// src/wallet/batching/batcher.ts
|
|
30
|
+
var TransactionBatcher = class {
|
|
31
|
+
coreBatch = [];
|
|
32
|
+
evmBatch = [];
|
|
33
|
+
autoExecuteTimer = null;
|
|
34
|
+
options;
|
|
35
|
+
constructor(options = {}) {
|
|
36
|
+
this.options = {
|
|
37
|
+
maxBatchSize: options.maxBatchSize || 10,
|
|
38
|
+
autoExecuteTimeout: options.autoExecuteTimeout || 0,
|
|
39
|
+
// 0 = disabled
|
|
40
|
+
minGasPrice: options.minGasPrice || 0n
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Add transaction to batch
|
|
45
|
+
*
|
|
46
|
+
* @param tx - Transaction to add
|
|
47
|
+
* @returns Transaction ID
|
|
48
|
+
*/
|
|
49
|
+
addTransaction(tx) {
|
|
50
|
+
const transaction = {
|
|
51
|
+
id: `tx_${Date.now()}_${Math.random().toString(36).substring(7)}`,
|
|
52
|
+
...tx,
|
|
53
|
+
addedAt: /* @__PURE__ */ new Date()
|
|
54
|
+
};
|
|
55
|
+
const batch = tx.chain === "core" ? this.coreBatch : this.evmBatch;
|
|
56
|
+
batch.push(transaction);
|
|
57
|
+
if (this.options.autoExecuteTimeout > 0 && batch.length === 1) {
|
|
58
|
+
this.startAutoExecuteTimer(tx.chain);
|
|
59
|
+
}
|
|
60
|
+
if (batch.length >= this.options.maxBatchSize) {
|
|
61
|
+
console.log(
|
|
62
|
+
`Batch for ${tx.chain} is full (${batch.length} transactions)`
|
|
63
|
+
);
|
|
64
|
+
}
|
|
65
|
+
return transaction.id;
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Remove transaction from batch
|
|
69
|
+
*
|
|
70
|
+
* @param transactionId - Transaction ID
|
|
71
|
+
* @param chain - Chain type
|
|
72
|
+
* @returns true if removed, false if not found
|
|
73
|
+
*/
|
|
74
|
+
removeTransaction(transactionId, chain) {
|
|
75
|
+
const batch = chain === "core" ? this.coreBatch : this.evmBatch;
|
|
76
|
+
const index = batch.findIndex((tx) => tx.id === transactionId);
|
|
77
|
+
if (index !== -1) {
|
|
78
|
+
batch.splice(index, 1);
|
|
79
|
+
return true;
|
|
80
|
+
}
|
|
81
|
+
return false;
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Get pending transactions for a chain
|
|
85
|
+
*
|
|
86
|
+
* @param chain - Chain type
|
|
87
|
+
* @returns Array of pending transactions
|
|
88
|
+
*/
|
|
89
|
+
getPendingTransactions(chain) {
|
|
90
|
+
return chain === "core" ? [...this.coreBatch] : [...this.evmBatch];
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* Get batch statistics
|
|
94
|
+
*
|
|
95
|
+
* @param chain - Chain type
|
|
96
|
+
* @returns Batch statistics
|
|
97
|
+
*/
|
|
98
|
+
getBatchStats(chain) {
|
|
99
|
+
const batch = chain === "core" ? this.coreBatch : this.evmBatch;
|
|
100
|
+
return {
|
|
101
|
+
count: batch.length,
|
|
102
|
+
totalValue: batch.reduce((sum, tx) => sum + (tx.value || 0n), 0n),
|
|
103
|
+
avgGasLimit: batch.length > 0 ? batch.reduce((sum, tx) => sum + (tx.gasLimit || 0n), 0n) / BigInt(batch.length) : 0n,
|
|
104
|
+
oldestTransaction: batch[0]?.addedAt
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* Execute batch of transactions
|
|
109
|
+
*
|
|
110
|
+
* Note: This is a simplified implementation. In production, you would:
|
|
111
|
+
* - Use multicall contracts for actual batching
|
|
112
|
+
* - Handle gas estimation
|
|
113
|
+
* - Implement retry logic
|
|
114
|
+
* - Support different batching strategies (sequential, parallel, etc.)
|
|
115
|
+
*
|
|
116
|
+
* @param chain - Chain to execute on
|
|
117
|
+
* @param signer - Function to sign and send transactions
|
|
118
|
+
* @returns Batch execution result
|
|
119
|
+
*/
|
|
120
|
+
async executeBatch(chain, signer) {
|
|
121
|
+
const batch = chain === "core" ? this.coreBatch : this.evmBatch;
|
|
122
|
+
if (batch.length === 0) {
|
|
123
|
+
throw new BatcherError("No transactions in batch", { chain });
|
|
124
|
+
}
|
|
125
|
+
this.stopAutoExecuteTimer();
|
|
126
|
+
const batchId = `batch_${Date.now()}_${Math.random().toString(36).substring(7)}`;
|
|
127
|
+
const transactionHashes = [];
|
|
128
|
+
let successCount = 0;
|
|
129
|
+
let failureCount = 0;
|
|
130
|
+
let totalGasUsed = 0n;
|
|
131
|
+
for (const tx of batch) {
|
|
132
|
+
try {
|
|
133
|
+
if (signer) {
|
|
134
|
+
const hash = await signer(tx);
|
|
135
|
+
transactionHashes.push(hash);
|
|
136
|
+
successCount++;
|
|
137
|
+
totalGasUsed += tx.gasLimit || 21000n;
|
|
138
|
+
} else {
|
|
139
|
+
const hash = `0x${Array.from(
|
|
140
|
+
{ length: 64 },
|
|
141
|
+
() => Math.floor(Math.random() * 16).toString(16)
|
|
142
|
+
).join("")}`;
|
|
143
|
+
transactionHashes.push(hash);
|
|
144
|
+
successCount++;
|
|
145
|
+
totalGasUsed += tx.gasLimit || 21000n;
|
|
146
|
+
}
|
|
147
|
+
} catch (error) {
|
|
148
|
+
failureCount++;
|
|
149
|
+
console.error(`Transaction ${tx.id} failed:`, error);
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
if (chain === "core") {
|
|
153
|
+
this.coreBatch = [];
|
|
154
|
+
} else {
|
|
155
|
+
this.evmBatch = [];
|
|
156
|
+
}
|
|
157
|
+
return {
|
|
158
|
+
batchId,
|
|
159
|
+
transactionHashes,
|
|
160
|
+
successCount,
|
|
161
|
+
failureCount,
|
|
162
|
+
executedAt: /* @__PURE__ */ new Date(),
|
|
163
|
+
totalGasUsed,
|
|
164
|
+
chain
|
|
165
|
+
};
|
|
166
|
+
}
|
|
167
|
+
/**
|
|
168
|
+
* Clear all pending transactions
|
|
169
|
+
*
|
|
170
|
+
* @param chain - Chain to clear, or undefined to clear both
|
|
171
|
+
*/
|
|
172
|
+
clearBatch(chain) {
|
|
173
|
+
if (chain === "core" || chain === void 0) {
|
|
174
|
+
this.coreBatch = [];
|
|
175
|
+
}
|
|
176
|
+
if (chain === "evm" || chain === void 0) {
|
|
177
|
+
this.evmBatch = [];
|
|
178
|
+
}
|
|
179
|
+
this.stopAutoExecuteTimer();
|
|
180
|
+
}
|
|
181
|
+
/**
|
|
182
|
+
* Start auto-execute timer
|
|
183
|
+
*/
|
|
184
|
+
startAutoExecuteTimer(chain) {
|
|
185
|
+
if (this.options.autoExecuteTimeout <= 0) return;
|
|
186
|
+
this.stopAutoExecuteTimer();
|
|
187
|
+
this.autoExecuteTimer = setTimeout(() => {
|
|
188
|
+
const batch = chain === "core" ? this.coreBatch : this.evmBatch;
|
|
189
|
+
if (batch.length > 0) {
|
|
190
|
+
console.log(
|
|
191
|
+
`Auto-executing batch for ${chain} (${batch.length} transactions)`
|
|
192
|
+
);
|
|
193
|
+
}
|
|
194
|
+
}, this.options.autoExecuteTimeout);
|
|
195
|
+
}
|
|
196
|
+
/**
|
|
197
|
+
* Stop auto-execute timer
|
|
198
|
+
*/
|
|
199
|
+
stopAutoExecuteTimer() {
|
|
200
|
+
if (this.autoExecuteTimer) {
|
|
201
|
+
clearTimeout(this.autoExecuteTimer);
|
|
202
|
+
this.autoExecuteTimer = null;
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
/**
|
|
206
|
+
* Get batcher configuration
|
|
207
|
+
*/
|
|
208
|
+
getOptions() {
|
|
209
|
+
return { ...this.options };
|
|
210
|
+
}
|
|
211
|
+
/**
|
|
212
|
+
* Update batcher configuration
|
|
213
|
+
*/
|
|
214
|
+
updateOptions(options) {
|
|
215
|
+
this.options = {
|
|
216
|
+
...this.options,
|
|
217
|
+
...options
|
|
218
|
+
};
|
|
219
|
+
if (options.autoExecuteTimeout !== void 0) {
|
|
220
|
+
this.stopAutoExecuteTimer();
|
|
221
|
+
if (this.coreBatch.length > 0) {
|
|
222
|
+
this.startAutoExecuteTimer("core");
|
|
223
|
+
}
|
|
224
|
+
if (this.evmBatch.length > 0) {
|
|
225
|
+
this.startAutoExecuteTimer("evm");
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
};
|
|
230
|
+
|
|
231
|
+
// src/wallet/derivation.ts
|
|
232
|
+
import { HDKey } from "@scure/bip32";
|
|
233
|
+
import {
|
|
234
|
+
generateMnemonic as generateBip39Mnemonic,
|
|
235
|
+
mnemonicToSeedSync,
|
|
236
|
+
validateMnemonic as validateBip39Mnemonic
|
|
237
|
+
} from "@scure/bip39";
|
|
238
|
+
import { wordlist } from "@scure/bip39/wordlists/english.js";
|
|
239
|
+
import { privateKeyToAccount as civePrivateKeyToAccount } from "cive/accounts";
|
|
240
|
+
import { bytesToHex } from "viem";
|
|
241
|
+
import { privateKeyToAccount as viemPrivateKeyToAccount } from "viem/accounts";
|
|
242
|
+
|
|
243
|
+
// src/wallet/types.ts
|
|
244
|
+
var COIN_TYPES = {
|
|
245
|
+
/** Conflux Core Space - registered coin type 503 */
|
|
246
|
+
CONFLUX: 503,
|
|
247
|
+
/** Ethereum/eSpace - standard coin type 60 */
|
|
248
|
+
ETHEREUM: 60
|
|
249
|
+
};
|
|
250
|
+
var CORE_NETWORK_IDS = {
|
|
251
|
+
/** Local development network */
|
|
252
|
+
LOCAL: 2029,
|
|
253
|
+
/** Testnet */
|
|
254
|
+
TESTNET: 1,
|
|
255
|
+
/** Mainnet */
|
|
256
|
+
MAINNET: 1029
|
|
257
|
+
};
|
|
258
|
+
|
|
259
|
+
// src/wallet/derivation.ts
|
|
260
|
+
function generateMnemonic(strength = 128) {
|
|
261
|
+
return generateBip39Mnemonic(wordlist, strength);
|
|
262
|
+
}
|
|
263
|
+
function validateMnemonic(mnemonic) {
|
|
264
|
+
const normalizedMnemonic = mnemonic.trim().toLowerCase();
|
|
265
|
+
const words = normalizedMnemonic.split(/\s+/);
|
|
266
|
+
const wordCount = words.length;
|
|
267
|
+
if (wordCount !== 12 && wordCount !== 24) {
|
|
268
|
+
return {
|
|
269
|
+
valid: false,
|
|
270
|
+
wordCount,
|
|
271
|
+
error: `Invalid word count: ${wordCount}. Must be 12 or 24.`
|
|
272
|
+
};
|
|
273
|
+
}
|
|
274
|
+
const valid = validateBip39Mnemonic(normalizedMnemonic, wordlist);
|
|
275
|
+
return {
|
|
276
|
+
valid,
|
|
277
|
+
wordCount,
|
|
278
|
+
error: valid ? void 0 : "Invalid mnemonic: checksum verification failed"
|
|
279
|
+
};
|
|
280
|
+
}
|
|
281
|
+
function deriveAccounts(mnemonic, options) {
|
|
282
|
+
const {
|
|
283
|
+
count,
|
|
284
|
+
startIndex = 0,
|
|
285
|
+
coreNetworkId = CORE_NETWORK_IDS.LOCAL,
|
|
286
|
+
accountType = "standard"
|
|
287
|
+
} = options;
|
|
288
|
+
const validation = validateMnemonic(mnemonic);
|
|
289
|
+
if (!validation.valid) {
|
|
290
|
+
throw new Error(`Invalid mnemonic: ${validation.error}`);
|
|
291
|
+
}
|
|
292
|
+
const normalizedMnemonic = mnemonic.trim().toLowerCase();
|
|
293
|
+
const seed = mnemonicToSeedSync(normalizedMnemonic);
|
|
294
|
+
const masterKey = HDKey.fromMasterSeed(seed);
|
|
295
|
+
const accounts = [];
|
|
296
|
+
const accountTypeIndex = accountType === "standard" ? 0 : 1;
|
|
297
|
+
for (let i = startIndex; i < startIndex + count; i++) {
|
|
298
|
+
const corePath = `m/44'/${COIN_TYPES.CONFLUX}'/${accountTypeIndex}'/0/${i}`;
|
|
299
|
+
const coreKey = masterKey.derive(corePath);
|
|
300
|
+
const evmPath = `m/44'/${COIN_TYPES.ETHEREUM}'/${accountTypeIndex}'/0/${i}`;
|
|
301
|
+
const evmKey = masterKey.derive(evmPath);
|
|
302
|
+
if (!coreKey.privateKey || !evmKey.privateKey) {
|
|
303
|
+
throw new Error(`Failed to derive keys at index ${i}`);
|
|
304
|
+
}
|
|
305
|
+
const corePrivateKey = bytesToHex(coreKey.privateKey);
|
|
306
|
+
const evmPrivateKey = bytesToHex(evmKey.privateKey);
|
|
307
|
+
const coreAccount = civePrivateKeyToAccount(corePrivateKey, {
|
|
308
|
+
networkId: coreNetworkId
|
|
309
|
+
});
|
|
310
|
+
const evmAccount = viemPrivateKeyToAccount(evmPrivateKey);
|
|
311
|
+
accounts.push({
|
|
312
|
+
index: i,
|
|
313
|
+
coreAddress: coreAccount.address,
|
|
314
|
+
evmAddress: evmAccount.address,
|
|
315
|
+
corePrivateKey,
|
|
316
|
+
evmPrivateKey,
|
|
317
|
+
paths: {
|
|
318
|
+
core: corePath,
|
|
319
|
+
evm: evmPath
|
|
320
|
+
}
|
|
321
|
+
});
|
|
322
|
+
}
|
|
323
|
+
return accounts;
|
|
324
|
+
}
|
|
325
|
+
function deriveAccount(mnemonic, index, coreNetworkId = CORE_NETWORK_IDS.LOCAL, accountType = "standard") {
|
|
326
|
+
const accounts = deriveAccounts(mnemonic, {
|
|
327
|
+
count: 1,
|
|
328
|
+
startIndex: index,
|
|
329
|
+
coreNetworkId,
|
|
330
|
+
accountType
|
|
331
|
+
});
|
|
332
|
+
return accounts[0];
|
|
333
|
+
}
|
|
334
|
+
function deriveFaucetAccount(mnemonic, coreNetworkId = CORE_NETWORK_IDS.LOCAL) {
|
|
335
|
+
return deriveAccount(mnemonic, 0, coreNetworkId, "mining");
|
|
336
|
+
}
|
|
337
|
+
function getDerivationPath(coinType, index, accountType = "standard") {
|
|
338
|
+
const accountTypeIndex = accountType === "standard" ? 0 : 1;
|
|
339
|
+
return `m/44'/${coinType}'/${accountTypeIndex}'/0/${index}`;
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
// src/wallet/embedded/custody.ts
|
|
343
|
+
import { generatePrivateKey, privateKeyToAccount } from "viem/accounts";
|
|
344
|
+
var EmbeddedWalletManager = class {
|
|
345
|
+
wallets = /* @__PURE__ */ new Map();
|
|
346
|
+
options;
|
|
347
|
+
constructor(options = {}) {
|
|
348
|
+
this.options = {
|
|
349
|
+
algorithm: options.algorithm || "aes-256-gcm",
|
|
350
|
+
iterations: options.iterations || 1e5,
|
|
351
|
+
autoCreate: options.autoCreate !== false
|
|
352
|
+
// Default true
|
|
353
|
+
};
|
|
354
|
+
}
|
|
355
|
+
/**
|
|
356
|
+
* Create a new embedded wallet for a user
|
|
357
|
+
*
|
|
358
|
+
* @param userId - User identifier
|
|
359
|
+
* @param password - Encryption password
|
|
360
|
+
* @returns Created wallet (without private key)
|
|
361
|
+
*/
|
|
362
|
+
async createWallet(userId, password) {
|
|
363
|
+
if (this.wallets.has(userId)) {
|
|
364
|
+
throw new EmbeddedWalletError("Wallet already exists", { userId });
|
|
365
|
+
}
|
|
366
|
+
const privateKey = generatePrivateKey();
|
|
367
|
+
const account = privateKeyToAccount(privateKey);
|
|
368
|
+
const { encrypted, iv, salt } = await this.encryptPrivateKey(
|
|
369
|
+
privateKey,
|
|
370
|
+
password
|
|
371
|
+
);
|
|
372
|
+
const evmAddress = account.address;
|
|
373
|
+
const coreAddress = `cfx:${evmAddress.slice(2)}`;
|
|
374
|
+
const wallet = {
|
|
375
|
+
userId,
|
|
376
|
+
coreAddress,
|
|
377
|
+
evmAddress,
|
|
378
|
+
encryptedPrivateKey: encrypted,
|
|
379
|
+
encryption: {
|
|
380
|
+
algorithm: this.options.algorithm,
|
|
381
|
+
iv,
|
|
382
|
+
salt
|
|
383
|
+
},
|
|
384
|
+
createdAt: /* @__PURE__ */ new Date(),
|
|
385
|
+
lastAccessedAt: /* @__PURE__ */ new Date(),
|
|
386
|
+
isActive: true
|
|
387
|
+
};
|
|
388
|
+
this.wallets.set(userId, wallet);
|
|
389
|
+
const { encryptedPrivateKey: _, ...publicWallet } = wallet;
|
|
390
|
+
return publicWallet;
|
|
391
|
+
}
|
|
392
|
+
/**
|
|
393
|
+
* Get wallet info (without private key)
|
|
394
|
+
*
|
|
395
|
+
* @param userId - User identifier
|
|
396
|
+
* @returns Wallet info or undefined
|
|
397
|
+
*/
|
|
398
|
+
getWallet(userId) {
|
|
399
|
+
const wallet = this.wallets.get(userId);
|
|
400
|
+
if (!wallet) return void 0;
|
|
401
|
+
wallet.lastAccessedAt = /* @__PURE__ */ new Date();
|
|
402
|
+
const { encryptedPrivateKey: _, ...publicWallet } = wallet;
|
|
403
|
+
return publicWallet;
|
|
404
|
+
}
|
|
405
|
+
/**
|
|
406
|
+
* Check if user has a wallet
|
|
407
|
+
*
|
|
408
|
+
* @param userId - User identifier
|
|
409
|
+
* @returns true if wallet exists
|
|
410
|
+
*/
|
|
411
|
+
hasWallet(userId) {
|
|
412
|
+
return this.wallets.has(userId);
|
|
413
|
+
}
|
|
414
|
+
/**
|
|
415
|
+
* Sign transaction with user's embedded wallet
|
|
416
|
+
*
|
|
417
|
+
* @param userId - User identifier
|
|
418
|
+
* @param password - Decryption password
|
|
419
|
+
* @param request - Transaction request
|
|
420
|
+
* @returns Signed transaction
|
|
421
|
+
*/
|
|
422
|
+
async signTransaction(userId, password, request) {
|
|
423
|
+
const wallet = this.wallets.get(userId);
|
|
424
|
+
if (!wallet) {
|
|
425
|
+
throw new EmbeddedWalletError("Wallet not found", { userId });
|
|
426
|
+
}
|
|
427
|
+
if (!wallet.isActive) {
|
|
428
|
+
throw new EmbeddedWalletError("Wallet is not active", { userId });
|
|
429
|
+
}
|
|
430
|
+
const privateKey = await this.decryptPrivateKey(
|
|
431
|
+
wallet.encryptedPrivateKey,
|
|
432
|
+
password,
|
|
433
|
+
wallet.encryption.iv,
|
|
434
|
+
wallet.encryption.salt
|
|
435
|
+
);
|
|
436
|
+
const account = privateKeyToAccount(privateKey);
|
|
437
|
+
wallet.lastAccessedAt = /* @__PURE__ */ new Date();
|
|
438
|
+
const serialized = JSON.stringify({
|
|
439
|
+
from: account.address,
|
|
440
|
+
...request,
|
|
441
|
+
value: request.value?.toString(),
|
|
442
|
+
gasLimit: request.gasLimit?.toString(),
|
|
443
|
+
gasPrice: request.gasPrice?.toString()
|
|
444
|
+
});
|
|
445
|
+
const signature = await account.signMessage({
|
|
446
|
+
message: serialized
|
|
447
|
+
});
|
|
448
|
+
return {
|
|
449
|
+
rawTransaction: signature,
|
|
450
|
+
hash: `0x${Array.from(
|
|
451
|
+
{ length: 64 },
|
|
452
|
+
() => Math.floor(Math.random() * 16).toString(16)
|
|
453
|
+
).join("")}`,
|
|
454
|
+
from: account.address,
|
|
455
|
+
chain: request.chain
|
|
456
|
+
};
|
|
457
|
+
}
|
|
458
|
+
/**
|
|
459
|
+
* Export wallet for user backup
|
|
460
|
+
*
|
|
461
|
+
* @param userId - User identifier
|
|
462
|
+
* @param password - Encryption password
|
|
463
|
+
* @returns Encrypted wallet export
|
|
464
|
+
*/
|
|
465
|
+
async exportWallet(userId, password) {
|
|
466
|
+
const wallet = this.wallets.get(userId);
|
|
467
|
+
if (!wallet) {
|
|
468
|
+
throw new EmbeddedWalletError("Wallet not found", { userId });
|
|
469
|
+
}
|
|
470
|
+
const exportData = JSON.stringify({
|
|
471
|
+
coreAddress: wallet.coreAddress,
|
|
472
|
+
evmAddress: wallet.evmAddress,
|
|
473
|
+
encryptedPrivateKey: wallet.encryptedPrivateKey,
|
|
474
|
+
encryption: wallet.encryption
|
|
475
|
+
});
|
|
476
|
+
const { encrypted, iv, salt } = await this.encryptPrivateKey(
|
|
477
|
+
exportData,
|
|
478
|
+
password
|
|
479
|
+
);
|
|
480
|
+
return {
|
|
481
|
+
userId,
|
|
482
|
+
encryptedData: encrypted,
|
|
483
|
+
encryption: {
|
|
484
|
+
algorithm: this.options.algorithm,
|
|
485
|
+
iv,
|
|
486
|
+
salt
|
|
487
|
+
},
|
|
488
|
+
exportedAt: /* @__PURE__ */ new Date()
|
|
489
|
+
};
|
|
490
|
+
}
|
|
491
|
+
/**
|
|
492
|
+
* Deactivate wallet
|
|
493
|
+
*
|
|
494
|
+
* @param userId - User identifier
|
|
495
|
+
*/
|
|
496
|
+
deactivateWallet(userId) {
|
|
497
|
+
const wallet = this.wallets.get(userId);
|
|
498
|
+
if (wallet) {
|
|
499
|
+
wallet.isActive = false;
|
|
500
|
+
}
|
|
501
|
+
}
|
|
502
|
+
/**
|
|
503
|
+
* Delete wallet permanently
|
|
504
|
+
*
|
|
505
|
+
* WARNING: This operation cannot be undone
|
|
506
|
+
*
|
|
507
|
+
* @param userId - User identifier
|
|
508
|
+
* @returns true if deleted, false if not found
|
|
509
|
+
*/
|
|
510
|
+
deleteWallet(userId) {
|
|
511
|
+
return this.wallets.delete(userId);
|
|
512
|
+
}
|
|
513
|
+
/**
|
|
514
|
+
* List all wallets (without private keys)
|
|
515
|
+
*
|
|
516
|
+
* @returns Array of wallet info
|
|
517
|
+
*/
|
|
518
|
+
listWallets() {
|
|
519
|
+
return Array.from(this.wallets.values()).map((wallet) => {
|
|
520
|
+
const { encryptedPrivateKey: _, ...publicWallet } = wallet;
|
|
521
|
+
return publicWallet;
|
|
522
|
+
});
|
|
523
|
+
}
|
|
524
|
+
/**
|
|
525
|
+
* Get wallet statistics
|
|
526
|
+
*
|
|
527
|
+
* @returns Wallet statistics
|
|
528
|
+
*/
|
|
529
|
+
getStats() {
|
|
530
|
+
const all = Array.from(this.wallets.values());
|
|
531
|
+
const active = all.filter((w) => w.isActive);
|
|
532
|
+
return {
|
|
533
|
+
total: all.length,
|
|
534
|
+
active: active.length,
|
|
535
|
+
inactive: all.length - active.length
|
|
536
|
+
};
|
|
537
|
+
}
|
|
538
|
+
/**
|
|
539
|
+
* Encrypt private key
|
|
540
|
+
*
|
|
541
|
+
* NOTE: Simplified implementation for demonstration
|
|
542
|
+
* Production should use proper encryption (node:crypto, @noble/ciphers, etc.)
|
|
543
|
+
*/
|
|
544
|
+
async encryptPrivateKey(data, password) {
|
|
545
|
+
const iv = Array.from(
|
|
546
|
+
{ length: 16 },
|
|
547
|
+
() => Math.floor(Math.random() * 256).toString(16).padStart(2, "0")
|
|
548
|
+
).join("");
|
|
549
|
+
const salt = Array.from(
|
|
550
|
+
{ length: 32 },
|
|
551
|
+
() => Math.floor(Math.random() * 256).toString(16).padStart(2, "0")
|
|
552
|
+
).join("");
|
|
553
|
+
const mockEncrypted = Buffer.from(
|
|
554
|
+
JSON.stringify({ data, password, iv, salt })
|
|
555
|
+
).toString("base64");
|
|
556
|
+
return {
|
|
557
|
+
encrypted: mockEncrypted,
|
|
558
|
+
iv,
|
|
559
|
+
salt
|
|
560
|
+
};
|
|
561
|
+
}
|
|
562
|
+
/**
|
|
563
|
+
* Decrypt private key
|
|
564
|
+
*
|
|
565
|
+
* NOTE: Simplified implementation for demonstration
|
|
566
|
+
*/
|
|
567
|
+
async decryptPrivateKey(encrypted, password, _iv, _salt) {
|
|
568
|
+
try {
|
|
569
|
+
const decoded = JSON.parse(
|
|
570
|
+
Buffer.from(encrypted, "base64").toString("utf-8")
|
|
571
|
+
);
|
|
572
|
+
if (decoded.password !== password) {
|
|
573
|
+
throw new Error("Invalid password");
|
|
574
|
+
}
|
|
575
|
+
return decoded.data;
|
|
576
|
+
} catch (error) {
|
|
577
|
+
throw new EmbeddedWalletError("Failed to decrypt private key", {
|
|
578
|
+
error: error instanceof Error ? error.message : "Unknown error"
|
|
579
|
+
});
|
|
580
|
+
}
|
|
581
|
+
}
|
|
582
|
+
};
|
|
583
|
+
|
|
584
|
+
// src/wallet/session-keys/manager.ts
|
|
585
|
+
import { privateKeyToAccount as privateKeyToAccount2 } from "viem/accounts";
|
|
586
|
+
var SessionKeyManager = class {
|
|
587
|
+
sessionKeys = /* @__PURE__ */ new Map();
|
|
588
|
+
/**
|
|
589
|
+
* Generate a new session key
|
|
590
|
+
*
|
|
591
|
+
* @param parentAddress - Parent wallet address
|
|
592
|
+
* @param options - Session key configuration
|
|
593
|
+
* @returns Created session key
|
|
594
|
+
*/
|
|
595
|
+
generateSessionKey(parentAddress, options) {
|
|
596
|
+
const privateKey = `0x${Array.from(
|
|
597
|
+
{ length: 64 },
|
|
598
|
+
() => Math.floor(Math.random() * 16).toString(16)
|
|
599
|
+
).join("")}`;
|
|
600
|
+
const account = privateKeyToAccount2(privateKey);
|
|
601
|
+
const ttl = options.ttl || 3600;
|
|
602
|
+
const now = /* @__PURE__ */ new Date();
|
|
603
|
+
const expiresAt = new Date(now.getTime() + ttl * 1e3);
|
|
604
|
+
const sessionKey = {
|
|
605
|
+
id: `sk_${Date.now()}_${Math.random().toString(36).substring(7)}`,
|
|
606
|
+
privateKey,
|
|
607
|
+
address: account.address,
|
|
608
|
+
parentAddress,
|
|
609
|
+
ttl,
|
|
610
|
+
expiresAt,
|
|
611
|
+
permissions: options.permissions || {},
|
|
612
|
+
createdAt: now,
|
|
613
|
+
isActive: true,
|
|
614
|
+
chain: options.chain
|
|
615
|
+
};
|
|
616
|
+
this.sessionKeys.set(sessionKey.id, sessionKey);
|
|
617
|
+
return sessionKey;
|
|
618
|
+
}
|
|
619
|
+
/**
|
|
620
|
+
* Get session key by ID
|
|
621
|
+
*
|
|
622
|
+
* @param sessionKeyId - Session key identifier
|
|
623
|
+
* @returns Session key or undefined
|
|
624
|
+
*/
|
|
625
|
+
getSessionKey(sessionKeyId) {
|
|
626
|
+
const sessionKey = this.sessionKeys.get(sessionKeyId);
|
|
627
|
+
if (sessionKey && /* @__PURE__ */ new Date() > sessionKey.expiresAt) {
|
|
628
|
+
sessionKey.isActive = false;
|
|
629
|
+
}
|
|
630
|
+
return sessionKey;
|
|
631
|
+
}
|
|
632
|
+
/**
|
|
633
|
+
* Revoke a session key
|
|
634
|
+
*
|
|
635
|
+
* @param sessionKeyId - Session key identifier
|
|
636
|
+
*/
|
|
637
|
+
revokeSessionKey(sessionKeyId) {
|
|
638
|
+
const sessionKey = this.sessionKeys.get(sessionKeyId);
|
|
639
|
+
if (sessionKey) {
|
|
640
|
+
sessionKey.isActive = false;
|
|
641
|
+
}
|
|
642
|
+
}
|
|
643
|
+
/**
|
|
644
|
+
* List all session keys for a parent address
|
|
645
|
+
*
|
|
646
|
+
* @param parentAddress - Parent wallet address
|
|
647
|
+
* @returns Array of session keys
|
|
648
|
+
*/
|
|
649
|
+
listSessionKeys(parentAddress) {
|
|
650
|
+
return Array.from(this.sessionKeys.values()).filter(
|
|
651
|
+
(sk) => sk.parentAddress.toLowerCase() === parentAddress.toLowerCase()
|
|
652
|
+
);
|
|
653
|
+
}
|
|
654
|
+
/**
|
|
655
|
+
* List active session keys for a parent address
|
|
656
|
+
*
|
|
657
|
+
* @param parentAddress - Parent wallet address
|
|
658
|
+
* @returns Array of active session keys
|
|
659
|
+
*/
|
|
660
|
+
listActiveSessionKeys(parentAddress) {
|
|
661
|
+
return this.listSessionKeys(parentAddress).filter(
|
|
662
|
+
(sk) => sk.isActive && /* @__PURE__ */ new Date() <= sk.expiresAt
|
|
663
|
+
);
|
|
664
|
+
}
|
|
665
|
+
/**
|
|
666
|
+
* Validate transaction against session key permissions
|
|
667
|
+
*
|
|
668
|
+
* @param sessionKey - Session key
|
|
669
|
+
* @param request - Transaction request
|
|
670
|
+
* @throws SessionKeyError if validation fails
|
|
671
|
+
*/
|
|
672
|
+
validateTransaction(sessionKey, request) {
|
|
673
|
+
if (!sessionKey.isActive) {
|
|
674
|
+
throw new SessionKeyError("Session key is not active", {
|
|
675
|
+
sessionKeyId: sessionKey.id
|
|
676
|
+
});
|
|
677
|
+
}
|
|
678
|
+
if (/* @__PURE__ */ new Date() > sessionKey.expiresAt) {
|
|
679
|
+
throw new SessionKeyError("Session key has expired", {
|
|
680
|
+
sessionKeyId: sessionKey.id,
|
|
681
|
+
expiresAt: sessionKey.expiresAt
|
|
682
|
+
});
|
|
683
|
+
}
|
|
684
|
+
if (request.chain !== sessionKey.chain) {
|
|
685
|
+
throw new SessionKeyError("Chain mismatch", {
|
|
686
|
+
sessionKeyId: sessionKey.id,
|
|
687
|
+
allowedChain: sessionKey.chain,
|
|
688
|
+
requestedChain: request.chain
|
|
689
|
+
});
|
|
690
|
+
}
|
|
691
|
+
const { permissions } = sessionKey;
|
|
692
|
+
if (permissions.maxValue && request.value && request.value > permissions.maxValue) {
|
|
693
|
+
throw new SessionKeyError("Transaction value exceeds maximum", {
|
|
694
|
+
sessionKeyId: sessionKey.id,
|
|
695
|
+
maxValue: permissions.maxValue.toString(),
|
|
696
|
+
requestedValue: request.value.toString()
|
|
697
|
+
});
|
|
698
|
+
}
|
|
699
|
+
if (permissions.contracts && permissions.contracts.length > 0) {
|
|
700
|
+
const isWhitelisted = permissions.contracts.some(
|
|
701
|
+
(addr) => addr.toLowerCase() === request.to.toLowerCase()
|
|
702
|
+
);
|
|
703
|
+
if (!isWhitelisted) {
|
|
704
|
+
throw new SessionKeyError("Contract not whitelisted", {
|
|
705
|
+
sessionKeyId: sessionKey.id,
|
|
706
|
+
whitelistedContracts: permissions.contracts,
|
|
707
|
+
requestedContract: request.to
|
|
708
|
+
});
|
|
709
|
+
}
|
|
710
|
+
}
|
|
711
|
+
if (permissions.operations && permissions.operations.length > 0 && request.data) {
|
|
712
|
+
const selector = request.data.slice(0, 10);
|
|
713
|
+
const isAllowed = permissions.operations.some(
|
|
714
|
+
(op) => op.toLowerCase() === selector.toLowerCase()
|
|
715
|
+
);
|
|
716
|
+
if (!isAllowed) {
|
|
717
|
+
throw new SessionKeyError("Operation not allowed", {
|
|
718
|
+
sessionKeyId: sessionKey.id,
|
|
719
|
+
allowedOperations: permissions.operations,
|
|
720
|
+
requestedOperation: selector
|
|
721
|
+
});
|
|
722
|
+
}
|
|
723
|
+
}
|
|
724
|
+
}
|
|
725
|
+
/**
|
|
726
|
+
* Sign transaction with session key
|
|
727
|
+
*
|
|
728
|
+
* @param sessionKeyId - Session key identifier
|
|
729
|
+
* @param request - Transaction request
|
|
730
|
+
* @returns Signed transaction
|
|
731
|
+
* @throws SessionKeyError if session key is invalid or transaction violates permissions
|
|
732
|
+
*/
|
|
733
|
+
async signWithSessionKey(sessionKeyId, request) {
|
|
734
|
+
const sessionKey = this.sessionKeys.get(sessionKeyId);
|
|
735
|
+
if (!sessionKey) {
|
|
736
|
+
throw new SessionKeyError("Session key not found", { sessionKeyId });
|
|
737
|
+
}
|
|
738
|
+
this.validateTransaction(sessionKey, request);
|
|
739
|
+
const account = privateKeyToAccount2(sessionKey.privateKey);
|
|
740
|
+
const serialized = JSON.stringify({
|
|
741
|
+
from: account.address,
|
|
742
|
+
to: request.to,
|
|
743
|
+
value: request.value?.toString(),
|
|
744
|
+
data: request.data,
|
|
745
|
+
gasLimit: request.gasLimit?.toString(),
|
|
746
|
+
gasPrice: request.gasPrice?.toString(),
|
|
747
|
+
nonce: request.nonce,
|
|
748
|
+
chain: request.chain
|
|
749
|
+
});
|
|
750
|
+
const signature = await account.signMessage({
|
|
751
|
+
message: serialized
|
|
752
|
+
});
|
|
753
|
+
return {
|
|
754
|
+
rawTransaction: signature,
|
|
755
|
+
hash: `0x${Array.from(
|
|
756
|
+
{ length: 64 },
|
|
757
|
+
() => Math.floor(Math.random() * 16).toString(16)
|
|
758
|
+
).join("")}`,
|
|
759
|
+
from: account.address,
|
|
760
|
+
chain: request.chain
|
|
761
|
+
};
|
|
762
|
+
}
|
|
763
|
+
/**
|
|
764
|
+
* Clean up expired session keys
|
|
765
|
+
*
|
|
766
|
+
* @returns Number of removed session keys
|
|
767
|
+
*/
|
|
768
|
+
cleanupExpired() {
|
|
769
|
+
const now = /* @__PURE__ */ new Date();
|
|
770
|
+
let removed = 0;
|
|
771
|
+
for (const [id, sessionKey] of this.sessionKeys.entries()) {
|
|
772
|
+
if (now > sessionKey.expiresAt) {
|
|
773
|
+
this.sessionKeys.delete(id);
|
|
774
|
+
removed++;
|
|
775
|
+
}
|
|
776
|
+
}
|
|
777
|
+
return removed;
|
|
778
|
+
}
|
|
779
|
+
/**
|
|
780
|
+
* Get session key statistics
|
|
781
|
+
*
|
|
782
|
+
* @returns Statistics about session keys
|
|
783
|
+
*/
|
|
784
|
+
getStats() {
|
|
785
|
+
const all = Array.from(this.sessionKeys.values());
|
|
786
|
+
const active = all.filter(
|
|
787
|
+
(sk) => sk.isActive && /* @__PURE__ */ new Date() <= sk.expiresAt
|
|
788
|
+
);
|
|
789
|
+
const expired = all.filter((sk) => /* @__PURE__ */ new Date() > sk.expiresAt);
|
|
790
|
+
return {
|
|
791
|
+
total: all.length,
|
|
792
|
+
active: active.length,
|
|
793
|
+
expired: expired.length,
|
|
794
|
+
inactive: all.length - active.length - expired.length
|
|
795
|
+
};
|
|
796
|
+
}
|
|
797
|
+
};
|
|
798
|
+
export {
|
|
799
|
+
BatcherError,
|
|
800
|
+
COIN_TYPES,
|
|
801
|
+
CORE_NETWORK_IDS,
|
|
802
|
+
EmbeddedWalletError,
|
|
803
|
+
EmbeddedWalletManager,
|
|
804
|
+
SessionKeyError,
|
|
805
|
+
SessionKeyManager,
|
|
806
|
+
TransactionBatcher,
|
|
807
|
+
WalletError,
|
|
808
|
+
deriveAccount,
|
|
809
|
+
deriveAccounts,
|
|
810
|
+
deriveFaucetAccount,
|
|
811
|
+
generateMnemonic,
|
|
812
|
+
getDerivationPath,
|
|
813
|
+
validateMnemonic
|
|
814
|
+
};
|
|
815
|
+
//# sourceMappingURL=index.js.map
|