@hardkas/accounts 0.11.2-alpha → 0.11.6-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-TYSAH7ZC.js +10 -0
- package/dist/chunk-3XXJ6UUU.js +99 -0
- package/dist/chunk-6DPO5V3N.js +90 -0
- package/dist/chunk-ILASLDZU.js +472 -0
- package/dist/chunk-R5P3WIWR.js +103 -0
- package/dist/chunk-WXMOC6WN.js +456 -0
- package/dist/chunk-YLG2NIAU.js +166 -0
- package/dist/dev-accounts-UULCTDJB.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
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
// src/authorizers.ts
|
|
2
|
+
var StaticSignatureScriptAuthorizer = class {
|
|
3
|
+
constructor(signatureScript) {
|
|
4
|
+
this.signatureScript = signatureScript;
|
|
5
|
+
}
|
|
6
|
+
signatureScript;
|
|
7
|
+
authorize() {
|
|
8
|
+
return {
|
|
9
|
+
kind: "signature-script",
|
|
10
|
+
signatureScript: this.signatureScript
|
|
11
|
+
};
|
|
12
|
+
}
|
|
13
|
+
};
|
|
14
|
+
var PrivateKeyAuthorizer = class {
|
|
15
|
+
constructor(accountName, privateKeyHex) {
|
|
16
|
+
this.accountName = accountName;
|
|
17
|
+
this.privateKeyHex = privateKeyHex;
|
|
18
|
+
}
|
|
19
|
+
accountName;
|
|
20
|
+
privateKeyHex;
|
|
21
|
+
authorize(context) {
|
|
22
|
+
const planInput = context.plan.inputs[context.inputIndex];
|
|
23
|
+
if (!planInput) {
|
|
24
|
+
throw new Error(`INVALID_AUTHORIZER_INPUT_INDEX: Input index ${context.inputIndex} does not exist in plan.`);
|
|
25
|
+
}
|
|
26
|
+
return {
|
|
27
|
+
kind: "wasm-signer",
|
|
28
|
+
signer: {
|
|
29
|
+
signInput: async (ctx) => {
|
|
30
|
+
const { wasm, wasmTransaction, inputIndex } = ctx;
|
|
31
|
+
const privateKey = new wasm.PrivateKey(this.privateKeyHex);
|
|
32
|
+
const networkId = context.plan.networkId || "simnet";
|
|
33
|
+
const expectedAddress = privateKey.toKeypair().toAddress(networkId).toString();
|
|
34
|
+
if (planInput.address && expectedAddress !== planInput.address) {
|
|
35
|
+
throw new Error(
|
|
36
|
+
`PRIVATE_KEY_DOES_NOT_CONTROL_INPUT: The provided private key for account '${this.accountName}' derives to address '${expectedAddress}', but input ${context.inputIndex} is controlled by '${planInput.address}'.`
|
|
37
|
+
);
|
|
38
|
+
}
|
|
39
|
+
const tx = wasmTransaction;
|
|
40
|
+
try {
|
|
41
|
+
console.log("DEBUG: Calling signTransaction");
|
|
42
|
+
const signedTx = wasm.signTransaction(tx, [privateKey], false);
|
|
43
|
+
console.log("DEBUG: signTransaction OK");
|
|
44
|
+
const sigScript = signedTx.inputs[inputIndex].signatureScript;
|
|
45
|
+
if (!sigScript || sigScript.length === 0) {
|
|
46
|
+
throw new Error(`UNAUTHORIZED_TRANSACTION_INPUT: Kaspa WASM failed to generate a signature script for input ${inputIndex} using account '${this.accountName}'.`);
|
|
47
|
+
}
|
|
48
|
+
const sigScriptHex = typeof sigScript === "string" ? sigScript : Buffer.from(sigScript).toString("hex");
|
|
49
|
+
return sigScriptHex;
|
|
50
|
+
} catch (e) {
|
|
51
|
+
throw new Error(`UNAUTHORIZED_TRANSACTION_INPUT: Failed to sign input ${inputIndex} with PrivateKeyAuthorizer: ${e.message}`);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
};
|
|
58
|
+
var LazyAccountAuthorizer = class {
|
|
59
|
+
constructor(accountName, workspaceRoot) {
|
|
60
|
+
this.accountName = accountName;
|
|
61
|
+
this.workspaceRoot = workspaceRoot;
|
|
62
|
+
}
|
|
63
|
+
accountName;
|
|
64
|
+
workspaceRoot;
|
|
65
|
+
async authorize(context) {
|
|
66
|
+
const { resolveHardkasAccount } = await import("./resolve-EXYT2GAC.js");
|
|
67
|
+
const account = await resolveHardkasAccount({
|
|
68
|
+
nameOrAddress: this.accountName,
|
|
69
|
+
config: { cwd: this.workspaceRoot }
|
|
70
|
+
});
|
|
71
|
+
let pkValue = account.privateKeyEnv ? process.env[account.privateKeyEnv] : void 0;
|
|
72
|
+
if (!pkValue && account.privateKey) {
|
|
73
|
+
pkValue = account.privateKey;
|
|
74
|
+
}
|
|
75
|
+
if (!pkValue && account.keystorePath) {
|
|
76
|
+
try {
|
|
77
|
+
const { KeystoreManager } = await import("./keystore-CMWEKGBF.js");
|
|
78
|
+
const { DEV_ACCOUNTS_PASSWORD } = await import("./dev-accounts-UULCTDJB.js");
|
|
79
|
+
const keystore = await KeystoreManager.loadEncryptedKeystore(account.keystorePath);
|
|
80
|
+
const unlock = await KeystoreManager.decryptEncryptedKeystore(keystore, DEV_ACCOUNTS_PASSWORD);
|
|
81
|
+
if (unlock.success && unlock.payload) {
|
|
82
|
+
pkValue = unlock.payload.privateKey;
|
|
83
|
+
}
|
|
84
|
+
} catch (e) {
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
if (!pkValue) {
|
|
88
|
+
throw new Error(`DEV_ACCOUNT_KEY_UNAVAILABLE: Missing required private key for account '${this.accountName}'.`);
|
|
89
|
+
}
|
|
90
|
+
const internalAuthorizer = new PrivateKeyAuthorizer(this.accountName, pkValue);
|
|
91
|
+
return internalAuthorizer.authorize(context);
|
|
92
|
+
}
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
export {
|
|
96
|
+
StaticSignatureScriptAuthorizer,
|
|
97
|
+
PrivateKeyAuthorizer,
|
|
98
|
+
LazyAccountAuthorizer
|
|
99
|
+
};
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
// src/signer-backend.ts
|
|
2
|
+
import path from "path";
|
|
3
|
+
import fs from "fs";
|
|
4
|
+
import { pathToFileURL } from "url";
|
|
5
|
+
async function loadKaspaWasm(config) {
|
|
6
|
+
const provider = config?.provider || "npm";
|
|
7
|
+
if (provider === "npm") {
|
|
8
|
+
try {
|
|
9
|
+
return await import("kaspa-wasm");
|
|
10
|
+
} catch (error) {
|
|
11
|
+
const err = new Error(
|
|
12
|
+
"SIGNER_BACKEND_UNAVAILABLE: Official Kaspa WASM backend is required to sign transactions.\nInstall it via: npm install kaspa-wasm"
|
|
13
|
+
);
|
|
14
|
+
err.code = "SIGNER_BACKEND_UNAVAILABLE";
|
|
15
|
+
throw err;
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
if (provider === "local" || provider === "release-asset") {
|
|
19
|
+
if (!config?.path) {
|
|
20
|
+
throw new Error(`WASM_PROVIDER_ERROR: 'path' must be provided when using provider '${provider}'`);
|
|
21
|
+
}
|
|
22
|
+
const absolutePath = path.isAbsolute(config.path) ? config.path : path.resolve(process.cwd(), config.path);
|
|
23
|
+
if (!fs.existsSync(absolutePath)) {
|
|
24
|
+
if (provider === "release-asset") {
|
|
25
|
+
const err = new Error(`WASM_RELEASE_ASSET_NOT_FOUND: Could not find WASM release asset at ${absolutePath}`);
|
|
26
|
+
err.code = "WASM_RELEASE_ASSET_NOT_FOUND";
|
|
27
|
+
throw err;
|
|
28
|
+
}
|
|
29
|
+
throw new Error(`WASM_PROVIDER_ERROR: Local WASM path does not exist at ${absolutePath}`);
|
|
30
|
+
}
|
|
31
|
+
try {
|
|
32
|
+
let entryPath = absolutePath;
|
|
33
|
+
if (fs.statSync(absolutePath).isDirectory()) {
|
|
34
|
+
const pkgPath = path.join(absolutePath, "package.json");
|
|
35
|
+
if (fs.existsSync(pkgPath)) {
|
|
36
|
+
const pkg = JSON.parse(fs.readFileSync(pkgPath, "utf8"));
|
|
37
|
+
if (pkg.main) {
|
|
38
|
+
entryPath = path.join(absolutePath, pkg.main);
|
|
39
|
+
} else {
|
|
40
|
+
entryPath = path.join(absolutePath, "index.js");
|
|
41
|
+
}
|
|
42
|
+
} else {
|
|
43
|
+
entryPath = path.join(absolutePath, "index.js");
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
return await import(pathToFileURL(entryPath).href);
|
|
47
|
+
} catch (error) {
|
|
48
|
+
const err = new Error(`WASM_LOAD_FAILED: Failed to load local WASM backend from ${absolutePath}. Details: ${error.message}`);
|
|
49
|
+
err.code = "WASM_LOAD_FAILED";
|
|
50
|
+
throw err;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
function detectCapabilities(sdk) {
|
|
55
|
+
let v1 = false;
|
|
56
|
+
if (sdk.createTransaction && sdk.createTransaction.length >= 8) {
|
|
57
|
+
v1 = true;
|
|
58
|
+
}
|
|
59
|
+
if (sdk.createV1Transaction || sdk.Transaction && sdk.Transaction.prototype && !!Object.getOwnPropertyDescriptor(sdk.Transaction.prototype, "storageMass")) {
|
|
60
|
+
v1 = true;
|
|
61
|
+
}
|
|
62
|
+
return { transactionV1Signing: v1 };
|
|
63
|
+
}
|
|
64
|
+
async function getKaspaSigningBackendStatus(config) {
|
|
65
|
+
try {
|
|
66
|
+
const sdk = await loadKaspaWasm(config);
|
|
67
|
+
return {
|
|
68
|
+
available: true,
|
|
69
|
+
name: "Kaspa WASM SDK",
|
|
70
|
+
version: sdk.version || "unknown",
|
|
71
|
+
capabilities: detectCapabilities(sdk)
|
|
72
|
+
};
|
|
73
|
+
} catch (error) {
|
|
74
|
+
if (error.code === "WASM_RELEASE_ASSET_NOT_FOUND") {
|
|
75
|
+
throw error;
|
|
76
|
+
}
|
|
77
|
+
return {
|
|
78
|
+
available: false,
|
|
79
|
+
name: "None",
|
|
80
|
+
error: error instanceof Error ? error.message : String(error),
|
|
81
|
+
capabilities: { transactionV1Signing: false }
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export {
|
|
87
|
+
loadKaspaWasm,
|
|
88
|
+
detectCapabilities,
|
|
89
|
+
getKaspaSigningBackendStatus
|
|
90
|
+
};
|
|
@@ -0,0 +1,472 @@
|
|
|
1
|
+
// src/resolve.ts
|
|
2
|
+
import fs2 from "fs";
|
|
3
|
+
import path2 from "path";
|
|
4
|
+
import { createDeterministicAccounts } from "@hardkas/localnet";
|
|
5
|
+
|
|
6
|
+
// src/real-accounts.ts
|
|
7
|
+
import fs from "fs";
|
|
8
|
+
import path from "path";
|
|
9
|
+
import { writeFileAtomicSync } from "@hardkas/core";
|
|
10
|
+
import {
|
|
11
|
+
HARDKAS_VERSION,
|
|
12
|
+
ARTIFACT_SCHEMAS,
|
|
13
|
+
ARTIFACT_VERSION
|
|
14
|
+
} from "@hardkas/artifacts";
|
|
15
|
+
function getDefaultRealAccountsPath(cwd) {
|
|
16
|
+
const root = cwd ?? process.cwd();
|
|
17
|
+
return path.join(root, ".hardkas", "accounts.real.json");
|
|
18
|
+
}
|
|
19
|
+
function createEmptyRealAccountStore() {
|
|
20
|
+
return {
|
|
21
|
+
schema: ARTIFACT_SCHEMAS.REAL_ACCOUNT_STORE,
|
|
22
|
+
hardkasVersion: HARDKAS_VERSION,
|
|
23
|
+
version: ARTIFACT_VERSION,
|
|
24
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
25
|
+
networkId: "simnet",
|
|
26
|
+
mode: "real",
|
|
27
|
+
connectionMode: "node",
|
|
28
|
+
warning: "HardKAS: Development account store. Encrypted storage is default. Unsafe plaintext storage is legacy.",
|
|
29
|
+
accounts: []
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
function loadRealAccountStoreSync(options) {
|
|
33
|
+
const filePath = options?.path || getDefaultRealAccountsPath(options?.cwd);
|
|
34
|
+
if (!fs.existsSync(filePath)) {
|
|
35
|
+
return null;
|
|
36
|
+
}
|
|
37
|
+
try {
|
|
38
|
+
const data = fs.readFileSync(filePath, "utf-8");
|
|
39
|
+
const store = JSON.parse(data);
|
|
40
|
+
for (const a of store.accounts) {
|
|
41
|
+
if ("privateKey" in a && a.privateKey !== void 0) {
|
|
42
|
+
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) {
|
|
43
|
+
const err = new Error(
|
|
44
|
+
"CORRUPTED_PRIVATE_KEY_SERIALIZATION: This account was generated by a broken alpha and must be regenerated. The private key was not recoverably stored."
|
|
45
|
+
);
|
|
46
|
+
err.code = "CORRUPTED_PRIVATE_KEY_SERIALIZATION";
|
|
47
|
+
throw err;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
const plaintextAccounts = store.accounts.filter((a) => a.privateKey);
|
|
52
|
+
if (plaintextAccounts.length > 0) {
|
|
53
|
+
const names = plaintextAccounts.map((a) => a.name).join(", ");
|
|
54
|
+
console.warn(
|
|
55
|
+
`
|
|
56
|
+
\u26A0\uFE0F [SECURITY WARNING] Plaintext private keys detected in legacy account store for: ${names}`
|
|
57
|
+
);
|
|
58
|
+
console.warn(` Location: ${filePath}`);
|
|
59
|
+
console.warn(
|
|
60
|
+
` Recommendation: Re-import these accounts using encrypted keystores.
|
|
61
|
+
`
|
|
62
|
+
);
|
|
63
|
+
}
|
|
64
|
+
return store;
|
|
65
|
+
} catch (e) {
|
|
66
|
+
throw new Error(
|
|
67
|
+
`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)}`
|
|
68
|
+
);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
async function loadRealAccountStore(options) {
|
|
72
|
+
return loadRealAccountStoreSync(options);
|
|
73
|
+
}
|
|
74
|
+
async function loadOrCreateRealAccountStore(options) {
|
|
75
|
+
const store = await loadRealAccountStore(options);
|
|
76
|
+
if (store) return store;
|
|
77
|
+
const newStore = createEmptyRealAccountStore();
|
|
78
|
+
await saveRealAccountStore(newStore, options);
|
|
79
|
+
return newStore;
|
|
80
|
+
}
|
|
81
|
+
async function saveRealAccountStore(store, options) {
|
|
82
|
+
const filePath = options?.path || getDefaultRealAccountsPath(options?.cwd);
|
|
83
|
+
try {
|
|
84
|
+
writeFileAtomicSync(filePath, JSON.stringify(store, null, 2), {
|
|
85
|
+
encoding: "utf-8",
|
|
86
|
+
mode: 384
|
|
87
|
+
});
|
|
88
|
+
} catch (e) {
|
|
89
|
+
throw new Error(
|
|
90
|
+
`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)}`
|
|
91
|
+
);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
function validateAccountName(name) {
|
|
95
|
+
if (!name) {
|
|
96
|
+
throw new Error("Account name is required.");
|
|
97
|
+
}
|
|
98
|
+
const nameRegex = /^[a-zA-Z0-9_-]+$/;
|
|
99
|
+
if (!nameRegex.test(name)) {
|
|
100
|
+
throw new Error(
|
|
101
|
+
`Invalid account name '${name}'. Only letters, numbers, dashes and underscores are allowed.`
|
|
102
|
+
);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
function validateAddressPrefix(address) {
|
|
106
|
+
if (!address) {
|
|
107
|
+
throw new Error("Address is required.");
|
|
108
|
+
}
|
|
109
|
+
const validPrefixes = ["kaspa:", "kaspatest:", "kaspasim:"];
|
|
110
|
+
const hasValidPrefix = validPrefixes.some((prefix) => address.startsWith(prefix));
|
|
111
|
+
if (!hasValidPrefix) {
|
|
112
|
+
const err = new Error(
|
|
113
|
+
`HARDKAS_INVALID_ADDRESS: Invalid address '${address}'. Must start with one of: ${validPrefixes.join(", ")}`
|
|
114
|
+
);
|
|
115
|
+
err.code = "HARDKAS_INVALID_ADDRESS";
|
|
116
|
+
throw err;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
function validateAddressNetwork(address, networkId, allowMainnet) {
|
|
120
|
+
if (address.startsWith("kaspa:sim_")) {
|
|
121
|
+
return;
|
|
122
|
+
}
|
|
123
|
+
validateAddressPrefix(address);
|
|
124
|
+
let expectedPrefix;
|
|
125
|
+
if (networkId === "mainnet") {
|
|
126
|
+
expectedPrefix = "kaspa:";
|
|
127
|
+
} else if (networkId === "testnet-10" || networkId === "testnet-11") {
|
|
128
|
+
expectedPrefix = "kaspatest:";
|
|
129
|
+
} else if (networkId === "simnet" || networkId === "devnet" || networkId === "simulated") {
|
|
130
|
+
expectedPrefix = "kaspasim:";
|
|
131
|
+
} else {
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
134
|
+
if (expectedPrefix !== "kaspa:" && address.startsWith("kaspa:") && allowMainnet) {
|
|
135
|
+
return;
|
|
136
|
+
}
|
|
137
|
+
if (!address.startsWith(expectedPrefix)) {
|
|
138
|
+
const err = new Error(
|
|
139
|
+
`NETWORK_ADDRESS_MISMATCH: Address '${address}' does not match the expected prefix '${expectedPrefix}' for network '${networkId}'.`
|
|
140
|
+
);
|
|
141
|
+
err.code = "NETWORK_ADDRESS_MISMATCH";
|
|
142
|
+
throw err;
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
function importRealDevAccount(store, account) {
|
|
146
|
+
validateAccountName(account.name);
|
|
147
|
+
validateAddressPrefix(account.address);
|
|
148
|
+
if (store.accounts.some((a) => a.name.toLowerCase() === account.name.toLowerCase())) {
|
|
149
|
+
throw new Error(`Account with name '${account.name}' already exists.`);
|
|
150
|
+
}
|
|
151
|
+
const newAccount = {
|
|
152
|
+
...account,
|
|
153
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
154
|
+
};
|
|
155
|
+
return {
|
|
156
|
+
...store,
|
|
157
|
+
accounts: [...store.accounts, newAccount]
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
function removeRealDevAccount(store, name) {
|
|
161
|
+
const index = store.accounts.findIndex(
|
|
162
|
+
(a) => a.name.toLowerCase() === name.toLowerCase()
|
|
163
|
+
);
|
|
164
|
+
if (index === -1) {
|
|
165
|
+
throw new Error(`Account with name '${name}' not found.`);
|
|
166
|
+
}
|
|
167
|
+
const newAccounts = [...store.accounts];
|
|
168
|
+
newAccounts.splice(index, 1);
|
|
169
|
+
return {
|
|
170
|
+
...store,
|
|
171
|
+
accounts: newAccounts
|
|
172
|
+
};
|
|
173
|
+
}
|
|
174
|
+
function getRealDevAccount(store, name) {
|
|
175
|
+
return store.accounts.find((a) => a.name.toLowerCase() === name.toLowerCase()) || null;
|
|
176
|
+
}
|
|
177
|
+
function listRealDevAccounts(store) {
|
|
178
|
+
return store.accounts;
|
|
179
|
+
}
|
|
180
|
+
function resolveRealAccountOrAddress(store, nameOrAddress) {
|
|
181
|
+
const account = store ? getRealDevAccount(store, nameOrAddress) : null;
|
|
182
|
+
if (account) {
|
|
183
|
+
return { address: account.address, name: account.name };
|
|
184
|
+
}
|
|
185
|
+
if (nameOrAddress.startsWith("kaspa:") || nameOrAddress.startsWith("kaspatest:") || nameOrAddress.startsWith("kaspasim:")) {
|
|
186
|
+
return { address: nameOrAddress };
|
|
187
|
+
}
|
|
188
|
+
throw new Error(
|
|
189
|
+
`'${nameOrAddress}' is not a registered real account name and is not a valid Kaspa address.`
|
|
190
|
+
);
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
// src/resolve.ts
|
|
194
|
+
function resolveHardkasAccount(options) {
|
|
195
|
+
const { nameOrAddress, config } = options;
|
|
196
|
+
if (nameOrAddress.startsWith("kaspa:") || nameOrAddress.startsWith("kaspatest:") || nameOrAddress.startsWith("kaspasim:")) {
|
|
197
|
+
return {
|
|
198
|
+
name: nameOrAddress,
|
|
199
|
+
kind: "external-wallet",
|
|
200
|
+
address: nameOrAddress
|
|
201
|
+
};
|
|
202
|
+
}
|
|
203
|
+
let alias = nameOrAddress;
|
|
204
|
+
if (alias === "0") alias = "alice";
|
|
205
|
+
if (alias === "1") alias = "bob";
|
|
206
|
+
const workspaceRoot = config?.cwd || process.cwd();
|
|
207
|
+
const devAccountPath = path2.join(
|
|
208
|
+
workspaceRoot,
|
|
209
|
+
".hardkas",
|
|
210
|
+
"dev-accounts",
|
|
211
|
+
`${alias}.json`
|
|
212
|
+
);
|
|
213
|
+
if (fs2.existsSync(devAccountPath)) {
|
|
214
|
+
try {
|
|
215
|
+
const data = fs2.readFileSync(devAccountPath, "utf-8");
|
|
216
|
+
const keystore = JSON.parse(data);
|
|
217
|
+
if (keystore.type === "hardkas.encryptedKeystore.v2") {
|
|
218
|
+
return {
|
|
219
|
+
name: alias,
|
|
220
|
+
kind: "kaspa-private-key",
|
|
221
|
+
address: keystore.metadata?.address,
|
|
222
|
+
keystorePath: devAccountPath
|
|
223
|
+
};
|
|
224
|
+
}
|
|
225
|
+
} catch (e) {
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
const keystoreJsonPath = path2.join(workspaceRoot, ".hardkas", "keystore.json");
|
|
229
|
+
if (fs2.existsSync(keystoreJsonPath)) {
|
|
230
|
+
try {
|
|
231
|
+
const data = fs2.readFileSync(keystoreJsonPath, "utf-8");
|
|
232
|
+
const ks = JSON.parse(data);
|
|
233
|
+
if (ks[alias]) {
|
|
234
|
+
return {
|
|
235
|
+
name: alias,
|
|
236
|
+
kind: ks[alias].type === "simulated" ? "simulated" : "kaspa-private-key",
|
|
237
|
+
address: ks[alias].address
|
|
238
|
+
};
|
|
239
|
+
}
|
|
240
|
+
} catch (e) {
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
if (config?.accounts && config.accounts[alias]) {
|
|
244
|
+
const accConfig = config.accounts[alias];
|
|
245
|
+
return {
|
|
246
|
+
name: alias,
|
|
247
|
+
...accConfig
|
|
248
|
+
};
|
|
249
|
+
}
|
|
250
|
+
const realStore = loadRealAccountStoreSync({ cwd: workspaceRoot });
|
|
251
|
+
const realAcc = realStore ? getRealDevAccount(realStore, alias) : null;
|
|
252
|
+
if (realAcc) {
|
|
253
|
+
return {
|
|
254
|
+
name: realAcc.name,
|
|
255
|
+
kind: "kaspa-private-key",
|
|
256
|
+
// Assuming Kaspa for now, could be extensible
|
|
257
|
+
address: realAcc.address,
|
|
258
|
+
...realAcc.privateKeyEnv ? { privateKeyEnv: realAcc.privateKeyEnv } : {},
|
|
259
|
+
...realAcc.privateKey ? { privateKey: realAcc.privateKey } : {}
|
|
260
|
+
};
|
|
261
|
+
}
|
|
262
|
+
const detAccounts = createDeterministicAccounts();
|
|
263
|
+
const det = detAccounts.find((a) => a.name === alias);
|
|
264
|
+
if (det) {
|
|
265
|
+
return {
|
|
266
|
+
name: det.name,
|
|
267
|
+
kind: "simulated",
|
|
268
|
+
address: det.address,
|
|
269
|
+
evmAddress: det.evmAddress
|
|
270
|
+
};
|
|
271
|
+
}
|
|
272
|
+
const available = listHardkasAccounts(config).map((a) => a.name).join(", ");
|
|
273
|
+
throw new Error(
|
|
274
|
+
`Unknown HardKAS account '${nameOrAddress}'. Available accounts: ${available}`
|
|
275
|
+
);
|
|
276
|
+
}
|
|
277
|
+
function listHardkasAccounts(config) {
|
|
278
|
+
const accounts = /* @__PURE__ */ new Map();
|
|
279
|
+
const detAccounts = createDeterministicAccounts();
|
|
280
|
+
for (const det of detAccounts) {
|
|
281
|
+
accounts.set(det.name, {
|
|
282
|
+
name: det.name,
|
|
283
|
+
kind: "simulated",
|
|
284
|
+
address: det.address,
|
|
285
|
+
evmAddress: det.evmAddress
|
|
286
|
+
});
|
|
287
|
+
}
|
|
288
|
+
const workspaceRoot = config?.cwd || process.cwd();
|
|
289
|
+
const devAccountsDir = path2.join(workspaceRoot, ".hardkas", "dev-accounts");
|
|
290
|
+
if (fs2.existsSync(devAccountsDir)) {
|
|
291
|
+
const files = fs2.readdirSync(devAccountsDir);
|
|
292
|
+
for (const file of files) {
|
|
293
|
+
if (file.endsWith(".json")) {
|
|
294
|
+
try {
|
|
295
|
+
const name = path2.basename(file, ".json");
|
|
296
|
+
const data = fs2.readFileSync(path2.join(devAccountsDir, file), "utf-8");
|
|
297
|
+
const keystore = JSON.parse(data);
|
|
298
|
+
if (keystore.type === "hardkas.encryptedKeystore.v2") {
|
|
299
|
+
accounts.set(name, {
|
|
300
|
+
name,
|
|
301
|
+
kind: "kaspa-private-key",
|
|
302
|
+
address: keystore.payload?.address || keystore.metadata?.address,
|
|
303
|
+
keystorePath: path2.join(devAccountsDir, file)
|
|
304
|
+
});
|
|
305
|
+
}
|
|
306
|
+
} catch (e) {
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
const keystoreJsonPath = path2.join(workspaceRoot, ".hardkas", "keystore.json");
|
|
312
|
+
if (fs2.existsSync(keystoreJsonPath)) {
|
|
313
|
+
try {
|
|
314
|
+
const data = fs2.readFileSync(keystoreJsonPath, "utf-8");
|
|
315
|
+
const ks = JSON.parse(data);
|
|
316
|
+
for (const [name, acc] of Object.entries(ks)) {
|
|
317
|
+
if (acc.type === "simulated") {
|
|
318
|
+
accounts.set(name, {
|
|
319
|
+
name,
|
|
320
|
+
kind: "simulated",
|
|
321
|
+
address: acc.address
|
|
322
|
+
});
|
|
323
|
+
} else {
|
|
324
|
+
accounts.set(name, {
|
|
325
|
+
name,
|
|
326
|
+
kind: "kaspa-private-key",
|
|
327
|
+
address: acc.address
|
|
328
|
+
});
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
} catch (e) {
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
const realStore = loadRealAccountStoreSync({ cwd: workspaceRoot });
|
|
335
|
+
if (realStore) {
|
|
336
|
+
for (const realAcc of listRealDevAccounts(realStore)) {
|
|
337
|
+
accounts.set(realAcc.name, {
|
|
338
|
+
name: realAcc.name,
|
|
339
|
+
kind: "kaspa-private-key",
|
|
340
|
+
address: realAcc.address,
|
|
341
|
+
...realAcc.privateKeyEnv ? { privateKeyEnv: realAcc.privateKeyEnv } : {},
|
|
342
|
+
...realAcc.privateKey ? { privateKey: realAcc.privateKey } : {}
|
|
343
|
+
});
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
const keystoreDir = path2.join(process.cwd(), ".hardkas", "keystore");
|
|
347
|
+
if (fs2.existsSync(keystoreDir)) {
|
|
348
|
+
const files = fs2.readdirSync(keystoreDir);
|
|
349
|
+
for (const file of files) {
|
|
350
|
+
if (file.endsWith(".json")) {
|
|
351
|
+
try {
|
|
352
|
+
const name = path2.basename(file, ".json");
|
|
353
|
+
const data = fs2.readFileSync(path2.join(keystoreDir, file), "utf-8");
|
|
354
|
+
const keystore = JSON.parse(data);
|
|
355
|
+
if (keystore.type === "hardkas.encryptedKeystore.v2") {
|
|
356
|
+
accounts.set(name, {
|
|
357
|
+
name,
|
|
358
|
+
kind: "kaspa-private-key",
|
|
359
|
+
address: keystore.payload?.address || keystore.metadata?.address,
|
|
360
|
+
// Payloads are encrypted, but address might be in metadata
|
|
361
|
+
keystorePath: path2.join(keystoreDir, file)
|
|
362
|
+
});
|
|
363
|
+
}
|
|
364
|
+
} catch (e) {
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
if (config?.accounts) {
|
|
370
|
+
for (const [name, accConfig] of Object.entries(config.accounts)) {
|
|
371
|
+
const existing = accounts.get(name);
|
|
372
|
+
if (existing && existing.kind === "kaspa-private-key" && accConfig.kind === "simulated") {
|
|
373
|
+
continue;
|
|
374
|
+
}
|
|
375
|
+
accounts.set(name, {
|
|
376
|
+
name,
|
|
377
|
+
...accConfig
|
|
378
|
+
});
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
return Array.from(accounts.values());
|
|
382
|
+
}
|
|
383
|
+
async function resolveHardkasAccountAddress(accountOrAddress, config, context = "L1") {
|
|
384
|
+
if (accountOrAddress.startsWith("kaspa:") || accountOrAddress.startsWith("kaspatest:") || accountOrAddress.startsWith("kaspasim:")) {
|
|
385
|
+
if (context === "L2") {
|
|
386
|
+
throw new Error(
|
|
387
|
+
`Invalid L2 address provided: ${accountOrAddress}. Expected EVM address or account alias.`
|
|
388
|
+
);
|
|
389
|
+
}
|
|
390
|
+
if (!accountOrAddress.startsWith("kaspa:sim_")) {
|
|
391
|
+
try {
|
|
392
|
+
const kaspa = await import("kaspa-wasm");
|
|
393
|
+
try {
|
|
394
|
+
if (typeof kaspa.Address === "function" || kaspa.Address) {
|
|
395
|
+
new kaspa.Address(accountOrAddress);
|
|
396
|
+
}
|
|
397
|
+
} catch (e) {
|
|
398
|
+
const err = new Error(
|
|
399
|
+
`HARDKAS_INVALID_ADDRESS: Invalid Kaspa address format or checksum.`
|
|
400
|
+
);
|
|
401
|
+
err.code = "HARDKAS_INVALID_ADDRESS";
|
|
402
|
+
throw err;
|
|
403
|
+
}
|
|
404
|
+
} catch (e) {
|
|
405
|
+
if (e instanceof Error && e.code === "HARDKAS_INVALID_ADDRESS") throw e;
|
|
406
|
+
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"))) {
|
|
407
|
+
const err = new Error(
|
|
408
|
+
"ADDRESS_VALIDATOR_UNAVAILABLE: The Kaspa address validator backend is not available."
|
|
409
|
+
);
|
|
410
|
+
err.code = "ADDRESS_VALIDATOR_UNAVAILABLE";
|
|
411
|
+
throw err;
|
|
412
|
+
}
|
|
413
|
+
throw e;
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
return accountOrAddress;
|
|
417
|
+
}
|
|
418
|
+
if (accountOrAddress.startsWith("0x") && accountOrAddress.length === 42) {
|
|
419
|
+
return accountOrAddress;
|
|
420
|
+
}
|
|
421
|
+
const account = resolveHardkasAccount({ nameOrAddress: accountOrAddress, config });
|
|
422
|
+
if (context === "L2") {
|
|
423
|
+
const evmAddress = account.evmAddress;
|
|
424
|
+
if (!evmAddress) {
|
|
425
|
+
throw new Error(
|
|
426
|
+
`Account '${account.name}' does not have an EVM address configured for L2.`
|
|
427
|
+
);
|
|
428
|
+
}
|
|
429
|
+
return evmAddress;
|
|
430
|
+
}
|
|
431
|
+
if (!account.address) {
|
|
432
|
+
throw new Error(`Account '${account.name}' does not have a resolved address yet.`);
|
|
433
|
+
}
|
|
434
|
+
return account.address;
|
|
435
|
+
}
|
|
436
|
+
function describeAccount(account) {
|
|
437
|
+
const desc = {
|
|
438
|
+
name: account.name,
|
|
439
|
+
kind: account.kind
|
|
440
|
+
};
|
|
441
|
+
if (account.address) {
|
|
442
|
+
desc.address = account.address;
|
|
443
|
+
}
|
|
444
|
+
if (account.kind === "kaspa-private-key" || account.kind === "evm-private-key") {
|
|
445
|
+
desc.privateKeyEnv = account.privateKeyEnv;
|
|
446
|
+
}
|
|
447
|
+
if (account.kind === "external-wallet" && account.walletId) {
|
|
448
|
+
desc.walletId = account.walletId;
|
|
449
|
+
}
|
|
450
|
+
return desc;
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
export {
|
|
454
|
+
getDefaultRealAccountsPath,
|
|
455
|
+
createEmptyRealAccountStore,
|
|
456
|
+
loadRealAccountStoreSync,
|
|
457
|
+
loadRealAccountStore,
|
|
458
|
+
loadOrCreateRealAccountStore,
|
|
459
|
+
saveRealAccountStore,
|
|
460
|
+
validateAccountName,
|
|
461
|
+
validateAddressPrefix,
|
|
462
|
+
validateAddressNetwork,
|
|
463
|
+
importRealDevAccount,
|
|
464
|
+
removeRealDevAccount,
|
|
465
|
+
getRealDevAccount,
|
|
466
|
+
listRealDevAccounts,
|
|
467
|
+
resolveRealAccountOrAddress,
|
|
468
|
+
resolveHardkasAccount,
|
|
469
|
+
listHardkasAccounts,
|
|
470
|
+
resolveHardkasAccountAddress,
|
|
471
|
+
describeAccount
|
|
472
|
+
};
|