@hardkas/accounts 0.11.2-alpha → 0.11.4-alpha
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/authorizers-N2RUAVJV.js +10 -0
- package/dist/chunk-6DPO5V3N.js +90 -0
- package/dist/chunk-ILASLDZU.js +472 -0
- package/dist/chunk-MRXCDBKH.js +97 -0
- package/dist/chunk-TY5CXPHB.js +452 -0
- package/dist/chunk-WRRHW2WO.js +103 -0
- package/dist/chunk-YLG2NIAU.js +166 -0
- package/dist/dev-accounts-FU4XTSGF.js +18 -0
- package/dist/index.d.ts +54 -28
- package/dist/index.js +96 -1183
- package/dist/internal/wasm-rpc-serialization.d.ts +5 -0
- package/dist/internal/wasm-rpc-serialization.js +6 -0
- package/dist/keystore-CMWEKGBF.js +6 -0
- package/dist/resolve-EXYT2GAC.js +12 -0
- package/dist/{signer-backend-T4JT2RCK.js → signer-backend-W3LNCQA3.js} +1 -1
- package/package.json +16 -8
- package/dist/chunk-7QKDZQBR.js +0 -46
package/dist/index.js
CHANGED
|
@@ -1,8 +1,46 @@
|
|
|
1
|
+
import {
|
|
2
|
+
DEV_ACCOUNTS_PASSWORD,
|
|
3
|
+
KaspaWasmPrivateKeySigner,
|
|
4
|
+
assertSigningNetworkAllowed,
|
|
5
|
+
createDevSigner,
|
|
6
|
+
ensureDevAccounts,
|
|
7
|
+
getOrCreateDevAccount,
|
|
8
|
+
listDevAccountsSync
|
|
9
|
+
} from "./chunk-TY5CXPHB.js";
|
|
10
|
+
import "./chunk-WRRHW2WO.js";
|
|
11
|
+
import {
|
|
12
|
+
createEmptyRealAccountStore,
|
|
13
|
+
describeAccount,
|
|
14
|
+
getDefaultRealAccountsPath,
|
|
15
|
+
getRealDevAccount,
|
|
16
|
+
importRealDevAccount,
|
|
17
|
+
listHardkasAccounts,
|
|
18
|
+
listRealDevAccounts,
|
|
19
|
+
loadOrCreateRealAccountStore,
|
|
20
|
+
loadRealAccountStore,
|
|
21
|
+
loadRealAccountStoreSync,
|
|
22
|
+
removeRealDevAccount,
|
|
23
|
+
resolveHardkasAccount,
|
|
24
|
+
resolveHardkasAccountAddress,
|
|
25
|
+
resolveRealAccountOrAddress,
|
|
26
|
+
saveRealAccountStore,
|
|
27
|
+
validateAccountName,
|
|
28
|
+
validateAddressNetwork,
|
|
29
|
+
validateAddressPrefix
|
|
30
|
+
} from "./chunk-ILASLDZU.js";
|
|
1
31
|
import {
|
|
2
32
|
detectCapabilities,
|
|
3
33
|
getKaspaSigningBackendStatus,
|
|
4
34
|
loadKaspaWasm
|
|
5
|
-
} from "./chunk-
|
|
35
|
+
} from "./chunk-6DPO5V3N.js";
|
|
36
|
+
import {
|
|
37
|
+
KeystoreManager
|
|
38
|
+
} from "./chunk-YLG2NIAU.js";
|
|
39
|
+
import {
|
|
40
|
+
LazyAccountAuthorizer,
|
|
41
|
+
PrivateKeyAuthorizer,
|
|
42
|
+
StaticSignatureScriptAuthorizer
|
|
43
|
+
} from "./chunk-MRXCDBKH.js";
|
|
6
44
|
|
|
7
45
|
// src/simulated.ts
|
|
8
46
|
var SimulatedSigner = class {
|
|
@@ -19,458 +57,6 @@ var SimulatedSigner = class {
|
|
|
19
57
|
}
|
|
20
58
|
};
|
|
21
59
|
|
|
22
|
-
// src/resolve.ts
|
|
23
|
-
import fs2 from "fs";
|
|
24
|
-
import path2 from "path";
|
|
25
|
-
import { createDeterministicAccounts } from "@hardkas/localnet";
|
|
26
|
-
|
|
27
|
-
// src/real-accounts.ts
|
|
28
|
-
import fs from "fs";
|
|
29
|
-
import path from "path";
|
|
30
|
-
import { writeFileAtomicSync } from "@hardkas/core";
|
|
31
|
-
import {
|
|
32
|
-
HARDKAS_VERSION,
|
|
33
|
-
ARTIFACT_SCHEMAS,
|
|
34
|
-
ARTIFACT_VERSION
|
|
35
|
-
} from "@hardkas/artifacts";
|
|
36
|
-
function getDefaultRealAccountsPath(cwd) {
|
|
37
|
-
const root = cwd ?? process.cwd();
|
|
38
|
-
return path.join(root, ".hardkas", "accounts.real.json");
|
|
39
|
-
}
|
|
40
|
-
function createEmptyRealAccountStore() {
|
|
41
|
-
return {
|
|
42
|
-
schema: ARTIFACT_SCHEMAS.REAL_ACCOUNT_STORE,
|
|
43
|
-
hardkasVersion: HARDKAS_VERSION,
|
|
44
|
-
version: ARTIFACT_VERSION,
|
|
45
|
-
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
46
|
-
networkId: "simnet",
|
|
47
|
-
mode: "real",
|
|
48
|
-
connectionMode: "node",
|
|
49
|
-
warning: "HardKAS: Development account store. Encrypted storage is default. Unsafe plaintext storage is legacy.",
|
|
50
|
-
accounts: []
|
|
51
|
-
};
|
|
52
|
-
}
|
|
53
|
-
function loadRealAccountStoreSync(options) {
|
|
54
|
-
const filePath = options?.path || getDefaultRealAccountsPath(options?.cwd);
|
|
55
|
-
if (!fs.existsSync(filePath)) {
|
|
56
|
-
return null;
|
|
57
|
-
}
|
|
58
|
-
try {
|
|
59
|
-
const data = fs.readFileSync(filePath, "utf-8");
|
|
60
|
-
const store = JSON.parse(data);
|
|
61
|
-
for (const a of store.accounts) {
|
|
62
|
-
if ("privateKey" in a && a.privateKey !== void 0) {
|
|
63
|
-
if (a.privateKey === null || a.privateKey === "" || a.privateKey === "[object Object]" || typeof a.privateKey === "object" || typeof a.privateKey === "string" && a.privateKey.includes("__wbg_ptr") || typeof a.privateKey === "object" && "__wbg_ptr" in a.privateKey) {
|
|
64
|
-
const err = new Error(
|
|
65
|
-
"CORRUPTED_PRIVATE_KEY_SERIALIZATION: This account was generated by a broken alpha and must be regenerated. The private key was not recoverably stored."
|
|
66
|
-
);
|
|
67
|
-
err.code = "CORRUPTED_PRIVATE_KEY_SERIALIZATION";
|
|
68
|
-
throw err;
|
|
69
|
-
}
|
|
70
|
-
}
|
|
71
|
-
}
|
|
72
|
-
const plaintextAccounts = store.accounts.filter((a) => a.privateKey);
|
|
73
|
-
if (plaintextAccounts.length > 0) {
|
|
74
|
-
const names = plaintextAccounts.map((a) => a.name).join(", ");
|
|
75
|
-
console.warn(
|
|
76
|
-
`
|
|
77
|
-
\u26A0\uFE0F [SECURITY WARNING] Plaintext private keys detected in legacy account store for: ${names}`
|
|
78
|
-
);
|
|
79
|
-
console.warn(` Location: ${filePath}`);
|
|
80
|
-
console.warn(
|
|
81
|
-
` Recommendation: Re-import these accounts using encrypted keystores.
|
|
82
|
-
`
|
|
83
|
-
);
|
|
84
|
-
}
|
|
85
|
-
return store;
|
|
86
|
-
} catch (e) {
|
|
87
|
-
throw new Error(
|
|
88
|
-
`Failed to load real account store at ${filePath}: ${e instanceof Error ? e instanceof Error ? e instanceof Error ? e.message : String(e) : String(e) : String(e)}`
|
|
89
|
-
);
|
|
90
|
-
}
|
|
91
|
-
}
|
|
92
|
-
async function loadRealAccountStore(options) {
|
|
93
|
-
return loadRealAccountStoreSync(options);
|
|
94
|
-
}
|
|
95
|
-
async function loadOrCreateRealAccountStore(options) {
|
|
96
|
-
const store = await loadRealAccountStore(options);
|
|
97
|
-
if (store) return store;
|
|
98
|
-
const newStore = createEmptyRealAccountStore();
|
|
99
|
-
await saveRealAccountStore(newStore, options);
|
|
100
|
-
return newStore;
|
|
101
|
-
}
|
|
102
|
-
async function saveRealAccountStore(store, options) {
|
|
103
|
-
const filePath = options?.path || getDefaultRealAccountsPath(options?.cwd);
|
|
104
|
-
try {
|
|
105
|
-
writeFileAtomicSync(filePath, JSON.stringify(store, null, 2), {
|
|
106
|
-
encoding: "utf-8",
|
|
107
|
-
mode: 384
|
|
108
|
-
});
|
|
109
|
-
} catch (e) {
|
|
110
|
-
throw new Error(
|
|
111
|
-
`Failed to save real account store at ${filePath}: ${e instanceof Error ? e instanceof Error ? e instanceof Error ? e.message : String(e) : String(e) : String(e)}`
|
|
112
|
-
);
|
|
113
|
-
}
|
|
114
|
-
}
|
|
115
|
-
function validateAccountName(name) {
|
|
116
|
-
if (!name) {
|
|
117
|
-
throw new Error("Account name is required.");
|
|
118
|
-
}
|
|
119
|
-
const nameRegex = /^[a-zA-Z0-9_-]+$/;
|
|
120
|
-
if (!nameRegex.test(name)) {
|
|
121
|
-
throw new Error(
|
|
122
|
-
`Invalid account name '${name}'. Only letters, numbers, dashes and underscores are allowed.`
|
|
123
|
-
);
|
|
124
|
-
}
|
|
125
|
-
}
|
|
126
|
-
function validateAddressPrefix(address) {
|
|
127
|
-
if (!address) {
|
|
128
|
-
throw new Error("Address is required.");
|
|
129
|
-
}
|
|
130
|
-
const validPrefixes = ["kaspa:", "kaspatest:", "kaspasim:"];
|
|
131
|
-
const hasValidPrefix = validPrefixes.some((prefix) => address.startsWith(prefix));
|
|
132
|
-
if (!hasValidPrefix) {
|
|
133
|
-
const err = new Error(
|
|
134
|
-
`HARDKAS_INVALID_ADDRESS: Invalid address '${address}'. Must start with one of: ${validPrefixes.join(", ")}`
|
|
135
|
-
);
|
|
136
|
-
err.code = "HARDKAS_INVALID_ADDRESS";
|
|
137
|
-
throw err;
|
|
138
|
-
}
|
|
139
|
-
}
|
|
140
|
-
function validateAddressNetwork(address, networkId, allowMainnet) {
|
|
141
|
-
if (address.startsWith("kaspa:sim_")) {
|
|
142
|
-
return;
|
|
143
|
-
}
|
|
144
|
-
validateAddressPrefix(address);
|
|
145
|
-
let expectedPrefix;
|
|
146
|
-
if (networkId === "mainnet") {
|
|
147
|
-
expectedPrefix = "kaspa:";
|
|
148
|
-
} else if (networkId === "testnet-10" || networkId === "testnet-11") {
|
|
149
|
-
expectedPrefix = "kaspatest:";
|
|
150
|
-
} else if (networkId === "simnet" || networkId === "devnet" || networkId === "simulated") {
|
|
151
|
-
expectedPrefix = "kaspasim:";
|
|
152
|
-
} else {
|
|
153
|
-
return;
|
|
154
|
-
}
|
|
155
|
-
if (expectedPrefix !== "kaspa:" && address.startsWith("kaspa:") && allowMainnet) {
|
|
156
|
-
return;
|
|
157
|
-
}
|
|
158
|
-
if (!address.startsWith(expectedPrefix)) {
|
|
159
|
-
const err = new Error(
|
|
160
|
-
`NETWORK_ADDRESS_MISMATCH: Address '${address}' does not match the expected prefix '${expectedPrefix}' for network '${networkId}'.`
|
|
161
|
-
);
|
|
162
|
-
err.code = "NETWORK_ADDRESS_MISMATCH";
|
|
163
|
-
throw err;
|
|
164
|
-
}
|
|
165
|
-
}
|
|
166
|
-
function importRealDevAccount(store, account) {
|
|
167
|
-
validateAccountName(account.name);
|
|
168
|
-
validateAddressPrefix(account.address);
|
|
169
|
-
if (store.accounts.some((a) => a.name.toLowerCase() === account.name.toLowerCase())) {
|
|
170
|
-
throw new Error(`Account with name '${account.name}' already exists.`);
|
|
171
|
-
}
|
|
172
|
-
const newAccount = {
|
|
173
|
-
...account,
|
|
174
|
-
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
175
|
-
};
|
|
176
|
-
return {
|
|
177
|
-
...store,
|
|
178
|
-
accounts: [...store.accounts, newAccount]
|
|
179
|
-
};
|
|
180
|
-
}
|
|
181
|
-
function removeRealDevAccount(store, name) {
|
|
182
|
-
const index = store.accounts.findIndex(
|
|
183
|
-
(a) => a.name.toLowerCase() === name.toLowerCase()
|
|
184
|
-
);
|
|
185
|
-
if (index === -1) {
|
|
186
|
-
throw new Error(`Account with name '${name}' not found.`);
|
|
187
|
-
}
|
|
188
|
-
const newAccounts = [...store.accounts];
|
|
189
|
-
newAccounts.splice(index, 1);
|
|
190
|
-
return {
|
|
191
|
-
...store,
|
|
192
|
-
accounts: newAccounts
|
|
193
|
-
};
|
|
194
|
-
}
|
|
195
|
-
function getRealDevAccount(store, name) {
|
|
196
|
-
return store.accounts.find((a) => a.name.toLowerCase() === name.toLowerCase()) || null;
|
|
197
|
-
}
|
|
198
|
-
function listRealDevAccounts(store) {
|
|
199
|
-
return store.accounts;
|
|
200
|
-
}
|
|
201
|
-
function resolveRealAccountOrAddress(store, nameOrAddress) {
|
|
202
|
-
const account = store ? getRealDevAccount(store, nameOrAddress) : null;
|
|
203
|
-
if (account) {
|
|
204
|
-
return { address: account.address, name: account.name };
|
|
205
|
-
}
|
|
206
|
-
if (nameOrAddress.startsWith("kaspa:") || nameOrAddress.startsWith("kaspatest:") || nameOrAddress.startsWith("kaspasim:")) {
|
|
207
|
-
return { address: nameOrAddress };
|
|
208
|
-
}
|
|
209
|
-
throw new Error(
|
|
210
|
-
`'${nameOrAddress}' is not a registered real account name and is not a valid Kaspa address.`
|
|
211
|
-
);
|
|
212
|
-
}
|
|
213
|
-
|
|
214
|
-
// src/resolve.ts
|
|
215
|
-
function resolveHardkasAccount(options) {
|
|
216
|
-
const { nameOrAddress, config } = options;
|
|
217
|
-
if (nameOrAddress.startsWith("kaspa:") || nameOrAddress.startsWith("kaspatest:") || nameOrAddress.startsWith("kaspasim:")) {
|
|
218
|
-
return {
|
|
219
|
-
name: nameOrAddress,
|
|
220
|
-
kind: "external-wallet",
|
|
221
|
-
address: nameOrAddress
|
|
222
|
-
};
|
|
223
|
-
}
|
|
224
|
-
let alias = nameOrAddress;
|
|
225
|
-
if (alias === "0") alias = "alice";
|
|
226
|
-
if (alias === "1") alias = "bob";
|
|
227
|
-
const workspaceRoot = config?.cwd || process.cwd();
|
|
228
|
-
const devAccountPath = path2.join(
|
|
229
|
-
workspaceRoot,
|
|
230
|
-
".hardkas",
|
|
231
|
-
"dev-accounts",
|
|
232
|
-
`${alias}.json`
|
|
233
|
-
);
|
|
234
|
-
if (fs2.existsSync(devAccountPath)) {
|
|
235
|
-
try {
|
|
236
|
-
const data = fs2.readFileSync(devAccountPath, "utf-8");
|
|
237
|
-
const keystore = JSON.parse(data);
|
|
238
|
-
if (keystore.type === "hardkas.encryptedKeystore.v2") {
|
|
239
|
-
return {
|
|
240
|
-
name: alias,
|
|
241
|
-
kind: "kaspa-private-key",
|
|
242
|
-
address: keystore.metadata?.address,
|
|
243
|
-
keystorePath: devAccountPath
|
|
244
|
-
};
|
|
245
|
-
}
|
|
246
|
-
} catch (e) {
|
|
247
|
-
}
|
|
248
|
-
}
|
|
249
|
-
const keystoreJsonPath = path2.join(workspaceRoot, ".hardkas", "keystore.json");
|
|
250
|
-
if (fs2.existsSync(keystoreJsonPath)) {
|
|
251
|
-
try {
|
|
252
|
-
const data = fs2.readFileSync(keystoreJsonPath, "utf-8");
|
|
253
|
-
const ks = JSON.parse(data);
|
|
254
|
-
if (ks[alias]) {
|
|
255
|
-
return {
|
|
256
|
-
name: alias,
|
|
257
|
-
kind: ks[alias].type === "simulated" ? "simulated" : "kaspa-private-key",
|
|
258
|
-
address: ks[alias].address
|
|
259
|
-
};
|
|
260
|
-
}
|
|
261
|
-
} catch (e) {
|
|
262
|
-
}
|
|
263
|
-
}
|
|
264
|
-
if (config?.accounts && config.accounts[alias]) {
|
|
265
|
-
const accConfig = config.accounts[alias];
|
|
266
|
-
return {
|
|
267
|
-
name: alias,
|
|
268
|
-
...accConfig
|
|
269
|
-
};
|
|
270
|
-
}
|
|
271
|
-
const realStore = loadRealAccountStoreSync({ cwd: workspaceRoot });
|
|
272
|
-
const realAcc = realStore ? getRealDevAccount(realStore, alias) : null;
|
|
273
|
-
if (realAcc) {
|
|
274
|
-
return {
|
|
275
|
-
name: realAcc.name,
|
|
276
|
-
kind: "kaspa-private-key",
|
|
277
|
-
// Assuming Kaspa for now, could be extensible
|
|
278
|
-
address: realAcc.address,
|
|
279
|
-
...realAcc.privateKeyEnv ? { privateKeyEnv: realAcc.privateKeyEnv } : {},
|
|
280
|
-
...realAcc.privateKey ? { privateKey: realAcc.privateKey } : {}
|
|
281
|
-
};
|
|
282
|
-
}
|
|
283
|
-
const detAccounts = createDeterministicAccounts();
|
|
284
|
-
const det = detAccounts.find((a) => a.name === alias);
|
|
285
|
-
if (det) {
|
|
286
|
-
return {
|
|
287
|
-
name: det.name,
|
|
288
|
-
kind: "simulated",
|
|
289
|
-
address: det.address,
|
|
290
|
-
evmAddress: det.evmAddress
|
|
291
|
-
};
|
|
292
|
-
}
|
|
293
|
-
const available = listHardkasAccounts(config).map((a) => a.name).join(", ");
|
|
294
|
-
throw new Error(
|
|
295
|
-
`Unknown HardKAS account '${nameOrAddress}'. Available accounts: ${available}`
|
|
296
|
-
);
|
|
297
|
-
}
|
|
298
|
-
function listHardkasAccounts(config) {
|
|
299
|
-
const accounts = /* @__PURE__ */ new Map();
|
|
300
|
-
const detAccounts = createDeterministicAccounts();
|
|
301
|
-
for (const det of detAccounts) {
|
|
302
|
-
accounts.set(det.name, {
|
|
303
|
-
name: det.name,
|
|
304
|
-
kind: "simulated",
|
|
305
|
-
address: det.address,
|
|
306
|
-
evmAddress: det.evmAddress
|
|
307
|
-
});
|
|
308
|
-
}
|
|
309
|
-
const workspaceRoot = config?.cwd || process.cwd();
|
|
310
|
-
const devAccountsDir = path2.join(workspaceRoot, ".hardkas", "dev-accounts");
|
|
311
|
-
if (fs2.existsSync(devAccountsDir)) {
|
|
312
|
-
const files = fs2.readdirSync(devAccountsDir);
|
|
313
|
-
for (const file of files) {
|
|
314
|
-
if (file.endsWith(".json")) {
|
|
315
|
-
try {
|
|
316
|
-
const name = path2.basename(file, ".json");
|
|
317
|
-
const data = fs2.readFileSync(path2.join(devAccountsDir, file), "utf-8");
|
|
318
|
-
const keystore = JSON.parse(data);
|
|
319
|
-
if (keystore.type === "hardkas.encryptedKeystore.v2") {
|
|
320
|
-
accounts.set(name, {
|
|
321
|
-
name,
|
|
322
|
-
kind: "kaspa-private-key",
|
|
323
|
-
address: keystore.payload?.address || keystore.metadata?.address,
|
|
324
|
-
keystorePath: path2.join(devAccountsDir, file)
|
|
325
|
-
});
|
|
326
|
-
}
|
|
327
|
-
} catch (e) {
|
|
328
|
-
}
|
|
329
|
-
}
|
|
330
|
-
}
|
|
331
|
-
}
|
|
332
|
-
const keystoreJsonPath = path2.join(workspaceRoot, ".hardkas", "keystore.json");
|
|
333
|
-
if (fs2.existsSync(keystoreJsonPath)) {
|
|
334
|
-
try {
|
|
335
|
-
const data = fs2.readFileSync(keystoreJsonPath, "utf-8");
|
|
336
|
-
const ks = JSON.parse(data);
|
|
337
|
-
for (const [name, acc] of Object.entries(ks)) {
|
|
338
|
-
if (acc.type === "simulated") {
|
|
339
|
-
accounts.set(name, {
|
|
340
|
-
name,
|
|
341
|
-
kind: "simulated",
|
|
342
|
-
address: acc.address
|
|
343
|
-
});
|
|
344
|
-
} else {
|
|
345
|
-
accounts.set(name, {
|
|
346
|
-
name,
|
|
347
|
-
kind: "kaspa-private-key",
|
|
348
|
-
address: acc.address
|
|
349
|
-
});
|
|
350
|
-
}
|
|
351
|
-
}
|
|
352
|
-
} catch (e) {
|
|
353
|
-
}
|
|
354
|
-
}
|
|
355
|
-
const realStore = loadRealAccountStoreSync({ cwd: workspaceRoot });
|
|
356
|
-
if (realStore) {
|
|
357
|
-
for (const realAcc of listRealDevAccounts(realStore)) {
|
|
358
|
-
accounts.set(realAcc.name, {
|
|
359
|
-
name: realAcc.name,
|
|
360
|
-
kind: "kaspa-private-key",
|
|
361
|
-
address: realAcc.address,
|
|
362
|
-
...realAcc.privateKeyEnv ? { privateKeyEnv: realAcc.privateKeyEnv } : {},
|
|
363
|
-
...realAcc.privateKey ? { privateKey: realAcc.privateKey } : {}
|
|
364
|
-
});
|
|
365
|
-
}
|
|
366
|
-
}
|
|
367
|
-
const keystoreDir = path2.join(process.cwd(), ".hardkas", "keystore");
|
|
368
|
-
if (fs2.existsSync(keystoreDir)) {
|
|
369
|
-
const files = fs2.readdirSync(keystoreDir);
|
|
370
|
-
for (const file of files) {
|
|
371
|
-
if (file.endsWith(".json")) {
|
|
372
|
-
try {
|
|
373
|
-
const name = path2.basename(file, ".json");
|
|
374
|
-
const data = fs2.readFileSync(path2.join(keystoreDir, file), "utf-8");
|
|
375
|
-
const keystore = JSON.parse(data);
|
|
376
|
-
if (keystore.type === "hardkas.encryptedKeystore.v2") {
|
|
377
|
-
accounts.set(name, {
|
|
378
|
-
name,
|
|
379
|
-
kind: "kaspa-private-key",
|
|
380
|
-
address: keystore.payload?.address || keystore.metadata?.address,
|
|
381
|
-
// Payloads are encrypted, but address might be in metadata
|
|
382
|
-
keystorePath: path2.join(keystoreDir, file)
|
|
383
|
-
});
|
|
384
|
-
}
|
|
385
|
-
} catch (e) {
|
|
386
|
-
}
|
|
387
|
-
}
|
|
388
|
-
}
|
|
389
|
-
}
|
|
390
|
-
if (config?.accounts) {
|
|
391
|
-
for (const [name, accConfig] of Object.entries(config.accounts)) {
|
|
392
|
-
const existing = accounts.get(name);
|
|
393
|
-
if (existing && existing.kind === "kaspa-private-key" && accConfig.kind === "simulated") {
|
|
394
|
-
continue;
|
|
395
|
-
}
|
|
396
|
-
accounts.set(name, {
|
|
397
|
-
name,
|
|
398
|
-
...accConfig
|
|
399
|
-
});
|
|
400
|
-
}
|
|
401
|
-
}
|
|
402
|
-
return Array.from(accounts.values());
|
|
403
|
-
}
|
|
404
|
-
async function resolveHardkasAccountAddress(accountOrAddress, config, context = "L1") {
|
|
405
|
-
if (accountOrAddress.startsWith("kaspa:") || accountOrAddress.startsWith("kaspatest:") || accountOrAddress.startsWith("kaspasim:")) {
|
|
406
|
-
if (context === "L2") {
|
|
407
|
-
throw new Error(
|
|
408
|
-
`Invalid L2 address provided: ${accountOrAddress}. Expected EVM address or account alias.`
|
|
409
|
-
);
|
|
410
|
-
}
|
|
411
|
-
if (!accountOrAddress.startsWith("kaspa:sim_")) {
|
|
412
|
-
try {
|
|
413
|
-
const kaspa = await import("kaspa-wasm");
|
|
414
|
-
try {
|
|
415
|
-
if (typeof kaspa.Address === "function" || kaspa.Address) {
|
|
416
|
-
new kaspa.Address(accountOrAddress);
|
|
417
|
-
}
|
|
418
|
-
} catch (e) {
|
|
419
|
-
const err = new Error(
|
|
420
|
-
`HARDKAS_INVALID_ADDRESS: Invalid Kaspa address format or checksum.`
|
|
421
|
-
);
|
|
422
|
-
err.code = "HARDKAS_INVALID_ADDRESS";
|
|
423
|
-
throw err;
|
|
424
|
-
}
|
|
425
|
-
} catch (e) {
|
|
426
|
-
if (e instanceof Error && e.code === "HARDKAS_INVALID_ADDRESS") throw e;
|
|
427
|
-
if (e instanceof Error && (e.code === "ERR_MODULE_NOT_FOUND" || (e instanceof Error ? e instanceof Error ? e.message : String(e) : String(e)).includes("Cannot find module") || (e instanceof Error ? e instanceof Error ? e.message : String(e) : String(e)).includes("kaspa-wasm"))) {
|
|
428
|
-
const err = new Error(
|
|
429
|
-
"ADDRESS_VALIDATOR_UNAVAILABLE: The Kaspa address validator backend is not available."
|
|
430
|
-
);
|
|
431
|
-
err.code = "ADDRESS_VALIDATOR_UNAVAILABLE";
|
|
432
|
-
throw err;
|
|
433
|
-
}
|
|
434
|
-
throw e;
|
|
435
|
-
}
|
|
436
|
-
}
|
|
437
|
-
return accountOrAddress;
|
|
438
|
-
}
|
|
439
|
-
if (accountOrAddress.startsWith("0x") && accountOrAddress.length === 42) {
|
|
440
|
-
return accountOrAddress;
|
|
441
|
-
}
|
|
442
|
-
const account = resolveHardkasAccount({ nameOrAddress: accountOrAddress, config });
|
|
443
|
-
if (context === "L2") {
|
|
444
|
-
const evmAddress = account.evmAddress;
|
|
445
|
-
if (!evmAddress) {
|
|
446
|
-
throw new Error(
|
|
447
|
-
`Account '${account.name}' does not have an EVM address configured for L2.`
|
|
448
|
-
);
|
|
449
|
-
}
|
|
450
|
-
return evmAddress;
|
|
451
|
-
}
|
|
452
|
-
if (!account.address) {
|
|
453
|
-
throw new Error(`Account '${account.name}' does not have a resolved address yet.`);
|
|
454
|
-
}
|
|
455
|
-
return account.address;
|
|
456
|
-
}
|
|
457
|
-
function describeAccount(account) {
|
|
458
|
-
const desc = {
|
|
459
|
-
name: account.name,
|
|
460
|
-
kind: account.kind
|
|
461
|
-
};
|
|
462
|
-
if (account.address) {
|
|
463
|
-
desc.address = account.address;
|
|
464
|
-
}
|
|
465
|
-
if (account.kind === "kaspa-private-key" || account.kind === "evm-private-key") {
|
|
466
|
-
desc.privateKeyEnv = account.privateKeyEnv;
|
|
467
|
-
}
|
|
468
|
-
if (account.kind === "external-wallet" && account.walletId) {
|
|
469
|
-
desc.walletId = account.walletId;
|
|
470
|
-
}
|
|
471
|
-
return desc;
|
|
472
|
-
}
|
|
473
|
-
|
|
474
60
|
// src/evm-export.ts
|
|
475
61
|
async function prepareEvmAccountExport(account, networkId, options = {}) {
|
|
476
62
|
if (networkId === "mainnet" || networkId.startsWith("testnet")) {
|
|
@@ -534,516 +120,11 @@ function getRequiredEnv(name) {
|
|
|
534
120
|
// src/signer.ts
|
|
535
121
|
import {
|
|
536
122
|
createSimulatedSignedTxArtifact,
|
|
537
|
-
calculateContentHash
|
|
538
|
-
HARDKAS_VERSION
|
|
123
|
+
calculateContentHash,
|
|
124
|
+
HARDKAS_VERSION,
|
|
539
125
|
createLineageTransition
|
|
540
126
|
} from "@hardkas/artifacts";
|
|
541
127
|
import { systemRuntimeContext } from "@hardkas/core";
|
|
542
|
-
|
|
543
|
-
// src/kaspa-wasm-signer.ts
|
|
544
|
-
import { calculateContentHash } from "@hardkas/artifacts";
|
|
545
|
-
|
|
546
|
-
// src/keystore.ts
|
|
547
|
-
import fs3 from "fs";
|
|
548
|
-
import path3 from "path";
|
|
549
|
-
import crypto from "crypto";
|
|
550
|
-
import { argon2id } from "hash-wasm";
|
|
551
|
-
import { writeFileAtomic } from "@hardkas/core";
|
|
552
|
-
var KeystoreManager = class {
|
|
553
|
-
/**
|
|
554
|
-
* Keystore container format version. Separate from ARTIFACT_VERSION.
|
|
555
|
-
* This versions the encrypted keystore envelope, not HardKAS artifacts.
|
|
556
|
-
*/
|
|
557
|
-
static KEYSTORE_FORMAT_VERSION = "2.0.0";
|
|
558
|
-
static KEYSTORE_FORMAT_TYPE = "hardkas.encryptedKeystore.v2";
|
|
559
|
-
/**
|
|
560
|
-
* Creates an encrypted keystore from a payload and password.
|
|
561
|
-
*/
|
|
562
|
-
static async createEncryptedKeystore(payload, password, options) {
|
|
563
|
-
if (!password) throw new Error("Password cannot be empty.");
|
|
564
|
-
if (password.length < 8)
|
|
565
|
-
throw new Error("Password must be at least 8 characters long.");
|
|
566
|
-
const salt = crypto.randomBytes(16);
|
|
567
|
-
const nonce = crypto.randomBytes(12);
|
|
568
|
-
const iterations = options.iterations || 3;
|
|
569
|
-
const memory = options.memory || 65536;
|
|
570
|
-
const parallelism = options.parallelism || 1;
|
|
571
|
-
const derivedKeyHex = await argon2id({
|
|
572
|
-
password,
|
|
573
|
-
salt,
|
|
574
|
-
parallelism,
|
|
575
|
-
iterations,
|
|
576
|
-
memorySize: memory,
|
|
577
|
-
hashLength: 32,
|
|
578
|
-
// 256 bits for AES-256
|
|
579
|
-
outputType: "hex"
|
|
580
|
-
});
|
|
581
|
-
const derivedKey = Buffer.from(derivedKeyHex, "hex");
|
|
582
|
-
const cipher = crypto.createCipheriv("aes-256-gcm", derivedKey, nonce);
|
|
583
|
-
const encryptedPayload = Buffer.concat([
|
|
584
|
-
cipher.update(JSON.stringify(payload), "utf8"),
|
|
585
|
-
cipher.final()
|
|
586
|
-
]);
|
|
587
|
-
const tag = cipher.getAuthTag();
|
|
588
|
-
derivedKey.fill(0);
|
|
589
|
-
return {
|
|
590
|
-
version: this.KEYSTORE_FORMAT_VERSION,
|
|
591
|
-
type: this.KEYSTORE_FORMAT_TYPE,
|
|
592
|
-
kdf: {
|
|
593
|
-
algorithm: "argon2id",
|
|
594
|
-
memory,
|
|
595
|
-
iterations,
|
|
596
|
-
parallelism,
|
|
597
|
-
salt: salt.toString("base64")
|
|
598
|
-
},
|
|
599
|
-
cipher: {
|
|
600
|
-
algorithm: "aes-256-gcm",
|
|
601
|
-
nonce: nonce.toString("base64"),
|
|
602
|
-
tag: tag.toString("base64")
|
|
603
|
-
},
|
|
604
|
-
encryptedPayload: encryptedPayload.toString("base64"),
|
|
605
|
-
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
606
|
-
metadata: {
|
|
607
|
-
label: options.label,
|
|
608
|
-
network: options.network,
|
|
609
|
-
address: payload.address
|
|
610
|
-
}
|
|
611
|
-
};
|
|
612
|
-
}
|
|
613
|
-
/**
|
|
614
|
-
* Decrypts an encrypted keystore using a password.
|
|
615
|
-
*/
|
|
616
|
-
static async decryptEncryptedKeystore(keystore, password) {
|
|
617
|
-
if (keystore.version !== this.KEYSTORE_FORMAT_VERSION) {
|
|
618
|
-
return {
|
|
619
|
-
success: false,
|
|
620
|
-
error: `Unsupported keystore version: ${keystore.version}`
|
|
621
|
-
};
|
|
622
|
-
}
|
|
623
|
-
try {
|
|
624
|
-
const salt = Buffer.from(keystore.kdf.salt, "base64");
|
|
625
|
-
const nonce = Buffer.from(keystore.cipher.nonce, "base64");
|
|
626
|
-
const tag = Buffer.from(keystore.cipher.tag, "base64");
|
|
627
|
-
const encryptedData = Buffer.from(keystore.encryptedPayload, "base64");
|
|
628
|
-
const derivedKeyHex = await argon2id({
|
|
629
|
-
password,
|
|
630
|
-
salt,
|
|
631
|
-
parallelism: keystore.kdf.parallelism,
|
|
632
|
-
iterations: keystore.kdf.iterations,
|
|
633
|
-
memorySize: keystore.kdf.memory,
|
|
634
|
-
hashLength: 32,
|
|
635
|
-
outputType: "hex"
|
|
636
|
-
});
|
|
637
|
-
const derivedKey = Buffer.from(derivedKeyHex, "hex");
|
|
638
|
-
const decipher = crypto.createDecipheriv("aes-256-gcm", derivedKey, nonce);
|
|
639
|
-
decipher.setAuthTag(tag);
|
|
640
|
-
const decrypted = Buffer.concat([decipher.update(encryptedData), decipher.final()]);
|
|
641
|
-
derivedKey.fill(0);
|
|
642
|
-
const payload = JSON.parse(decrypted.toString("utf8"));
|
|
643
|
-
return { success: true, payload };
|
|
644
|
-
} catch (e) {
|
|
645
|
-
return { success: false, error: "Invalid password or corrupted keystore." };
|
|
646
|
-
}
|
|
647
|
-
}
|
|
648
|
-
/**
|
|
649
|
-
* Verifies if the password is correct for the keystore.
|
|
650
|
-
*/
|
|
651
|
-
static async verifyKeystorePassword(keystore, password) {
|
|
652
|
-
const result = await this.decryptEncryptedKeystore(keystore, password);
|
|
653
|
-
return result.success;
|
|
654
|
-
}
|
|
655
|
-
/**
|
|
656
|
-
* Changes the password of an encrypted keystore.
|
|
657
|
-
*/
|
|
658
|
-
static async changeKeystorePassword(keystore, oldPassword, newPassword) {
|
|
659
|
-
const unlock = await this.decryptEncryptedKeystore(keystore, oldPassword);
|
|
660
|
-
if (!unlock.success || !unlock.payload) {
|
|
661
|
-
throw new Error("Invalid current password.");
|
|
662
|
-
}
|
|
663
|
-
return this.createEncryptedKeystore(unlock.payload, newPassword, {
|
|
664
|
-
label: keystore.metadata.label,
|
|
665
|
-
network: keystore.metadata.network,
|
|
666
|
-
iterations: keystore.kdf.iterations,
|
|
667
|
-
memory: keystore.kdf.memory,
|
|
668
|
-
parallelism: keystore.kdf.parallelism
|
|
669
|
-
});
|
|
670
|
-
}
|
|
671
|
-
/**
|
|
672
|
-
* Loads an encrypted keystore from the filesystem.
|
|
673
|
-
*/
|
|
674
|
-
static async loadEncryptedKeystore(filePath) {
|
|
675
|
-
try {
|
|
676
|
-
const data = await fs3.promises.readFile(filePath, "utf-8");
|
|
677
|
-
const keystore = JSON.parse(data);
|
|
678
|
-
if (keystore.type !== this.KEYSTORE_FORMAT_TYPE) {
|
|
679
|
-
throw new Error(`Invalid keystore type: ${keystore.type}`);
|
|
680
|
-
}
|
|
681
|
-
return keystore;
|
|
682
|
-
} catch (e) {
|
|
683
|
-
throw new Error(
|
|
684
|
-
`Failed to load keystore at ${filePath}: ${e instanceof Error ? e instanceof Error ? e instanceof Error ? e.message : String(e) : String(e) : String(e)}`
|
|
685
|
-
);
|
|
686
|
-
}
|
|
687
|
-
}
|
|
688
|
-
/**
|
|
689
|
-
* Saves an encrypted keystore to the filesystem.
|
|
690
|
-
*/
|
|
691
|
-
static async saveEncryptedKeystore(filePath, keystore) {
|
|
692
|
-
try {
|
|
693
|
-
const dir = path3.dirname(filePath);
|
|
694
|
-
if (!fs3.existsSync(dir)) {
|
|
695
|
-
await fs3.promises.mkdir(dir, { recursive: true });
|
|
696
|
-
}
|
|
697
|
-
await writeFileAtomic(filePath, JSON.stringify(keystore, null, 2), {
|
|
698
|
-
encoding: "utf-8",
|
|
699
|
-
mode: 384
|
|
700
|
-
});
|
|
701
|
-
} catch (e) {
|
|
702
|
-
throw new Error(
|
|
703
|
-
`Failed to save keystore at ${filePath}: ${e instanceof Error ? e instanceof Error ? e instanceof Error ? e.message : String(e) : String(e) : String(e)}`
|
|
704
|
-
);
|
|
705
|
-
}
|
|
706
|
-
}
|
|
707
|
-
};
|
|
708
|
-
|
|
709
|
-
// src/dev-accounts.ts
|
|
710
|
-
import fs4 from "fs";
|
|
711
|
-
import path4 from "path";
|
|
712
|
-
import crypto2 from "crypto";
|
|
713
|
-
import { deterministicCompare } from "@hardkas/core";
|
|
714
|
-
var DEV_ACCOUNTS_PASSWORD = "hardkas-local-dev";
|
|
715
|
-
var SIMNET_DETERMINISTIC_SEED = "hardkas-deterministic-simnet-seed-v1";
|
|
716
|
-
async function ensureDevAccounts(workspaceDir) {
|
|
717
|
-
const devAccountsDir = path4.join(workspaceDir, ".hardkas", "dev-accounts");
|
|
718
|
-
if (!fs4.existsSync(devAccountsDir)) {
|
|
719
|
-
await fs4.promises.mkdir(devAccountsDir, { recursive: true });
|
|
720
|
-
}
|
|
721
|
-
await getOrCreateDevAccount(workspaceDir, 0, "alice");
|
|
722
|
-
await getOrCreateDevAccount(workspaceDir, 1, "bob");
|
|
723
|
-
}
|
|
724
|
-
async function getOrCreateDevAccount(workspaceDir, index, alias) {
|
|
725
|
-
const devAccountsDir = path4.join(workspaceDir, ".hardkas", "dev-accounts");
|
|
726
|
-
const filePath = path4.join(devAccountsDir, `${alias}.json`);
|
|
727
|
-
if (fs4.existsSync(filePath)) {
|
|
728
|
-
const keystore2 = await KeystoreManager.loadEncryptedKeystore(filePath);
|
|
729
|
-
const unlock = await KeystoreManager.decryptEncryptedKeystore(
|
|
730
|
-
keystore2,
|
|
731
|
-
DEV_ACCOUNTS_PASSWORD
|
|
732
|
-
);
|
|
733
|
-
if (!unlock.success || !unlock.payload) {
|
|
734
|
-
throw new Error(
|
|
735
|
-
`Failed to decrypt dev account ${alias}. Expected password: ${DEV_ACCOUNTS_PASSWORD}`
|
|
736
|
-
);
|
|
737
|
-
}
|
|
738
|
-
return {
|
|
739
|
-
address: unlock.payload.address,
|
|
740
|
-
privateKey: unlock.payload.privateKey,
|
|
741
|
-
publicKey: unlock.payload.publicKey
|
|
742
|
-
};
|
|
743
|
-
}
|
|
744
|
-
const seedString = `${SIMNET_DETERMINISTIC_SEED}-${index}`;
|
|
745
|
-
const privateKeyHex = crypto2.createHash("sha256").update(seedString).digest("hex");
|
|
746
|
-
const network = "simnet";
|
|
747
|
-
const isSimnet = ["simnet", "kaspasim", "local"].includes(network);
|
|
748
|
-
let address = "";
|
|
749
|
-
let privateKey = "";
|
|
750
|
-
let publicKey = "";
|
|
751
|
-
try {
|
|
752
|
-
if (isSimnet) {
|
|
753
|
-
let kaspaWasm;
|
|
754
|
-
try {
|
|
755
|
-
kaspaWasm = await import(
|
|
756
|
-
/* @vite-ignore */
|
|
757
|
-
"kaspa-wasm"
|
|
758
|
-
);
|
|
759
|
-
} catch (e) {
|
|
760
|
-
console.warn(`
|
|
761
|
-
[Warning] kaspa-wasm is not installed. Required for simnet.`);
|
|
762
|
-
return { address: "", privateKey: "", publicKey: "" };
|
|
763
|
-
}
|
|
764
|
-
const privKey = new kaspaWasm.PrivateKey(privateKeyHex);
|
|
765
|
-
const kp = privKey.toKeypair();
|
|
766
|
-
address = kp.toAddress(network).toString();
|
|
767
|
-
publicKey = kp.publicKey;
|
|
768
|
-
privateKey = privateKeyHex;
|
|
769
|
-
} else {
|
|
770
|
-
let sdkModule;
|
|
771
|
-
try {
|
|
772
|
-
sdkModule = await import(
|
|
773
|
-
/* @vite-ignore */
|
|
774
|
-
"@kaspa/core-lib"
|
|
775
|
-
);
|
|
776
|
-
} catch (e) {
|
|
777
|
-
console.warn(`
|
|
778
|
-
[Warning] @kaspa/core-lib is not installed.`);
|
|
779
|
-
return { address: "", privateKey: "", publicKey: "" };
|
|
780
|
-
}
|
|
781
|
-
const sdk = sdkModule.default || sdkModule;
|
|
782
|
-
if (typeof sdk.initRuntime === "function") {
|
|
783
|
-
await sdk.initRuntime();
|
|
784
|
-
}
|
|
785
|
-
const privKey = new sdk.PrivateKey(privateKeyHex);
|
|
786
|
-
const pubKey = privKey.toPublicKey();
|
|
787
|
-
try {
|
|
788
|
-
address = pubKey.toAddress(network).toString();
|
|
789
|
-
} catch (e) {
|
|
790
|
-
const msg = e instanceof Error ? e instanceof Error ? e instanceof Error ? e.message : String(e) : String(e) : String(e);
|
|
791
|
-
if (msg.includes("Second argument must be") || msg.includes("Unsupported")) {
|
|
792
|
-
const err = new Error("DEV_ACCOUNT_BACKEND_UNSUPPORTED_NETWORK");
|
|
793
|
-
err.code = "DEV_ACCOUNT_BACKEND_UNSUPPORTED_NETWORK";
|
|
794
|
-
throw err;
|
|
795
|
-
}
|
|
796
|
-
throw e;
|
|
797
|
-
}
|
|
798
|
-
publicKey = pubKey.toString();
|
|
799
|
-
privateKey = privKey.toString();
|
|
800
|
-
}
|
|
801
|
-
} catch (e) {
|
|
802
|
-
const msg = e instanceof Error ? e instanceof Error ? e instanceof Error ? e.message : String(e) : String(e) : String(e);
|
|
803
|
-
if (msg === "DEV_ACCOUNT_BACKEND_UNSUPPORTED_NETWORK") {
|
|
804
|
-
throw e;
|
|
805
|
-
}
|
|
806
|
-
console.warn(`
|
|
807
|
-
[Warning] Could not generate dev account '${alias}'.
|
|
808
|
-
${msg}`);
|
|
809
|
-
return { address: "", privateKey: "", publicKey: "" };
|
|
810
|
-
}
|
|
811
|
-
const accountData = {
|
|
812
|
-
address,
|
|
813
|
-
privateKey,
|
|
814
|
-
publicKey
|
|
815
|
-
};
|
|
816
|
-
if (!fs4.existsSync(devAccountsDir)) {
|
|
817
|
-
await fs4.promises.mkdir(devAccountsDir, { recursive: true });
|
|
818
|
-
}
|
|
819
|
-
const payload = {
|
|
820
|
-
address: accountData.address,
|
|
821
|
-
privateKey: accountData.privateKey,
|
|
822
|
-
network: "simnet"
|
|
823
|
-
};
|
|
824
|
-
if (accountData.publicKey) {
|
|
825
|
-
payload.publicKey = accountData.publicKey;
|
|
826
|
-
}
|
|
827
|
-
const keystore = await KeystoreManager.createEncryptedKeystore(
|
|
828
|
-
payload,
|
|
829
|
-
DEV_ACCOUNTS_PASSWORD,
|
|
830
|
-
{
|
|
831
|
-
label: alias,
|
|
832
|
-
network: "simnet"
|
|
833
|
-
}
|
|
834
|
-
);
|
|
835
|
-
await KeystoreManager.saveEncryptedKeystore(filePath, keystore);
|
|
836
|
-
return accountData;
|
|
837
|
-
}
|
|
838
|
-
function listDevAccountsSync(workspaceDir) {
|
|
839
|
-
const devAccountsDir = path4.join(workspaceDir, ".hardkas", "dev-accounts");
|
|
840
|
-
if (!fs4.existsSync(devAccountsDir)) {
|
|
841
|
-
return [];
|
|
842
|
-
}
|
|
843
|
-
const accounts = [];
|
|
844
|
-
const files = fs4.readdirSync(devAccountsDir);
|
|
845
|
-
for (const file of files) {
|
|
846
|
-
if (file.endsWith(".json")) {
|
|
847
|
-
const name = path4.basename(file, ".json");
|
|
848
|
-
try {
|
|
849
|
-
const data = fs4.readFileSync(path4.join(devAccountsDir, file), "utf-8");
|
|
850
|
-
const keystore = JSON.parse(data);
|
|
851
|
-
if (keystore.type === "hardkas.encryptedKeystore.v2") {
|
|
852
|
-
accounts.push({
|
|
853
|
-
name,
|
|
854
|
-
address: keystore.metadata?.address || ""
|
|
855
|
-
});
|
|
856
|
-
}
|
|
857
|
-
} catch (e) {
|
|
858
|
-
}
|
|
859
|
-
}
|
|
860
|
-
}
|
|
861
|
-
accounts.sort((a, b) => deterministicCompare(a.name, b.name));
|
|
862
|
-
return accounts;
|
|
863
|
-
}
|
|
864
|
-
|
|
865
|
-
// src/kaspa-wasm-signer.ts
|
|
866
|
-
function toHex(arr) {
|
|
867
|
-
return Buffer.from(arr).toString("hex");
|
|
868
|
-
}
|
|
869
|
-
function parseWasmTxToRpc(wasmTxStr) {
|
|
870
|
-
let parsed = JSON.parse(wasmTxStr);
|
|
871
|
-
if (typeof parsed === "string") {
|
|
872
|
-
parsed = JSON.parse(parsed);
|
|
873
|
-
}
|
|
874
|
-
const txInner = parsed.tx ? parsed.tx.inner : parsed.inner;
|
|
875
|
-
if (!txInner) throw new Error("Could not find inner tx data");
|
|
876
|
-
return {
|
|
877
|
-
version: txInner.version || 0,
|
|
878
|
-
inputs: (txInner.inputs || []).map((i) => ({
|
|
879
|
-
previousOutpoint: {
|
|
880
|
-
transactionId: i.inner.previousOutpoint.inner.transactionId,
|
|
881
|
-
index: i.inner.previousOutpoint.inner.index
|
|
882
|
-
},
|
|
883
|
-
signatureScript: toHex(i.inner.signatureScript),
|
|
884
|
-
sequence: i.inner.sequence || 0,
|
|
885
|
-
sigOpCount: i.inner.sigOpCount || 1
|
|
886
|
-
})),
|
|
887
|
-
outputs: (txInner.outputs || []).map((o) => ({
|
|
888
|
-
amount: o.inner.value.toString(),
|
|
889
|
-
scriptPublicKey: {
|
|
890
|
-
version: parseInt(o.inner.scriptPublicKey.substring(0, 4), 16) || 0,
|
|
891
|
-
scriptPublicKey: o.inner.scriptPublicKey.substring(4)
|
|
892
|
-
}
|
|
893
|
-
})),
|
|
894
|
-
lockTime: txInner.lockTime || 0,
|
|
895
|
-
subnetworkId: txInner.subnetworkId || "0000000000000000000000000000000000000000",
|
|
896
|
-
gas: txInner.gas || 0,
|
|
897
|
-
payload: txInner.payload && txInner.payload.length > 0 ? toHex(txInner.payload) : ""
|
|
898
|
-
};
|
|
899
|
-
}
|
|
900
|
-
var KaspaWasmPrivateKeySigner = class {
|
|
901
|
-
constructor(options) {
|
|
902
|
-
this.options = options;
|
|
903
|
-
}
|
|
904
|
-
options;
|
|
905
|
-
kind = "kaspa-private-key";
|
|
906
|
-
async signTxPlan(input) {
|
|
907
|
-
const plan = input.planArtifact;
|
|
908
|
-
const account = this.options.account;
|
|
909
|
-
const sdk = await loadKaspaWasm();
|
|
910
|
-
const { detectCapabilities: detectCapabilities2 } = await import("./signer-backend-T4JT2RCK.js");
|
|
911
|
-
const capabilities = detectCapabilities2(sdk);
|
|
912
|
-
if (plan.computeBudget !== void 0 || plan.outputs.some((o) => o.covenant)) {
|
|
913
|
-
if (!capabilities.transactionV1Signing) {
|
|
914
|
-
throw new Error("Transaction V1 signing is not supported by the installed kaspa-wasm version");
|
|
915
|
-
}
|
|
916
|
-
}
|
|
917
|
-
assertSigningNetworkAllowed({
|
|
918
|
-
network: plan.networkId,
|
|
919
|
-
mode: plan.mode,
|
|
920
|
-
allowMainnet: this.options.allowMainnet
|
|
921
|
-
});
|
|
922
|
-
let pkValue = account.privateKeyEnv ? process.env[account.privateKeyEnv] : void 0;
|
|
923
|
-
if (!pkValue && account.privateKey) {
|
|
924
|
-
if (plan.networkId === "mainnet") {
|
|
925
|
-
throw new Error(
|
|
926
|
-
`Mainnet guard: Unsafe plaintext privateKey fallback is forbidden on mainnet for account '${account.name}'. Use privateKeyEnv instead.`
|
|
927
|
-
);
|
|
928
|
-
}
|
|
929
|
-
pkValue = account.privateKey;
|
|
930
|
-
}
|
|
931
|
-
if (!pkValue && account.keystorePath) {
|
|
932
|
-
try {
|
|
933
|
-
const keystore = await KeystoreManager.loadEncryptedKeystore(
|
|
934
|
-
account.keystorePath
|
|
935
|
-
);
|
|
936
|
-
const unlock = await KeystoreManager.decryptEncryptedKeystore(
|
|
937
|
-
keystore,
|
|
938
|
-
DEV_ACCOUNTS_PASSWORD
|
|
939
|
-
);
|
|
940
|
-
if (unlock.success && unlock.payload) {
|
|
941
|
-
pkValue = unlock.payload.privateKey;
|
|
942
|
-
}
|
|
943
|
-
} catch (e) {
|
|
944
|
-
}
|
|
945
|
-
}
|
|
946
|
-
if (!pkValue) {
|
|
947
|
-
const err = new Error(
|
|
948
|
-
`DEV_ACCOUNT_KEY_UNAVAILABLE: Missing required private key for account '${account.name}'.`
|
|
949
|
-
);
|
|
950
|
-
err.code = "DEV_ACCOUNT_KEY_UNAVAILABLE";
|
|
951
|
-
throw err;
|
|
952
|
-
}
|
|
953
|
-
if (typeof pkValue !== "string" || pkValue.trim() === "" || !/^[0-9a-fA-F]{64}$/.test(pkValue)) {
|
|
954
|
-
const err = new Error(
|
|
955
|
-
"INVALID_PRIVATE_KEY_MATERIAL: Private key must be a valid 64-character hex string."
|
|
956
|
-
);
|
|
957
|
-
err.code = "INVALID_PRIVATE_KEY_MATERIAL";
|
|
958
|
-
throw err;
|
|
959
|
-
}
|
|
960
|
-
try {
|
|
961
|
-
const privateKey = new sdk.PrivateKey(pkValue);
|
|
962
|
-
const utxos = plan.inputs.map((u) => {
|
|
963
|
-
if (!u.outpoint.transactionId || u.outpoint.index === void 0) {
|
|
964
|
-
throw new Error(`UTXO is missing transactionId or index. Re-run tx plan.`);
|
|
965
|
-
}
|
|
966
|
-
const spk = u.scriptPublicKey;
|
|
967
|
-
if (!spk) {
|
|
968
|
-
throw new Error(
|
|
969
|
-
"UTXO is missing scriptPublicKey. Real signing flows must never fabricate cryptographic state."
|
|
970
|
-
);
|
|
971
|
-
}
|
|
972
|
-
return {
|
|
973
|
-
address: plan.from.address,
|
|
974
|
-
outpoint: {
|
|
975
|
-
transactionId: u.outpoint.transactionId,
|
|
976
|
-
index: u.outpoint.index
|
|
977
|
-
},
|
|
978
|
-
utxoEntry: {
|
|
979
|
-
amount: BigInt(u.amountSompi),
|
|
980
|
-
scriptPublicKey: spk,
|
|
981
|
-
blockDaaScore: BigInt(u.blockDaaScore || "0"),
|
|
982
|
-
isCoinbase: !!u.isCoinbase
|
|
983
|
-
}
|
|
984
|
-
};
|
|
985
|
-
});
|
|
986
|
-
const outputs = plan.outputs.map((o) => {
|
|
987
|
-
if (!o.address) throw new Error("Output is missing address.");
|
|
988
|
-
return {
|
|
989
|
-
address: o.address,
|
|
990
|
-
amount: BigInt(o.amountSompi)
|
|
991
|
-
};
|
|
992
|
-
});
|
|
993
|
-
const changeAddress = plan.change?.address ? new sdk.Address(plan.change.address) : void 0;
|
|
994
|
-
const priorityFee = BigInt(plan.estimatedFeeSompi);
|
|
995
|
-
const unsignedTx = sdk.createTransaction(
|
|
996
|
-
utxos,
|
|
997
|
-
outputs,
|
|
998
|
-
changeAddress,
|
|
999
|
-
priorityFee
|
|
1000
|
-
);
|
|
1001
|
-
const signedTx = sdk.signTransaction(unsignedTx, [privateKey], true);
|
|
1002
|
-
const rawTx = JSON.stringify(parseWasmTxToRpc(signedTx.toString()));
|
|
1003
|
-
return {
|
|
1004
|
-
signatureKind: "kaspa-private-key",
|
|
1005
|
-
signerAddress: account.address || privateKey.toAddress(plan.networkId).toString(),
|
|
1006
|
-
signedTransaction: {
|
|
1007
|
-
format: "hex",
|
|
1008
|
-
payload: rawTx
|
|
1009
|
-
},
|
|
1010
|
-
txId: signedTx.id,
|
|
1011
|
-
signature: {
|
|
1012
|
-
// We use the txid as the signature identifier in the artifact
|
|
1013
|
-
value: signedTx.id || calculateContentHash(plan)
|
|
1014
|
-
}
|
|
1015
|
-
};
|
|
1016
|
-
} catch (error) {
|
|
1017
|
-
throw new Error(
|
|
1018
|
-
`Kaspa WASM signing failed: ${error instanceof Error ? error.message : String(error)}`
|
|
1019
|
-
);
|
|
1020
|
-
}
|
|
1021
|
-
}
|
|
1022
|
-
};
|
|
1023
|
-
function assertSigningNetworkAllowed(input) {
|
|
1024
|
-
const isMainnet = input.network === "mainnet";
|
|
1025
|
-
if (isMainnet && !input.allowMainnet) {
|
|
1026
|
-
throw new Error(
|
|
1027
|
-
"Mainnet signing is disabled by default. Use --allow-mainnet-signing only if you understand the risks."
|
|
1028
|
-
);
|
|
1029
|
-
}
|
|
1030
|
-
}
|
|
1031
|
-
|
|
1032
|
-
// src/signer.ts
|
|
1033
|
-
var SimulatedTxPlanSigner = class {
|
|
1034
|
-
kind = "simulated";
|
|
1035
|
-
async signTxPlan(input) {
|
|
1036
|
-
const plan = input.planArtifact;
|
|
1037
|
-
return {
|
|
1038
|
-
signatureKind: "simulated",
|
|
1039
|
-
signerAddress: plan.from.address,
|
|
1040
|
-
signedTransaction: {
|
|
1041
|
-
format: "simulated",
|
|
1042
|
-
payload: `simulated-signed-tx:${plan.planId}`
|
|
1043
|
-
}
|
|
1044
|
-
};
|
|
1045
|
-
}
|
|
1046
|
-
};
|
|
1047
128
|
var UnsupportedRealKaspaSigner = class {
|
|
1048
129
|
kind = "unsupported";
|
|
1049
130
|
async signTxPlan(_input) {
|
|
@@ -1053,55 +134,52 @@ var UnsupportedRealKaspaSigner = class {
|
|
|
1053
134
|
}
|
|
1054
135
|
};
|
|
1055
136
|
async function signTxPlanArtifact(input) {
|
|
1056
|
-
const { planArtifact, account } = input;
|
|
137
|
+
const { planArtifact, account, authorizers } = input;
|
|
1057
138
|
const planRecord = planArtifact;
|
|
1058
139
|
if (planArtifact.schema === "hardkas.txPlan") {
|
|
1059
140
|
} else if (planRecord.status !== "built" && planRecord.status !== "unsigned") {
|
|
1060
141
|
throw new Error(`Cannot sign artifact with status: ${planRecord.status}`);
|
|
1061
142
|
}
|
|
1062
143
|
if (planArtifact.mode === "simulated") {
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
} else {
|
|
1069
|
-
if (account.kind === "simulated") {
|
|
1070
|
-
throw new Error(
|
|
1071
|
-
`Real Kaspa transaction plans (mode: ${planArtifact.mode}) cannot be signed with simulated accounts.`
|
|
1072
|
-
);
|
|
1073
|
-
}
|
|
144
|
+
return createSimulatedSignedTxArtifact(
|
|
145
|
+
planArtifact,
|
|
146
|
+
`simulated-signed-tx:${planArtifact.planId}`,
|
|
147
|
+
systemRuntimeContext
|
|
148
|
+
);
|
|
1074
149
|
}
|
|
1075
150
|
if (planArtifact.networkId === "mainnet" && !input.allowMainnet) {
|
|
1076
151
|
throw new Error(
|
|
1077
152
|
"Mainnet signing is disabled by default. Use --allow-mainnet-signing only if you understand the risks."
|
|
1078
153
|
);
|
|
1079
154
|
}
|
|
1080
|
-
if (account.kind === "simulated") {
|
|
1081
|
-
|
|
1082
|
-
planArtifact
|
|
1083
|
-
`simulated-signed-tx:${planArtifact.planId}`,
|
|
1084
|
-
systemRuntimeContext
|
|
155
|
+
if (account && account.kind === "simulated") {
|
|
156
|
+
throw new Error(
|
|
157
|
+
`Real Kaspa transaction plans (mode: ${planArtifact.mode}) cannot be signed with simulated accounts.`
|
|
1085
158
|
);
|
|
1086
159
|
}
|
|
1087
|
-
if (account.kind === "kaspa-private-key") {
|
|
1088
|
-
const status = await getKaspaSigningBackendStatus();
|
|
160
|
+
if (!account || account.kind === "kaspa-private-key" || typeof account.authorize === "function") {
|
|
161
|
+
const status = await getKaspaSigningBackendStatus(input.config?.wasm);
|
|
1089
162
|
if (!status.available) {
|
|
1090
163
|
throw new Error(
|
|
1091
164
|
`Real Kaspa signing is not available: ${status.error || "Unknown error"}. Ensure 'kaspa' package is installed.`
|
|
1092
165
|
);
|
|
1093
166
|
}
|
|
1094
|
-
const
|
|
167
|
+
const signerOptions = {
|
|
1095
168
|
account,
|
|
1096
169
|
allowMainnet: input.allowMainnet
|
|
1097
|
-
}
|
|
170
|
+
};
|
|
171
|
+
if (input.config?.wasm) {
|
|
172
|
+
signerOptions.wasmConfig = input.config.wasm;
|
|
173
|
+
}
|
|
174
|
+
const signer = new KaspaWasmPrivateKeySigner(signerOptions);
|
|
1098
175
|
const result = await signer.signTxPlan({
|
|
1099
176
|
planArtifact,
|
|
1100
|
-
accountName: account.name
|
|
177
|
+
...account?.name ? { accountName: account.name } : {},
|
|
178
|
+
...input.authorizers ? { authorizers: input.authorizers } : {}
|
|
1101
179
|
});
|
|
1102
180
|
const artifact = {
|
|
1103
181
|
schema: "hardkas.signedTx",
|
|
1104
|
-
hardkasVersion:
|
|
182
|
+
hardkasVersion: HARDKAS_VERSION,
|
|
1105
183
|
version: "1.0.0-alpha",
|
|
1106
184
|
status: "signed",
|
|
1107
185
|
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
@@ -1124,7 +202,7 @@ async function signTxPlanArtifact(input) {
|
|
|
1124
202
|
...planArtifact.networkProfileRef ? { networkProfileRef: planArtifact.networkProfileRef } : {},
|
|
1125
203
|
...planArtifact.assumptionRef ? { assumptionRef: planArtifact.assumptionRef } : {}
|
|
1126
204
|
};
|
|
1127
|
-
const contentHash =
|
|
205
|
+
const contentHash = calculateContentHash(artifact);
|
|
1128
206
|
artifact.signedId = `signed-${contentHash.slice(0, 16)}`;
|
|
1129
207
|
artifact.contentHash = contentHash;
|
|
1130
208
|
if (artifact.lineage) {
|
|
@@ -1132,16 +210,16 @@ async function signTxPlanArtifact(input) {
|
|
|
1132
210
|
}
|
|
1133
211
|
return artifact;
|
|
1134
212
|
}
|
|
1135
|
-
if (account.kind === "external-wallet") {
|
|
213
|
+
if (account && account.kind === "external-wallet") {
|
|
1136
214
|
throw new Error("External wallet signing is not implemented yet.");
|
|
1137
215
|
}
|
|
1138
|
-
if (account.kind === "evm-private-key") {
|
|
216
|
+
if (account && account.kind === "evm-private-key") {
|
|
1139
217
|
throw new Error(
|
|
1140
218
|
"EVM accounts are reserved for future Igra support and cannot sign Kaspa L1 transactions."
|
|
1141
219
|
);
|
|
1142
220
|
}
|
|
1143
221
|
const accountRecord = account;
|
|
1144
|
-
throw new Error(`Unsupported account kind for signing: ${accountRecord.kind}`);
|
|
222
|
+
throw new Error(`Unsupported account kind for signing: ${accountRecord ? accountRecord.kind : "unknown"}`);
|
|
1145
223
|
}
|
|
1146
224
|
|
|
1147
225
|
// src/kaspa-sdk-keygen.ts
|
|
@@ -1170,8 +248,8 @@ var KaspaSdkKeyGenerator = class {
|
|
|
1170
248
|
const network = options?.networkId || this.networkId;
|
|
1171
249
|
try {
|
|
1172
250
|
if (typeof sdk.PrivateKey === "function") {
|
|
1173
|
-
const
|
|
1174
|
-
const randomBytes =
|
|
251
|
+
const crypto = await import("crypto");
|
|
252
|
+
const randomBytes = crypto.randomBytes(32);
|
|
1175
253
|
const hex = randomBytes.toString("hex");
|
|
1176
254
|
const privKey = new sdk.PrivateKey(hex);
|
|
1177
255
|
const kp = privKey.toKeypair();
|
|
@@ -1208,173 +286,6 @@ async function createLocalKaspaWallet(options) {
|
|
|
1208
286
|
return await keygen.generateAccount();
|
|
1209
287
|
}
|
|
1210
288
|
|
|
1211
|
-
// src/fixture-signer.ts
|
|
1212
|
-
import {
|
|
1213
|
-
calculateContentHash as calculateContentHash3,
|
|
1214
|
-
HARDKAS_VERSION as HARDKAS_VERSION3,
|
|
1215
|
-
ARTIFACT_VERSION as ARTIFACT_VERSION2,
|
|
1216
|
-
CURRENT_HASH_VERSION
|
|
1217
|
-
} from "@hardkas/artifacts";
|
|
1218
|
-
function toHex2(arr) {
|
|
1219
|
-
return Buffer.from(arr).toString("hex");
|
|
1220
|
-
}
|
|
1221
|
-
function parseWasmTxToRpc2(wasmTxStr) {
|
|
1222
|
-
let parsed = JSON.parse(wasmTxStr);
|
|
1223
|
-
while (typeof parsed === "string") {
|
|
1224
|
-
parsed = JSON.parse(parsed);
|
|
1225
|
-
}
|
|
1226
|
-
const txInner = parsed.tx ? parsed.tx.inner : parsed.inner;
|
|
1227
|
-
if (!txInner) throw new Error("Could not find inner tx data");
|
|
1228
|
-
return {
|
|
1229
|
-
version: txInner.version || 0,
|
|
1230
|
-
inputs: (txInner.inputs || []).map((i) => ({
|
|
1231
|
-
previousOutpoint: {
|
|
1232
|
-
transactionId: i.inner.previousOutpoint.inner.transactionId,
|
|
1233
|
-
index: i.inner.previousOutpoint.inner.index
|
|
1234
|
-
},
|
|
1235
|
-
signatureScript: toHex2(i.inner.signatureScript),
|
|
1236
|
-
sequence: i.inner.sequence || 0,
|
|
1237
|
-
sigOpCount: i.inner.sigOpCount || 1
|
|
1238
|
-
})),
|
|
1239
|
-
outputs: (txInner.outputs || []).map((o) => ({
|
|
1240
|
-
value: o.inner.value,
|
|
1241
|
-
scriptPublicKey: {
|
|
1242
|
-
version: parseInt(o.inner.scriptPublicKey.substring(0, 4), 16) || 0,
|
|
1243
|
-
script: o.inner.scriptPublicKey.substring(4)
|
|
1244
|
-
}
|
|
1245
|
-
})),
|
|
1246
|
-
lockTime: txInner.lockTime || 0,
|
|
1247
|
-
subnetworkId: txInner.subnetworkId || "0000000000000000000000000000000000000000",
|
|
1248
|
-
gas: txInner.gas || 0,
|
|
1249
|
-
payload: txInner.payload && txInner.payload.length > 0 ? toHex2(txInner.payload) : "",
|
|
1250
|
-
mass: txInner.mass || 0
|
|
1251
|
-
};
|
|
1252
|
-
}
|
|
1253
|
-
var HardkasFixtureSigner = class {
|
|
1254
|
-
networkId;
|
|
1255
|
-
// A deterministic, known private key exclusively for Docker tests.
|
|
1256
|
-
FIXTURE_PK = "b7e151628aed2a6abf7158809cf4f3c762e7160f38b4da56a784d9045190cfef";
|
|
1257
|
-
constructor(networkId = "simnet") {
|
|
1258
|
-
this.networkId = networkId;
|
|
1259
|
-
if (networkId === "mainnet") {
|
|
1260
|
-
throw new Error("FixtureSigner cannot be used on mainnet.");
|
|
1261
|
-
}
|
|
1262
|
-
}
|
|
1263
|
-
async loadKaspa() {
|
|
1264
|
-
try {
|
|
1265
|
-
return await import("kaspa-wasm");
|
|
1266
|
-
} catch (e) {
|
|
1267
|
-
const err = new Error(
|
|
1268
|
-
"SIGNER_BACKEND_UNAVAILABLE: Official Kaspa WASM backend is required to sign transactions.\nInstall it via: npm install kaspa-wasm"
|
|
1269
|
-
);
|
|
1270
|
-
err.code = "SIGNER_BACKEND_UNAVAILABLE";
|
|
1271
|
-
throw err;
|
|
1272
|
-
}
|
|
1273
|
-
}
|
|
1274
|
-
async getAddress() {
|
|
1275
|
-
const kaspa = await this.loadKaspa();
|
|
1276
|
-
const privKey = new kaspa.PrivateKey(this.FIXTURE_PK);
|
|
1277
|
-
return privKey.toKeypair().toAddress(this.networkId).toString();
|
|
1278
|
-
}
|
|
1279
|
-
async signTransaction(plan) {
|
|
1280
|
-
if (plan.networkId === "mainnet") {
|
|
1281
|
-
throw new Error("FixtureSigner refuses to sign mainnet transactions.");
|
|
1282
|
-
}
|
|
1283
|
-
const kaspa = await this.loadKaspa();
|
|
1284
|
-
const privateKey = new kaspa.PrivateKey(this.FIXTURE_PK);
|
|
1285
|
-
const utxos = plan.inputs.map((u) => {
|
|
1286
|
-
if (!u.outpoint.transactionId || u.outpoint.index === void 0) {
|
|
1287
|
-
throw new Error(`UTXO is missing transactionId or index. Re-run tx plan.`);
|
|
1288
|
-
}
|
|
1289
|
-
const spk = u.scriptPublicKey;
|
|
1290
|
-
if (!spk) {
|
|
1291
|
-
throw new Error(
|
|
1292
|
-
"UTXO is missing scriptPublicKey. Real signing flows must never fabricate cryptographic state."
|
|
1293
|
-
);
|
|
1294
|
-
}
|
|
1295
|
-
return {
|
|
1296
|
-
address: plan.from.address,
|
|
1297
|
-
outpoint: {
|
|
1298
|
-
transactionId: u.outpoint.transactionId,
|
|
1299
|
-
index: u.outpoint.index
|
|
1300
|
-
},
|
|
1301
|
-
utxoEntry: {
|
|
1302
|
-
amount: BigInt(u.amountSompi),
|
|
1303
|
-
scriptPublicKey: spk,
|
|
1304
|
-
blockDaaScore: BigInt(u.blockDaaScore || "0"),
|
|
1305
|
-
isCoinbase: !!u.isCoinbase
|
|
1306
|
-
}
|
|
1307
|
-
};
|
|
1308
|
-
});
|
|
1309
|
-
const outputs = plan.outputs.map((o) => {
|
|
1310
|
-
if (!o.address) throw new Error("Output is missing address.");
|
|
1311
|
-
return {
|
|
1312
|
-
address: o.address,
|
|
1313
|
-
amount: BigInt(o.amountSompi)
|
|
1314
|
-
};
|
|
1315
|
-
});
|
|
1316
|
-
let changeAddress;
|
|
1317
|
-
if (plan.change && plan.change.address) {
|
|
1318
|
-
changeAddress = new kaspa.Address(plan.change.address);
|
|
1319
|
-
} else {
|
|
1320
|
-
changeAddress = new kaspa.Address(plan.from.address);
|
|
1321
|
-
}
|
|
1322
|
-
const priorityFee = BigInt(plan.estimatedFeeSompi || "0");
|
|
1323
|
-
const unsignedTx = kaspa.createTransaction(
|
|
1324
|
-
utxos,
|
|
1325
|
-
outputs,
|
|
1326
|
-
changeAddress,
|
|
1327
|
-
priorityFee
|
|
1328
|
-
);
|
|
1329
|
-
const signedTx = kaspa.signTransaction(unsignedTx, [privateKey], true);
|
|
1330
|
-
console.log("SIGNED TX TOSTRING:", signedTx.toString());
|
|
1331
|
-
const rawTx = JSON.stringify(parseWasmTxToRpc2(signedTx.toString()));
|
|
1332
|
-
const draft = {
|
|
1333
|
-
schema: "hardkas.signedTx",
|
|
1334
|
-
schemaVersion: "hardkas.artifact.v1",
|
|
1335
|
-
hardkasVersion: HARDKAS_VERSION3,
|
|
1336
|
-
version: ARTIFACT_VERSION2,
|
|
1337
|
-
hashVersion: CURRENT_HASH_VERSION,
|
|
1338
|
-
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1339
|
-
status: "signed",
|
|
1340
|
-
txId: signedTx.id,
|
|
1341
|
-
sourcePlanId: plan.planId,
|
|
1342
|
-
networkId: plan.networkId,
|
|
1343
|
-
mode: plan.mode,
|
|
1344
|
-
from: plan.from,
|
|
1345
|
-
to: plan.to,
|
|
1346
|
-
amountSompi: plan.amountSompi,
|
|
1347
|
-
unsignedPayloadHash: plan.contentHash,
|
|
1348
|
-
signedTransaction: {
|
|
1349
|
-
format: "hex",
|
|
1350
|
-
payload: rawTx
|
|
1351
|
-
},
|
|
1352
|
-
metadata: {
|
|
1353
|
-
signerBackend: "kaspa-wasm",
|
|
1354
|
-
fixture: true,
|
|
1355
|
-
networkGuard: "mainnet_rejected"
|
|
1356
|
-
},
|
|
1357
|
-
signatureMetadata: [
|
|
1358
|
-
{
|
|
1359
|
-
signer: "hardkas-local-docker-test-only",
|
|
1360
|
-
signedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
1361
|
-
}
|
|
1362
|
-
],
|
|
1363
|
-
lineage: {
|
|
1364
|
-
artifactId: "",
|
|
1365
|
-
lineageId: plan.lineage?.lineageId || plan.contentHash || "0".repeat(64),
|
|
1366
|
-
parentArtifactId: plan.contentHash || plan.planId,
|
|
1367
|
-
rootArtifactId: plan.lineage?.rootArtifactId || plan.contentHash || plan.planId
|
|
1368
|
-
}
|
|
1369
|
-
};
|
|
1370
|
-
const hash = calculateContentHash3(draft, CURRENT_HASH_VERSION);
|
|
1371
|
-
draft.signedId = `signed-${hash.slice(0, 16)}`;
|
|
1372
|
-
draft.contentHash = hash;
|
|
1373
|
-
if (draft.lineage) draft.lineage.artifactId = hash;
|
|
1374
|
-
return draft;
|
|
1375
|
-
}
|
|
1376
|
-
};
|
|
1377
|
-
|
|
1378
289
|
// src/real-signer.ts
|
|
1379
290
|
var UnsupportedRealTxSigner = class {
|
|
1380
291
|
async sign() {
|
|
@@ -1448,7 +359,7 @@ var KaspaSdkRealTxSigner = class {
|
|
|
1448
359
|
priorityFee
|
|
1449
360
|
);
|
|
1450
361
|
const signedTx = sdk.signTransaction(unsignedTx, [privateKey], true);
|
|
1451
|
-
const innerTx = signedTx.tx;
|
|
362
|
+
const innerTx = signedTx.tx || signedTx;
|
|
1452
363
|
const txId = innerTx?.id;
|
|
1453
364
|
const payload = JSON.stringify(
|
|
1454
365
|
innerTx.toJSON(),
|
|
@@ -1484,8 +395,8 @@ var UnsupportedKaspaKeyGenerator = class {
|
|
|
1484
395
|
};
|
|
1485
396
|
|
|
1486
397
|
// src/keystore-lock.ts
|
|
1487
|
-
import
|
|
1488
|
-
import
|
|
398
|
+
import fs from "fs";
|
|
399
|
+
import path from "path";
|
|
1489
400
|
async function withKeystoreLock(filePath, operation) {
|
|
1490
401
|
const lockFilePath = `${filePath}.lock`;
|
|
1491
402
|
const staleTimeoutMs = parseInt(process.env.HARDKAS_KEYSTORE_LOCK_STALE_MS || "30000", 10);
|
|
@@ -1493,15 +404,15 @@ async function withKeystoreLock(filePath, operation) {
|
|
|
1493
404
|
const start = Date.now();
|
|
1494
405
|
while (true) {
|
|
1495
406
|
try {
|
|
1496
|
-
|
|
1497
|
-
|
|
407
|
+
fs.mkdirSync(path.dirname(lockFilePath), { recursive: true });
|
|
408
|
+
fs.writeFileSync(lockFilePath, process.pid.toString(), { flag: "wx" });
|
|
1498
409
|
break;
|
|
1499
410
|
} catch (err) {
|
|
1500
411
|
if (err.code === "EEXIST") {
|
|
1501
|
-
const stats =
|
|
412
|
+
const stats = fs.statSync(lockFilePath, { throwIfNoEntry: false });
|
|
1502
413
|
if (stats && Date.now() - stats.mtimeMs > staleTimeoutMs) {
|
|
1503
414
|
try {
|
|
1504
|
-
|
|
415
|
+
fs.unlinkSync(lockFilePath);
|
|
1505
416
|
} catch (e) {
|
|
1506
417
|
}
|
|
1507
418
|
continue;
|
|
@@ -1522,33 +433,33 @@ async function withKeystoreLock(filePath, operation) {
|
|
|
1522
433
|
return await operation();
|
|
1523
434
|
} finally {
|
|
1524
435
|
try {
|
|
1525
|
-
|
|
436
|
+
fs.unlinkSync(lockFilePath);
|
|
1526
437
|
} catch (e) {
|
|
1527
438
|
}
|
|
1528
439
|
}
|
|
1529
440
|
}
|
|
1530
441
|
async function appendToKeystoreJson(workspaceRoot, alias, accountData) {
|
|
1531
|
-
const keystorePath =
|
|
1532
|
-
const tempPath =
|
|
442
|
+
const keystorePath = path.join(workspaceRoot, ".hardkas", "keystore.json");
|
|
443
|
+
const tempPath = path.join(workspaceRoot, ".hardkas", `keystore-${process.pid}-${Date.now()}.json.tmp`);
|
|
1533
444
|
await withKeystoreLock(keystorePath, async () => {
|
|
1534
445
|
let ks = {};
|
|
1535
|
-
if (
|
|
446
|
+
if (fs.existsSync(keystorePath)) {
|
|
1536
447
|
try {
|
|
1537
|
-
const data = await
|
|
448
|
+
const data = await fs.promises.readFile(keystorePath, "utf-8");
|
|
1538
449
|
ks = JSON.parse(data);
|
|
1539
450
|
} catch (e) {
|
|
1540
451
|
}
|
|
1541
452
|
}
|
|
1542
453
|
ks[alias] = accountData;
|
|
1543
|
-
const fd = await
|
|
454
|
+
const fd = await fs.promises.open(tempPath, "w");
|
|
1544
455
|
try {
|
|
1545
456
|
await fd.writeFile(JSON.stringify(ks, null, 2));
|
|
1546
457
|
await fd.sync();
|
|
1547
458
|
} finally {
|
|
1548
459
|
await fd.close();
|
|
1549
460
|
}
|
|
1550
|
-
await
|
|
1551
|
-
const written = await
|
|
461
|
+
await fs.promises.rename(tempPath, keystorePath);
|
|
462
|
+
const written = await fs.promises.readFile(keystorePath, "utf-8");
|
|
1552
463
|
JSON.parse(written);
|
|
1553
464
|
});
|
|
1554
465
|
}
|
|
@@ -1697,31 +608,31 @@ var WalletManagerImpl = class {
|
|
|
1697
608
|
var WalletManager = new WalletManagerImpl();
|
|
1698
609
|
|
|
1699
610
|
// src/wallet-state-store.ts
|
|
1700
|
-
import * as
|
|
1701
|
-
import * as
|
|
611
|
+
import * as fs2 from "fs";
|
|
612
|
+
import * as path2 from "path";
|
|
1702
613
|
var WalletStateStoreJson = class {
|
|
1703
614
|
filePath;
|
|
1704
615
|
constructor(options) {
|
|
1705
|
-
this.filePath = options?.filePath ||
|
|
616
|
+
this.filePath = options?.filePath || path2.join(process.cwd(), ".hardkas", "wallet-state.json");
|
|
1706
617
|
}
|
|
1707
618
|
/**
|
|
1708
619
|
* Ensures the directory exists before saving.
|
|
1709
620
|
*/
|
|
1710
621
|
ensureDir() {
|
|
1711
|
-
const dir =
|
|
1712
|
-
if (!
|
|
1713
|
-
|
|
622
|
+
const dir = path2.dirname(this.filePath);
|
|
623
|
+
if (!fs2.existsSync(dir)) {
|
|
624
|
+
fs2.mkdirSync(dir, { recursive: true });
|
|
1714
625
|
}
|
|
1715
626
|
}
|
|
1716
627
|
/**
|
|
1717
628
|
* Loads the entire state file into memory.
|
|
1718
629
|
*/
|
|
1719
630
|
loadAll() {
|
|
1720
|
-
if (!
|
|
631
|
+
if (!fs2.existsSync(this.filePath)) {
|
|
1721
632
|
return {};
|
|
1722
633
|
}
|
|
1723
634
|
try {
|
|
1724
|
-
return JSON.parse(
|
|
635
|
+
return JSON.parse(fs2.readFileSync(this.filePath, "utf-8"));
|
|
1725
636
|
} catch (e) {
|
|
1726
637
|
process.stderr.write(`[WalletStateStore] corrupt state file at ${this.filePath} \u2014 resetting indices to 0. Cause: ${e}
|
|
1727
638
|
`);
|
|
@@ -1734,8 +645,8 @@ var WalletStateStoreJson = class {
|
|
|
1734
645
|
saveAll(data) {
|
|
1735
646
|
this.ensureDir();
|
|
1736
647
|
const tmp = this.filePath + ".tmp";
|
|
1737
|
-
|
|
1738
|
-
|
|
648
|
+
fs2.writeFileSync(tmp, JSON.stringify(data, null, 2), "utf-8");
|
|
649
|
+
fs2.renameSync(tmp, this.filePath);
|
|
1739
650
|
}
|
|
1740
651
|
/**
|
|
1741
652
|
* Retrieves the state for a specific wallet.
|
|
@@ -1777,13 +688,14 @@ var WalletStateStoreJson = class {
|
|
|
1777
688
|
export {
|
|
1778
689
|
AddressManager,
|
|
1779
690
|
DEV_ACCOUNTS_PASSWORD,
|
|
1780
|
-
HardkasFixtureSigner,
|
|
1781
691
|
KaspaSdkKeyGenerator,
|
|
1782
692
|
KaspaSdkRealTxSigner,
|
|
1783
693
|
KaspaWasmPrivateKeySigner,
|
|
1784
694
|
KeystoreManager,
|
|
695
|
+
LazyAccountAuthorizer,
|
|
696
|
+
PrivateKeyAuthorizer,
|
|
1785
697
|
SimulatedSigner,
|
|
1786
|
-
|
|
698
|
+
StaticSignatureScriptAuthorizer,
|
|
1787
699
|
UnsupportedKaspaKeyGenerator,
|
|
1788
700
|
UnsupportedRealKaspaSigner,
|
|
1789
701
|
UnsupportedRealTxSigner,
|
|
@@ -1792,6 +704,7 @@ export {
|
|
|
1792
704
|
WalletStateStoreJson,
|
|
1793
705
|
appendToKeystoreJson,
|
|
1794
706
|
assertSigningNetworkAllowed,
|
|
707
|
+
createDevSigner,
|
|
1795
708
|
createEmptyRealAccountStore,
|
|
1796
709
|
createLocalKaspaWallet,
|
|
1797
710
|
describeAccount,
|