@unicitylabs/sphere-sdk 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/LICENSE +21 -0
- package/README.md +1112 -0
- package/dist/core/index.cjs +7042 -0
- package/dist/core/index.cjs.map +1 -0
- package/dist/core/index.d.cts +2548 -0
- package/dist/core/index.d.ts +2548 -0
- package/dist/core/index.js +6946 -0
- package/dist/core/index.js.map +1 -0
- package/dist/impl/browser/index.cjs +7853 -0
- package/dist/impl/browser/index.cjs.map +1 -0
- package/dist/impl/browser/index.d.cts +3016 -0
- package/dist/impl/browser/index.d.ts +3016 -0
- package/dist/impl/browser/index.js +7841 -0
- package/dist/impl/browser/index.js.map +1 -0
- package/dist/impl/nodejs/index.cjs +1767 -0
- package/dist/impl/nodejs/index.cjs.map +1 -0
- package/dist/impl/nodejs/index.d.cts +1091 -0
- package/dist/impl/nodejs/index.d.ts +1091 -0
- package/dist/impl/nodejs/index.js +1722 -0
- package/dist/impl/nodejs/index.js.map +1 -0
- package/dist/index.cjs +7647 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +10533 -0
- package/dist/index.d.ts +10533 -0
- package/dist/index.js +7523 -0
- package/dist/index.js.map +1 -0
- package/dist/l1/index.cjs +1545 -0
- package/dist/l1/index.cjs.map +1 -0
- package/dist/l1/index.d.cts +705 -0
- package/dist/l1/index.d.ts +705 -0
- package/dist/l1/index.js +1455 -0
- package/dist/l1/index.js.map +1 -0
- package/package.json +140 -0
|
@@ -0,0 +1,1545 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __create = Object.create;
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
7
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
+
var __export = (target, all) => {
|
|
9
|
+
for (var name in all)
|
|
10
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
11
|
+
};
|
|
12
|
+
var __copyProps = (to, from, except, desc) => {
|
|
13
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
14
|
+
for (let key of __getOwnPropNames(from))
|
|
15
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
16
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
17
|
+
}
|
|
18
|
+
return to;
|
|
19
|
+
};
|
|
20
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
21
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
22
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
23
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
24
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
25
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
26
|
+
mod
|
|
27
|
+
));
|
|
28
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
29
|
+
|
|
30
|
+
// l1/index.ts
|
|
31
|
+
var l1_exports = {};
|
|
32
|
+
__export(l1_exports, {
|
|
33
|
+
CHARSET: () => CHARSET,
|
|
34
|
+
VESTING_THRESHOLD: () => VESTING_THRESHOLD,
|
|
35
|
+
WalletAddressHelper: () => WalletAddressHelper,
|
|
36
|
+
addressToScriptHash: () => addressToScriptHash,
|
|
37
|
+
broadcast: () => broadcast,
|
|
38
|
+
buildSegWitTransaction: () => buildSegWitTransaction,
|
|
39
|
+
collectUtxosForAmount: () => collectUtxosForAmount,
|
|
40
|
+
computeHash160: () => computeHash160,
|
|
41
|
+
connect: () => connect,
|
|
42
|
+
convertBits: () => convertBits,
|
|
43
|
+
createAndSignTransaction: () => createAndSignTransaction,
|
|
44
|
+
createBech32: () => createBech32,
|
|
45
|
+
createScriptPubKey: () => createScriptPubKey,
|
|
46
|
+
createTransactionPlan: () => createTransactionPlan,
|
|
47
|
+
decodeBech32: () => decodeBech32,
|
|
48
|
+
decrypt: () => decrypt,
|
|
49
|
+
decryptWallet: () => decryptWallet,
|
|
50
|
+
deriveChildKey: () => deriveChildKey2,
|
|
51
|
+
deriveChildKeyBIP32: () => deriveChildKeyBIP32,
|
|
52
|
+
deriveKeyAtPath: () => deriveKeyAtPath2,
|
|
53
|
+
disconnect: () => disconnect,
|
|
54
|
+
domIdToPath: () => domIdToPath,
|
|
55
|
+
ec: () => ec,
|
|
56
|
+
encodeBech32: () => encodeBech32,
|
|
57
|
+
encrypt: () => encrypt,
|
|
58
|
+
encryptWallet: () => encryptWallet,
|
|
59
|
+
generateAddressFromMasterKey: () => generateAddressFromMasterKey,
|
|
60
|
+
generateAddressInfo: () => generateAddressInfo,
|
|
61
|
+
generateHDAddress: () => generateHDAddress,
|
|
62
|
+
generateHDAddressBIP32: () => generateHDAddressBIP32,
|
|
63
|
+
generateMasterKeyFromSeed: () => generateMasterKeyFromSeed,
|
|
64
|
+
generatePrivateKey: () => generatePrivateKey,
|
|
65
|
+
getBalance: () => getBalance,
|
|
66
|
+
getBlockHeader: () => getBlockHeader,
|
|
67
|
+
getCurrentBlockHeight: () => getCurrentBlockHeight,
|
|
68
|
+
getIndexFromPath: () => getIndexFromPath,
|
|
69
|
+
getTransaction: () => getTransaction,
|
|
70
|
+
getTransactionHistory: () => getTransactionHistory,
|
|
71
|
+
getUtxo: () => getUtxo,
|
|
72
|
+
hash160: () => hash160,
|
|
73
|
+
hash160ToBytes: () => hash160ToBytes,
|
|
74
|
+
hexToWIF: () => hexToWIF,
|
|
75
|
+
isChangePath: () => isChangePath,
|
|
76
|
+
isWebSocketConnected: () => isWebSocketConnected,
|
|
77
|
+
parsePathComponents: () => parsePathComponents,
|
|
78
|
+
pathToDOMId: () => pathToDOMId,
|
|
79
|
+
privateKeyToAddressInfo: () => privateKeyToAddressInfo,
|
|
80
|
+
publicKeyToAddress: () => publicKeyToAddress,
|
|
81
|
+
rpc: () => rpc,
|
|
82
|
+
sendAlpha: () => sendAlpha,
|
|
83
|
+
subscribeBlocks: () => subscribeBlocks,
|
|
84
|
+
vestingClassifier: () => vestingClassifier,
|
|
85
|
+
vestingState: () => vestingState,
|
|
86
|
+
waitForConnection: () => waitForConnection
|
|
87
|
+
});
|
|
88
|
+
module.exports = __toCommonJS(l1_exports);
|
|
89
|
+
|
|
90
|
+
// l1/types.ts
|
|
91
|
+
function parsePathComponents(path) {
|
|
92
|
+
const match = path.match(/m\/\d+'\/\d+'\/\d+'\/(\d+)\/(\d+)/);
|
|
93
|
+
if (!match) return null;
|
|
94
|
+
return { chain: parseInt(match[1], 10), index: parseInt(match[2], 10) };
|
|
95
|
+
}
|
|
96
|
+
function isChangePath(path) {
|
|
97
|
+
const parsed = parsePathComponents(path);
|
|
98
|
+
return parsed?.chain === 1;
|
|
99
|
+
}
|
|
100
|
+
function getIndexFromPath(path) {
|
|
101
|
+
const parsed = parsePathComponents(path);
|
|
102
|
+
return parsed?.index ?? 0;
|
|
103
|
+
}
|
|
104
|
+
function pathToDOMId(path) {
|
|
105
|
+
return path.replace(/'/g, "h").replace(/\//g, "-");
|
|
106
|
+
}
|
|
107
|
+
function domIdToPath(encoded) {
|
|
108
|
+
const parts = encoded.split("-");
|
|
109
|
+
return parts.map((part, idx) => {
|
|
110
|
+
if (idx === 0) return part;
|
|
111
|
+
return part.endsWith("h") ? `${part.slice(0, -1)}'` : part;
|
|
112
|
+
}).join("/");
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// core/bech32.ts
|
|
116
|
+
var CHARSET = "qpzry9x8gf2tvdw0s3jn54khce6mua7l";
|
|
117
|
+
var GENERATOR = [996825010, 642813549, 513874426, 1027748829, 705979059];
|
|
118
|
+
function convertBits(data, fromBits, toBits, pad) {
|
|
119
|
+
let acc = 0;
|
|
120
|
+
let bits = 0;
|
|
121
|
+
const ret = [];
|
|
122
|
+
const maxv = (1 << toBits) - 1;
|
|
123
|
+
for (let i = 0; i < data.length; i++) {
|
|
124
|
+
const value = data[i];
|
|
125
|
+
if (value < 0 || value >> fromBits !== 0) return null;
|
|
126
|
+
acc = acc << fromBits | value;
|
|
127
|
+
bits += fromBits;
|
|
128
|
+
while (bits >= toBits) {
|
|
129
|
+
bits -= toBits;
|
|
130
|
+
ret.push(acc >> bits & maxv);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
if (pad) {
|
|
134
|
+
if (bits > 0) {
|
|
135
|
+
ret.push(acc << toBits - bits & maxv);
|
|
136
|
+
}
|
|
137
|
+
} else if (bits >= fromBits || acc << toBits - bits & maxv) {
|
|
138
|
+
return null;
|
|
139
|
+
}
|
|
140
|
+
return ret;
|
|
141
|
+
}
|
|
142
|
+
function hrpExpand(hrp) {
|
|
143
|
+
const ret = [];
|
|
144
|
+
for (let i = 0; i < hrp.length; i++) ret.push(hrp.charCodeAt(i) >> 5);
|
|
145
|
+
ret.push(0);
|
|
146
|
+
for (let i = 0; i < hrp.length; i++) ret.push(hrp.charCodeAt(i) & 31);
|
|
147
|
+
return ret;
|
|
148
|
+
}
|
|
149
|
+
function bech32Polymod(values) {
|
|
150
|
+
let chk = 1;
|
|
151
|
+
for (let p = 0; p < values.length; p++) {
|
|
152
|
+
const top = chk >> 25;
|
|
153
|
+
chk = (chk & 33554431) << 5 ^ values[p];
|
|
154
|
+
for (let i = 0; i < 5; i++) {
|
|
155
|
+
if (top >> i & 1) chk ^= GENERATOR[i];
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
return chk;
|
|
159
|
+
}
|
|
160
|
+
function bech32Checksum(hrp, data) {
|
|
161
|
+
const values = hrpExpand(hrp).concat(data).concat([0, 0, 0, 0, 0, 0]);
|
|
162
|
+
const mod = bech32Polymod(values) ^ 1;
|
|
163
|
+
const ret = [];
|
|
164
|
+
for (let p = 0; p < 6; p++) {
|
|
165
|
+
ret.push(mod >> 5 * (5 - p) & 31);
|
|
166
|
+
}
|
|
167
|
+
return ret;
|
|
168
|
+
}
|
|
169
|
+
function encodeBech32(hrp, version, program) {
|
|
170
|
+
if (version < 0 || version > 16) {
|
|
171
|
+
throw new Error("Invalid witness version");
|
|
172
|
+
}
|
|
173
|
+
const converted = convertBits(Array.from(program), 8, 5, true);
|
|
174
|
+
if (!converted) {
|
|
175
|
+
throw new Error("Failed to convert bits");
|
|
176
|
+
}
|
|
177
|
+
const data = [version].concat(converted);
|
|
178
|
+
const checksum = bech32Checksum(hrp, data);
|
|
179
|
+
const combined = data.concat(checksum);
|
|
180
|
+
let out = hrp + "1";
|
|
181
|
+
for (let i = 0; i < combined.length; i++) {
|
|
182
|
+
out += CHARSET[combined[i]];
|
|
183
|
+
}
|
|
184
|
+
return out;
|
|
185
|
+
}
|
|
186
|
+
function decodeBech32(addr) {
|
|
187
|
+
addr = addr.toLowerCase();
|
|
188
|
+
const pos = addr.lastIndexOf("1");
|
|
189
|
+
if (pos < 1) return null;
|
|
190
|
+
const hrp = addr.substring(0, pos);
|
|
191
|
+
const dataStr = addr.substring(pos + 1);
|
|
192
|
+
const data = [];
|
|
193
|
+
for (let i = 0; i < dataStr.length; i++) {
|
|
194
|
+
const val = CHARSET.indexOf(dataStr[i]);
|
|
195
|
+
if (val === -1) return null;
|
|
196
|
+
data.push(val);
|
|
197
|
+
}
|
|
198
|
+
const checksum = bech32Checksum(hrp, data.slice(0, -6));
|
|
199
|
+
for (let i = 0; i < 6; i++) {
|
|
200
|
+
if (checksum[i] !== data[data.length - 6 + i]) {
|
|
201
|
+
return null;
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
const version = data[0];
|
|
205
|
+
const program = convertBits(data.slice(1, -6), 5, 8, false);
|
|
206
|
+
if (!program) return null;
|
|
207
|
+
return {
|
|
208
|
+
hrp,
|
|
209
|
+
witnessVersion: version,
|
|
210
|
+
data: Uint8Array.from(program)
|
|
211
|
+
};
|
|
212
|
+
}
|
|
213
|
+
var createBech32 = encodeBech32;
|
|
214
|
+
|
|
215
|
+
// l1/addressToScriptHash.ts
|
|
216
|
+
var import_crypto_js = __toESM(require("crypto-js"), 1);
|
|
217
|
+
function bytesToHex(buf) {
|
|
218
|
+
return Array.from(buf).map((b) => b.toString(16).padStart(2, "0")).join("");
|
|
219
|
+
}
|
|
220
|
+
function addressToScriptHash(address) {
|
|
221
|
+
const decoded = decodeBech32(address);
|
|
222
|
+
if (!decoded) throw new Error("Invalid bech32 address: " + address);
|
|
223
|
+
const scriptHex = "0014" + bytesToHex(decoded.data);
|
|
224
|
+
const sha = import_crypto_js.default.SHA256(import_crypto_js.default.enc.Hex.parse(scriptHex)).toString();
|
|
225
|
+
return sha.match(/../g).reverse().join("");
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
// core/crypto.ts
|
|
229
|
+
var bip39 = __toESM(require("bip39"), 1);
|
|
230
|
+
var import_crypto_js2 = __toESM(require("crypto-js"), 1);
|
|
231
|
+
var import_elliptic = __toESM(require("elliptic"), 1);
|
|
232
|
+
var ec = new import_elliptic.default.ec("secp256k1");
|
|
233
|
+
var CURVE_ORDER = BigInt(
|
|
234
|
+
"0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141"
|
|
235
|
+
);
|
|
236
|
+
function generateMasterKey(seedHex) {
|
|
237
|
+
const I = import_crypto_js2.default.HmacSHA512(
|
|
238
|
+
import_crypto_js2.default.enc.Hex.parse(seedHex),
|
|
239
|
+
import_crypto_js2.default.enc.Utf8.parse("Bitcoin seed")
|
|
240
|
+
).toString();
|
|
241
|
+
const IL = I.substring(0, 64);
|
|
242
|
+
const IR = I.substring(64);
|
|
243
|
+
const masterKeyBigInt = BigInt("0x" + IL);
|
|
244
|
+
if (masterKeyBigInt === 0n || masterKeyBigInt >= CURVE_ORDER) {
|
|
245
|
+
throw new Error("Invalid master key generated");
|
|
246
|
+
}
|
|
247
|
+
return {
|
|
248
|
+
privateKey: IL,
|
|
249
|
+
chainCode: IR
|
|
250
|
+
};
|
|
251
|
+
}
|
|
252
|
+
function deriveChildKey(parentPrivKey, parentChainCode, index) {
|
|
253
|
+
const isHardened = index >= 2147483648;
|
|
254
|
+
let data;
|
|
255
|
+
if (isHardened) {
|
|
256
|
+
const indexHex = index.toString(16).padStart(8, "0");
|
|
257
|
+
data = "00" + parentPrivKey + indexHex;
|
|
258
|
+
} else {
|
|
259
|
+
const keyPair = ec.keyFromPrivate(parentPrivKey, "hex");
|
|
260
|
+
const compressedPubKey = keyPair.getPublic(true, "hex");
|
|
261
|
+
const indexHex = index.toString(16).padStart(8, "0");
|
|
262
|
+
data = compressedPubKey + indexHex;
|
|
263
|
+
}
|
|
264
|
+
const I = import_crypto_js2.default.HmacSHA512(
|
|
265
|
+
import_crypto_js2.default.enc.Hex.parse(data),
|
|
266
|
+
import_crypto_js2.default.enc.Hex.parse(parentChainCode)
|
|
267
|
+
).toString();
|
|
268
|
+
const IL = I.substring(0, 64);
|
|
269
|
+
const IR = I.substring(64);
|
|
270
|
+
const ilBigInt = BigInt("0x" + IL);
|
|
271
|
+
const parentKeyBigInt = BigInt("0x" + parentPrivKey);
|
|
272
|
+
if (ilBigInt >= CURVE_ORDER) {
|
|
273
|
+
throw new Error("Invalid key: IL >= curve order");
|
|
274
|
+
}
|
|
275
|
+
const childKeyBigInt = (ilBigInt + parentKeyBigInt) % CURVE_ORDER;
|
|
276
|
+
if (childKeyBigInt === 0n) {
|
|
277
|
+
throw new Error("Invalid key: child key is zero");
|
|
278
|
+
}
|
|
279
|
+
const childPrivKey = childKeyBigInt.toString(16).padStart(64, "0");
|
|
280
|
+
return {
|
|
281
|
+
privateKey: childPrivKey,
|
|
282
|
+
chainCode: IR
|
|
283
|
+
};
|
|
284
|
+
}
|
|
285
|
+
function deriveKeyAtPath(masterPrivKey, masterChainCode, path) {
|
|
286
|
+
const pathParts = path.replace("m/", "").split("/");
|
|
287
|
+
let currentKey = masterPrivKey;
|
|
288
|
+
let currentChainCode = masterChainCode;
|
|
289
|
+
for (const part of pathParts) {
|
|
290
|
+
const isHardened = part.endsWith("'") || part.endsWith("h");
|
|
291
|
+
const indexStr = part.replace(/['h]$/, "");
|
|
292
|
+
let index = parseInt(indexStr, 10);
|
|
293
|
+
if (isHardened) {
|
|
294
|
+
index += 2147483648;
|
|
295
|
+
}
|
|
296
|
+
const derived = deriveChildKey(currentKey, currentChainCode, index);
|
|
297
|
+
currentKey = derived.privateKey;
|
|
298
|
+
currentChainCode = derived.chainCode;
|
|
299
|
+
}
|
|
300
|
+
return {
|
|
301
|
+
privateKey: currentKey,
|
|
302
|
+
chainCode: currentChainCode
|
|
303
|
+
};
|
|
304
|
+
}
|
|
305
|
+
function getPublicKey(privateKey, compressed = true) {
|
|
306
|
+
const keyPair = ec.keyFromPrivate(privateKey, "hex");
|
|
307
|
+
return keyPair.getPublic(compressed, "hex");
|
|
308
|
+
}
|
|
309
|
+
function sha256(data, inputEncoding = "hex") {
|
|
310
|
+
const parsed = inputEncoding === "hex" ? import_crypto_js2.default.enc.Hex.parse(data) : import_crypto_js2.default.enc.Utf8.parse(data);
|
|
311
|
+
return import_crypto_js2.default.SHA256(parsed).toString();
|
|
312
|
+
}
|
|
313
|
+
function ripemd160(data, inputEncoding = "hex") {
|
|
314
|
+
const parsed = inputEncoding === "hex" ? import_crypto_js2.default.enc.Hex.parse(data) : import_crypto_js2.default.enc.Utf8.parse(data);
|
|
315
|
+
return import_crypto_js2.default.RIPEMD160(parsed).toString();
|
|
316
|
+
}
|
|
317
|
+
function hash160(data) {
|
|
318
|
+
const sha = sha256(data, "hex");
|
|
319
|
+
return ripemd160(sha, "hex");
|
|
320
|
+
}
|
|
321
|
+
var computeHash160 = hash160;
|
|
322
|
+
function hash160ToBytes(hash160Hex) {
|
|
323
|
+
const matches = hash160Hex.match(/../g);
|
|
324
|
+
if (!matches) return new Uint8Array(0);
|
|
325
|
+
return Uint8Array.from(matches.map((x) => parseInt(x, 16)));
|
|
326
|
+
}
|
|
327
|
+
function publicKeyToAddress(publicKey, prefix = "alpha", witnessVersion = 0) {
|
|
328
|
+
const pubKeyHash = hash160(publicKey);
|
|
329
|
+
const programBytes = hash160ToBytes(pubKeyHash);
|
|
330
|
+
return encodeBech32(prefix, witnessVersion, programBytes);
|
|
331
|
+
}
|
|
332
|
+
function privateKeyToAddressInfo(privateKey, prefix = "alpha") {
|
|
333
|
+
const publicKey = getPublicKey(privateKey);
|
|
334
|
+
const address = publicKeyToAddress(publicKey, prefix);
|
|
335
|
+
return { address, publicKey };
|
|
336
|
+
}
|
|
337
|
+
function generateAddressInfo(privateKey, index, path, prefix = "alpha") {
|
|
338
|
+
const { address, publicKey } = privateKeyToAddressInfo(privateKey, prefix);
|
|
339
|
+
return {
|
|
340
|
+
privateKey,
|
|
341
|
+
publicKey,
|
|
342
|
+
address,
|
|
343
|
+
path,
|
|
344
|
+
index
|
|
345
|
+
};
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
// l1/crypto.ts
|
|
349
|
+
var import_crypto_js3 = __toESM(require("crypto-js"), 1);
|
|
350
|
+
var SALT = "alpha_wallet_salt";
|
|
351
|
+
var PBKDF2_ITERATIONS = 1e5;
|
|
352
|
+
function encrypt(text, password) {
|
|
353
|
+
return import_crypto_js3.default.AES.encrypt(text, password).toString();
|
|
354
|
+
}
|
|
355
|
+
function decrypt(encrypted, password) {
|
|
356
|
+
const bytes = import_crypto_js3.default.AES.decrypt(encrypted, password);
|
|
357
|
+
return bytes.toString(import_crypto_js3.default.enc.Utf8);
|
|
358
|
+
}
|
|
359
|
+
function generatePrivateKey() {
|
|
360
|
+
return import_crypto_js3.default.lib.WordArray.random(32).toString();
|
|
361
|
+
}
|
|
362
|
+
function encryptWallet(masterPrivateKey, password) {
|
|
363
|
+
const passwordKey = import_crypto_js3.default.PBKDF2(password, SALT, {
|
|
364
|
+
keySize: 256 / 32,
|
|
365
|
+
iterations: PBKDF2_ITERATIONS
|
|
366
|
+
}).toString();
|
|
367
|
+
const encrypted = import_crypto_js3.default.AES.encrypt(
|
|
368
|
+
masterPrivateKey,
|
|
369
|
+
passwordKey
|
|
370
|
+
).toString();
|
|
371
|
+
return encrypted;
|
|
372
|
+
}
|
|
373
|
+
function decryptWallet(encryptedData, password) {
|
|
374
|
+
const passwordKey = import_crypto_js3.default.PBKDF2(password, SALT, {
|
|
375
|
+
keySize: 256 / 32,
|
|
376
|
+
iterations: PBKDF2_ITERATIONS
|
|
377
|
+
}).toString();
|
|
378
|
+
const decrypted = import_crypto_js3.default.AES.decrypt(encryptedData, passwordKey);
|
|
379
|
+
return decrypted.toString(import_crypto_js3.default.enc.Utf8);
|
|
380
|
+
}
|
|
381
|
+
function hexToWIF(hexKey) {
|
|
382
|
+
const versionByte = "80";
|
|
383
|
+
const extendedKey = versionByte + hexKey;
|
|
384
|
+
const hash1 = import_crypto_js3.default.SHA256(import_crypto_js3.default.enc.Hex.parse(extendedKey)).toString();
|
|
385
|
+
const hash2 = import_crypto_js3.default.SHA256(import_crypto_js3.default.enc.Hex.parse(hash1)).toString();
|
|
386
|
+
const checksum = hash2.substring(0, 8);
|
|
387
|
+
const finalHex = extendedKey + checksum;
|
|
388
|
+
return base58Encode(finalHex);
|
|
389
|
+
}
|
|
390
|
+
function base58Encode(hex) {
|
|
391
|
+
const ALPHABET = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz";
|
|
392
|
+
let num = BigInt("0x" + hex);
|
|
393
|
+
let encoded = "";
|
|
394
|
+
while (num > 0n) {
|
|
395
|
+
const remainder = Number(num % 58n);
|
|
396
|
+
num = num / 58n;
|
|
397
|
+
encoded = ALPHABET[remainder] + encoded;
|
|
398
|
+
}
|
|
399
|
+
for (let i = 0; i < hex.length && hex.substring(i, i + 2) === "00"; i += 2) {
|
|
400
|
+
encoded = "1" + encoded;
|
|
401
|
+
}
|
|
402
|
+
return encoded;
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
// l1/address.ts
|
|
406
|
+
var import_crypto_js4 = __toESM(require("crypto-js"), 1);
|
|
407
|
+
var deriveChildKeyBIP32 = deriveChildKey;
|
|
408
|
+
var deriveKeyAtPath2 = deriveKeyAtPath;
|
|
409
|
+
function generateMasterKeyFromSeed(seedHex) {
|
|
410
|
+
const result = generateMasterKey(seedHex);
|
|
411
|
+
return {
|
|
412
|
+
masterPrivateKey: result.privateKey,
|
|
413
|
+
masterChainCode: result.chainCode
|
|
414
|
+
};
|
|
415
|
+
}
|
|
416
|
+
function generateHDAddressBIP32(masterPriv, chainCode, index, basePath = "m/44'/0'/0'", isChange = false) {
|
|
417
|
+
const chain = isChange ? 1 : 0;
|
|
418
|
+
const fullPath = `${basePath}/${chain}/${index}`;
|
|
419
|
+
const derived = deriveKeyAtPath(masterPriv, chainCode, fullPath);
|
|
420
|
+
return generateAddressInfo(derived.privateKey, index, fullPath);
|
|
421
|
+
}
|
|
422
|
+
function generateAddressFromMasterKey(masterPrivateKey, index) {
|
|
423
|
+
const derivationPath = `m/44'/0'/${index}'`;
|
|
424
|
+
const hmacInput = import_crypto_js4.default.enc.Hex.parse(masterPrivateKey);
|
|
425
|
+
const hmacKey = import_crypto_js4.default.enc.Utf8.parse(derivationPath);
|
|
426
|
+
const hmacOutput = import_crypto_js4.default.HmacSHA512(hmacInput, hmacKey).toString();
|
|
427
|
+
const childPrivateKey = hmacOutput.substring(0, 64);
|
|
428
|
+
return generateAddressInfo(childPrivateKey, index, derivationPath);
|
|
429
|
+
}
|
|
430
|
+
function deriveChildKey2(masterPriv, chainCode, index) {
|
|
431
|
+
const data = masterPriv + index.toString(16).padStart(8, "0");
|
|
432
|
+
const I = import_crypto_js4.default.HmacSHA512(
|
|
433
|
+
import_crypto_js4.default.enc.Hex.parse(data),
|
|
434
|
+
import_crypto_js4.default.enc.Hex.parse(chainCode)
|
|
435
|
+
).toString();
|
|
436
|
+
return {
|
|
437
|
+
privateKey: I.substring(0, 64),
|
|
438
|
+
nextChainCode: I.substring(64)
|
|
439
|
+
};
|
|
440
|
+
}
|
|
441
|
+
function generateHDAddress(masterPriv, chainCode, index) {
|
|
442
|
+
const child = deriveChildKey2(masterPriv, chainCode, index);
|
|
443
|
+
const path = `m/44'/0'/0'/${index}`;
|
|
444
|
+
return generateAddressInfo(child.privateKey, index, path);
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
// l1/network.ts
|
|
448
|
+
var DEFAULT_ENDPOINT = "wss://fulcrum.unicity.network:50004";
|
|
449
|
+
var ws = null;
|
|
450
|
+
var isConnected = false;
|
|
451
|
+
var isConnecting = false;
|
|
452
|
+
var requestId = 0;
|
|
453
|
+
var intentionalClose = false;
|
|
454
|
+
var reconnectAttempts = 0;
|
|
455
|
+
var isBlockSubscribed = false;
|
|
456
|
+
var lastBlockHeader = null;
|
|
457
|
+
var pending = {};
|
|
458
|
+
var blockSubscribers = [];
|
|
459
|
+
var connectionCallbacks = [];
|
|
460
|
+
var MAX_RECONNECT_ATTEMPTS = 10;
|
|
461
|
+
var BASE_DELAY = 2e3;
|
|
462
|
+
var MAX_DELAY = 6e4;
|
|
463
|
+
var RPC_TIMEOUT = 3e4;
|
|
464
|
+
var CONNECTION_TIMEOUT = 3e4;
|
|
465
|
+
function isWebSocketConnected() {
|
|
466
|
+
return isConnected && ws !== null && ws.readyState === WebSocket.OPEN;
|
|
467
|
+
}
|
|
468
|
+
function waitForConnection() {
|
|
469
|
+
if (isWebSocketConnected()) {
|
|
470
|
+
return Promise.resolve();
|
|
471
|
+
}
|
|
472
|
+
return new Promise((resolve, reject) => {
|
|
473
|
+
const callback = {
|
|
474
|
+
resolve: () => {
|
|
475
|
+
if (callback.timeoutId) clearTimeout(callback.timeoutId);
|
|
476
|
+
resolve();
|
|
477
|
+
},
|
|
478
|
+
reject: (err) => {
|
|
479
|
+
if (callback.timeoutId) clearTimeout(callback.timeoutId);
|
|
480
|
+
reject(err);
|
|
481
|
+
}
|
|
482
|
+
};
|
|
483
|
+
callback.timeoutId = setTimeout(() => {
|
|
484
|
+
const idx = connectionCallbacks.indexOf(callback);
|
|
485
|
+
if (idx > -1) connectionCallbacks.splice(idx, 1);
|
|
486
|
+
reject(new Error("Connection timeout"));
|
|
487
|
+
}, CONNECTION_TIMEOUT);
|
|
488
|
+
connectionCallbacks.push(callback);
|
|
489
|
+
});
|
|
490
|
+
}
|
|
491
|
+
function connect(endpoint = DEFAULT_ENDPOINT) {
|
|
492
|
+
if (isConnected) {
|
|
493
|
+
return Promise.resolve();
|
|
494
|
+
}
|
|
495
|
+
if (isConnecting) {
|
|
496
|
+
return waitForConnection();
|
|
497
|
+
}
|
|
498
|
+
isConnecting = true;
|
|
499
|
+
return new Promise((resolve, reject) => {
|
|
500
|
+
let hasResolved = false;
|
|
501
|
+
try {
|
|
502
|
+
ws = new WebSocket(endpoint);
|
|
503
|
+
} catch (err) {
|
|
504
|
+
console.error("[L1] WebSocket constructor threw exception:", err);
|
|
505
|
+
isConnecting = false;
|
|
506
|
+
reject(err);
|
|
507
|
+
return;
|
|
508
|
+
}
|
|
509
|
+
ws.onopen = () => {
|
|
510
|
+
isConnected = true;
|
|
511
|
+
isConnecting = false;
|
|
512
|
+
reconnectAttempts = 0;
|
|
513
|
+
hasResolved = true;
|
|
514
|
+
resolve();
|
|
515
|
+
connectionCallbacks.forEach((cb) => {
|
|
516
|
+
if (cb.timeoutId) clearTimeout(cb.timeoutId);
|
|
517
|
+
cb.resolve();
|
|
518
|
+
});
|
|
519
|
+
connectionCallbacks.length = 0;
|
|
520
|
+
};
|
|
521
|
+
ws.onclose = () => {
|
|
522
|
+
isConnected = false;
|
|
523
|
+
isBlockSubscribed = false;
|
|
524
|
+
Object.values(pending).forEach((req) => {
|
|
525
|
+
if (req.timeoutId) clearTimeout(req.timeoutId);
|
|
526
|
+
req.reject(new Error("WebSocket connection closed"));
|
|
527
|
+
});
|
|
528
|
+
Object.keys(pending).forEach((key) => delete pending[Number(key)]);
|
|
529
|
+
if (intentionalClose) {
|
|
530
|
+
intentionalClose = false;
|
|
531
|
+
isConnecting = false;
|
|
532
|
+
reconnectAttempts = 0;
|
|
533
|
+
if (!hasResolved) {
|
|
534
|
+
hasResolved = true;
|
|
535
|
+
reject(new Error("WebSocket connection closed intentionally"));
|
|
536
|
+
}
|
|
537
|
+
return;
|
|
538
|
+
}
|
|
539
|
+
if (reconnectAttempts >= MAX_RECONNECT_ATTEMPTS) {
|
|
540
|
+
console.error("[L1] Max reconnect attempts reached. Giving up.");
|
|
541
|
+
isConnecting = false;
|
|
542
|
+
const error = new Error("Max reconnect attempts reached");
|
|
543
|
+
connectionCallbacks.forEach((cb) => {
|
|
544
|
+
if (cb.timeoutId) clearTimeout(cb.timeoutId);
|
|
545
|
+
cb.reject(error);
|
|
546
|
+
});
|
|
547
|
+
connectionCallbacks.length = 0;
|
|
548
|
+
if (!hasResolved) {
|
|
549
|
+
hasResolved = true;
|
|
550
|
+
reject(error);
|
|
551
|
+
}
|
|
552
|
+
return;
|
|
553
|
+
}
|
|
554
|
+
const delay = Math.min(BASE_DELAY * Math.pow(2, reconnectAttempts), MAX_DELAY);
|
|
555
|
+
reconnectAttempts++;
|
|
556
|
+
console.warn(
|
|
557
|
+
`[L1] WebSocket closed unexpectedly. Reconnecting in ${delay}ms (attempt ${reconnectAttempts}/${MAX_RECONNECT_ATTEMPTS})...`
|
|
558
|
+
);
|
|
559
|
+
setTimeout(() => {
|
|
560
|
+
connect(endpoint).then(() => {
|
|
561
|
+
if (!hasResolved) {
|
|
562
|
+
hasResolved = true;
|
|
563
|
+
resolve();
|
|
564
|
+
}
|
|
565
|
+
}).catch((err) => {
|
|
566
|
+
if (!hasResolved) {
|
|
567
|
+
hasResolved = true;
|
|
568
|
+
reject(err);
|
|
569
|
+
}
|
|
570
|
+
});
|
|
571
|
+
}, delay);
|
|
572
|
+
};
|
|
573
|
+
ws.onerror = (err) => {
|
|
574
|
+
console.error("[L1] WebSocket error:", err);
|
|
575
|
+
};
|
|
576
|
+
ws.onmessage = (msg) => handleMessage(msg);
|
|
577
|
+
});
|
|
578
|
+
}
|
|
579
|
+
function handleMessage(event) {
|
|
580
|
+
const data = JSON.parse(event.data);
|
|
581
|
+
if (data.id && pending[data.id]) {
|
|
582
|
+
const request = pending[data.id];
|
|
583
|
+
delete pending[data.id];
|
|
584
|
+
if (data.error) {
|
|
585
|
+
request.reject(data.error);
|
|
586
|
+
} else {
|
|
587
|
+
request.resolve(data.result);
|
|
588
|
+
}
|
|
589
|
+
}
|
|
590
|
+
if (data.method === "blockchain.headers.subscribe") {
|
|
591
|
+
const header = data.params[0];
|
|
592
|
+
lastBlockHeader = header;
|
|
593
|
+
blockSubscribers.forEach((cb) => cb(header));
|
|
594
|
+
}
|
|
595
|
+
}
|
|
596
|
+
async function rpc(method, params = []) {
|
|
597
|
+
if (!isConnected && !isConnecting) {
|
|
598
|
+
await connect();
|
|
599
|
+
}
|
|
600
|
+
if (!isWebSocketConnected()) {
|
|
601
|
+
await waitForConnection();
|
|
602
|
+
}
|
|
603
|
+
return new Promise((resolve, reject) => {
|
|
604
|
+
if (!ws || ws.readyState !== WebSocket.OPEN) {
|
|
605
|
+
return reject(new Error("WebSocket not connected (OPEN)"));
|
|
606
|
+
}
|
|
607
|
+
const id = ++requestId;
|
|
608
|
+
const timeoutId = setTimeout(() => {
|
|
609
|
+
if (pending[id]) {
|
|
610
|
+
delete pending[id];
|
|
611
|
+
reject(new Error(`RPC timeout: ${method}`));
|
|
612
|
+
}
|
|
613
|
+
}, RPC_TIMEOUT);
|
|
614
|
+
pending[id] = {
|
|
615
|
+
resolve: (result) => {
|
|
616
|
+
clearTimeout(timeoutId);
|
|
617
|
+
resolve(result);
|
|
618
|
+
},
|
|
619
|
+
reject: (err) => {
|
|
620
|
+
clearTimeout(timeoutId);
|
|
621
|
+
reject(err);
|
|
622
|
+
},
|
|
623
|
+
timeoutId
|
|
624
|
+
};
|
|
625
|
+
ws.send(JSON.stringify({ jsonrpc: "2.0", id, method, params }));
|
|
626
|
+
});
|
|
627
|
+
}
|
|
628
|
+
async function getUtxo(address) {
|
|
629
|
+
const scripthash = addressToScriptHash(address);
|
|
630
|
+
const result = await rpc("blockchain.scripthash.listunspent", [scripthash]);
|
|
631
|
+
if (!Array.isArray(result)) {
|
|
632
|
+
console.warn("listunspent returned non-array:", result);
|
|
633
|
+
return [];
|
|
634
|
+
}
|
|
635
|
+
return result.map((u) => ({
|
|
636
|
+
tx_hash: u.tx_hash,
|
|
637
|
+
tx_pos: u.tx_pos,
|
|
638
|
+
value: u.value,
|
|
639
|
+
height: u.height,
|
|
640
|
+
address
|
|
641
|
+
}));
|
|
642
|
+
}
|
|
643
|
+
async function getBalance(address) {
|
|
644
|
+
const scriptHash = addressToScriptHash(address);
|
|
645
|
+
const result = await rpc("blockchain.scripthash.get_balance", [scriptHash]);
|
|
646
|
+
const confirmed = result.confirmed || 0;
|
|
647
|
+
const unconfirmed = result.unconfirmed || 0;
|
|
648
|
+
const totalSats = confirmed + unconfirmed;
|
|
649
|
+
const alpha = totalSats / 1e8;
|
|
650
|
+
return alpha;
|
|
651
|
+
}
|
|
652
|
+
async function broadcast(rawHex) {
|
|
653
|
+
return await rpc("blockchain.transaction.broadcast", [rawHex]);
|
|
654
|
+
}
|
|
655
|
+
async function subscribeBlocks(cb) {
|
|
656
|
+
if (!isConnected && !isConnecting) {
|
|
657
|
+
await connect();
|
|
658
|
+
}
|
|
659
|
+
if (!isWebSocketConnected()) {
|
|
660
|
+
await waitForConnection();
|
|
661
|
+
}
|
|
662
|
+
blockSubscribers.push(cb);
|
|
663
|
+
if (!isBlockSubscribed) {
|
|
664
|
+
isBlockSubscribed = true;
|
|
665
|
+
const header = await rpc("blockchain.headers.subscribe", []);
|
|
666
|
+
if (header) {
|
|
667
|
+
lastBlockHeader = header;
|
|
668
|
+
blockSubscribers.forEach((subscriber) => subscriber(header));
|
|
669
|
+
}
|
|
670
|
+
} else if (lastBlockHeader) {
|
|
671
|
+
cb(lastBlockHeader);
|
|
672
|
+
}
|
|
673
|
+
return () => {
|
|
674
|
+
const index = blockSubscribers.indexOf(cb);
|
|
675
|
+
if (index > -1) {
|
|
676
|
+
blockSubscribers.splice(index, 1);
|
|
677
|
+
}
|
|
678
|
+
};
|
|
679
|
+
}
|
|
680
|
+
async function getTransactionHistory(address) {
|
|
681
|
+
const scriptHash = addressToScriptHash(address);
|
|
682
|
+
const result = await rpc("blockchain.scripthash.get_history", [scriptHash]);
|
|
683
|
+
if (!Array.isArray(result)) {
|
|
684
|
+
console.warn("get_history returned non-array:", result);
|
|
685
|
+
return [];
|
|
686
|
+
}
|
|
687
|
+
return result;
|
|
688
|
+
}
|
|
689
|
+
async function getTransaction(txid) {
|
|
690
|
+
return await rpc("blockchain.transaction.get", [txid, true]);
|
|
691
|
+
}
|
|
692
|
+
async function getBlockHeader(height) {
|
|
693
|
+
return await rpc("blockchain.block.header", [height, height]);
|
|
694
|
+
}
|
|
695
|
+
async function getCurrentBlockHeight() {
|
|
696
|
+
try {
|
|
697
|
+
const header = await rpc("blockchain.headers.subscribe", []);
|
|
698
|
+
return header?.height || 0;
|
|
699
|
+
} catch (err) {
|
|
700
|
+
console.error("Error getting current block height:", err);
|
|
701
|
+
return 0;
|
|
702
|
+
}
|
|
703
|
+
}
|
|
704
|
+
function disconnect() {
|
|
705
|
+
if (ws) {
|
|
706
|
+
intentionalClose = true;
|
|
707
|
+
ws.close();
|
|
708
|
+
ws = null;
|
|
709
|
+
}
|
|
710
|
+
isConnected = false;
|
|
711
|
+
isConnecting = false;
|
|
712
|
+
reconnectAttempts = 0;
|
|
713
|
+
isBlockSubscribed = false;
|
|
714
|
+
Object.values(pending).forEach((req) => {
|
|
715
|
+
if (req.timeoutId) clearTimeout(req.timeoutId);
|
|
716
|
+
});
|
|
717
|
+
Object.keys(pending).forEach((key) => delete pending[Number(key)]);
|
|
718
|
+
connectionCallbacks.forEach((cb) => {
|
|
719
|
+
if (cb.timeoutId) clearTimeout(cb.timeoutId);
|
|
720
|
+
});
|
|
721
|
+
connectionCallbacks.length = 0;
|
|
722
|
+
}
|
|
723
|
+
|
|
724
|
+
// l1/tx.ts
|
|
725
|
+
var import_crypto_js5 = __toESM(require("crypto-js"), 1);
|
|
726
|
+
var import_elliptic2 = __toESM(require("elliptic"), 1);
|
|
727
|
+
|
|
728
|
+
// l1/vesting.ts
|
|
729
|
+
var VESTING_THRESHOLD = 28e4;
|
|
730
|
+
var currentBlockHeight = null;
|
|
731
|
+
var VestingClassifier = class {
|
|
732
|
+
memoryCache = /* @__PURE__ */ new Map();
|
|
733
|
+
dbName = "SphereVestingCacheV5";
|
|
734
|
+
// V5 - new cache with proper null handling
|
|
735
|
+
storeName = "vestingCache";
|
|
736
|
+
db = null;
|
|
737
|
+
/**
|
|
738
|
+
* Initialize IndexedDB for persistent caching
|
|
739
|
+
*/
|
|
740
|
+
async initDB() {
|
|
741
|
+
return new Promise((resolve, reject) => {
|
|
742
|
+
const request = indexedDB.open(this.dbName, 1);
|
|
743
|
+
request.onupgradeneeded = (event) => {
|
|
744
|
+
const db = event.target.result;
|
|
745
|
+
if (!db.objectStoreNames.contains(this.storeName)) {
|
|
746
|
+
db.createObjectStore(this.storeName, { keyPath: "txHash" });
|
|
747
|
+
}
|
|
748
|
+
};
|
|
749
|
+
request.onsuccess = (event) => {
|
|
750
|
+
this.db = event.target.result;
|
|
751
|
+
resolve();
|
|
752
|
+
};
|
|
753
|
+
request.onerror = () => reject(request.error);
|
|
754
|
+
});
|
|
755
|
+
}
|
|
756
|
+
/**
|
|
757
|
+
* Check if transaction is coinbase
|
|
758
|
+
*/
|
|
759
|
+
isCoinbaseTransaction(txData) {
|
|
760
|
+
if (txData.vin && txData.vin.length === 1) {
|
|
761
|
+
const vin = txData.vin[0];
|
|
762
|
+
if (vin.coinbase || !vin.txid && vin.coinbase !== void 0) {
|
|
763
|
+
return true;
|
|
764
|
+
}
|
|
765
|
+
if (vin.txid === "0000000000000000000000000000000000000000000000000000000000000000") {
|
|
766
|
+
return true;
|
|
767
|
+
}
|
|
768
|
+
}
|
|
769
|
+
return false;
|
|
770
|
+
}
|
|
771
|
+
/**
|
|
772
|
+
* Load from IndexedDB cache
|
|
773
|
+
*/
|
|
774
|
+
async loadFromDB(txHash) {
|
|
775
|
+
if (!this.db) return null;
|
|
776
|
+
return new Promise((resolve) => {
|
|
777
|
+
const tx = this.db.transaction(this.storeName, "readonly");
|
|
778
|
+
const store = tx.objectStore(this.storeName);
|
|
779
|
+
const request = store.get(txHash);
|
|
780
|
+
request.onsuccess = () => {
|
|
781
|
+
if (request.result) {
|
|
782
|
+
resolve({
|
|
783
|
+
blockHeight: request.result.blockHeight,
|
|
784
|
+
isCoinbase: request.result.isCoinbase,
|
|
785
|
+
inputTxId: request.result.inputTxId
|
|
786
|
+
});
|
|
787
|
+
} else {
|
|
788
|
+
resolve(null);
|
|
789
|
+
}
|
|
790
|
+
};
|
|
791
|
+
request.onerror = () => resolve(null);
|
|
792
|
+
});
|
|
793
|
+
}
|
|
794
|
+
/**
|
|
795
|
+
* Save to IndexedDB cache
|
|
796
|
+
*/
|
|
797
|
+
async saveToDB(txHash, entry) {
|
|
798
|
+
if (!this.db) return;
|
|
799
|
+
return new Promise((resolve) => {
|
|
800
|
+
const tx = this.db.transaction(this.storeName, "readwrite");
|
|
801
|
+
const store = tx.objectStore(this.storeName);
|
|
802
|
+
store.put({ txHash, ...entry });
|
|
803
|
+
tx.oncomplete = () => resolve();
|
|
804
|
+
tx.onerror = () => resolve();
|
|
805
|
+
});
|
|
806
|
+
}
|
|
807
|
+
/**
|
|
808
|
+
* Trace a transaction to its coinbase origin
|
|
809
|
+
* Alpha blockchain has single-input transactions, making this a linear trace
|
|
810
|
+
*/
|
|
811
|
+
async traceToOrigin(txHash) {
|
|
812
|
+
let currentTxHash = txHash;
|
|
813
|
+
let iterations = 0;
|
|
814
|
+
const MAX_ITERATIONS = 1e4;
|
|
815
|
+
while (iterations < MAX_ITERATIONS) {
|
|
816
|
+
iterations++;
|
|
817
|
+
const cached = this.memoryCache.get(currentTxHash);
|
|
818
|
+
if (cached) {
|
|
819
|
+
if (cached.isCoinbase) {
|
|
820
|
+
if (cached.blockHeight !== null && cached.blockHeight !== void 0) {
|
|
821
|
+
return { coinbaseHeight: cached.blockHeight };
|
|
822
|
+
}
|
|
823
|
+
} else if (cached.inputTxId) {
|
|
824
|
+
currentTxHash = cached.inputTxId;
|
|
825
|
+
continue;
|
|
826
|
+
}
|
|
827
|
+
}
|
|
828
|
+
const dbCached = await this.loadFromDB(currentTxHash);
|
|
829
|
+
if (dbCached) {
|
|
830
|
+
this.memoryCache.set(currentTxHash, dbCached);
|
|
831
|
+
if (dbCached.isCoinbase) {
|
|
832
|
+
if (dbCached.blockHeight !== null && dbCached.blockHeight !== void 0) {
|
|
833
|
+
return { coinbaseHeight: dbCached.blockHeight };
|
|
834
|
+
}
|
|
835
|
+
} else if (dbCached.inputTxId) {
|
|
836
|
+
currentTxHash = dbCached.inputTxId;
|
|
837
|
+
continue;
|
|
838
|
+
}
|
|
839
|
+
}
|
|
840
|
+
const txData = await getTransaction(currentTxHash);
|
|
841
|
+
if (!txData || !txData.txid) {
|
|
842
|
+
return { coinbaseHeight: null, error: `Failed to fetch tx ${currentTxHash}` };
|
|
843
|
+
}
|
|
844
|
+
const isCoinbase = this.isCoinbaseTransaction(txData);
|
|
845
|
+
let blockHeight = null;
|
|
846
|
+
if (txData.confirmations && currentBlockHeight !== null && currentBlockHeight !== void 0) {
|
|
847
|
+
blockHeight = currentBlockHeight - txData.confirmations + 1;
|
|
848
|
+
}
|
|
849
|
+
let inputTxId = null;
|
|
850
|
+
if (!isCoinbase && txData.vin && txData.vin.length > 0 && txData.vin[0].txid) {
|
|
851
|
+
inputTxId = txData.vin[0].txid;
|
|
852
|
+
}
|
|
853
|
+
const cacheEntry = {
|
|
854
|
+
blockHeight,
|
|
855
|
+
// Can be null if confirmations not available
|
|
856
|
+
isCoinbase,
|
|
857
|
+
inputTxId
|
|
858
|
+
};
|
|
859
|
+
this.memoryCache.set(currentTxHash, cacheEntry);
|
|
860
|
+
await this.saveToDB(currentTxHash, cacheEntry);
|
|
861
|
+
if (isCoinbase) {
|
|
862
|
+
return { coinbaseHeight: blockHeight };
|
|
863
|
+
}
|
|
864
|
+
if (!inputTxId) {
|
|
865
|
+
return { coinbaseHeight: null, error: "Could not find input transaction" };
|
|
866
|
+
}
|
|
867
|
+
currentTxHash = inputTxId;
|
|
868
|
+
}
|
|
869
|
+
return { coinbaseHeight: null, error: "Max iterations exceeded" };
|
|
870
|
+
}
|
|
871
|
+
/**
|
|
872
|
+
* Classify a single UTXO
|
|
873
|
+
*/
|
|
874
|
+
async classifyUtxo(utxo) {
|
|
875
|
+
const txHash = utxo.tx_hash || utxo.txid;
|
|
876
|
+
if (!txHash) {
|
|
877
|
+
return { isVested: false, coinbaseHeight: null, error: "No transaction hash" };
|
|
878
|
+
}
|
|
879
|
+
try {
|
|
880
|
+
const result = await this.traceToOrigin(txHash);
|
|
881
|
+
if (result.error || result.coinbaseHeight === null) {
|
|
882
|
+
return { isVested: false, coinbaseHeight: null, error: result.error || "Could not trace to origin" };
|
|
883
|
+
}
|
|
884
|
+
return {
|
|
885
|
+
isVested: result.coinbaseHeight <= VESTING_THRESHOLD,
|
|
886
|
+
coinbaseHeight: result.coinbaseHeight
|
|
887
|
+
};
|
|
888
|
+
} catch (err) {
|
|
889
|
+
return {
|
|
890
|
+
isVested: false,
|
|
891
|
+
coinbaseHeight: null,
|
|
892
|
+
error: err instanceof Error ? err.message : String(err)
|
|
893
|
+
};
|
|
894
|
+
}
|
|
895
|
+
}
|
|
896
|
+
/**
|
|
897
|
+
* Classify multiple UTXOs with progress callback
|
|
898
|
+
*/
|
|
899
|
+
async classifyUtxos(utxos, onProgress) {
|
|
900
|
+
currentBlockHeight = await getCurrentBlockHeight();
|
|
901
|
+
this.memoryCache.clear();
|
|
902
|
+
const vested = [];
|
|
903
|
+
const unvested = [];
|
|
904
|
+
const errors = [];
|
|
905
|
+
for (let i = 0; i < utxos.length; i++) {
|
|
906
|
+
const utxo = utxos[i];
|
|
907
|
+
const result = await this.classifyUtxo(utxo);
|
|
908
|
+
if (result.error) {
|
|
909
|
+
errors.push({ utxo, error: result.error });
|
|
910
|
+
unvested.push({
|
|
911
|
+
...utxo,
|
|
912
|
+
vestingStatus: "error",
|
|
913
|
+
coinbaseHeight: null
|
|
914
|
+
});
|
|
915
|
+
} else if (result.isVested) {
|
|
916
|
+
vested.push({
|
|
917
|
+
...utxo,
|
|
918
|
+
vestingStatus: "vested",
|
|
919
|
+
coinbaseHeight: result.coinbaseHeight
|
|
920
|
+
});
|
|
921
|
+
} else {
|
|
922
|
+
unvested.push({
|
|
923
|
+
...utxo,
|
|
924
|
+
vestingStatus: "unvested",
|
|
925
|
+
coinbaseHeight: result.coinbaseHeight
|
|
926
|
+
});
|
|
927
|
+
}
|
|
928
|
+
if (onProgress) {
|
|
929
|
+
onProgress(i + 1, utxos.length);
|
|
930
|
+
}
|
|
931
|
+
if (i % 5 === 0) {
|
|
932
|
+
await new Promise((resolve) => setTimeout(resolve, 0));
|
|
933
|
+
}
|
|
934
|
+
}
|
|
935
|
+
return { vested, unvested, errors };
|
|
936
|
+
}
|
|
937
|
+
/**
|
|
938
|
+
* Clear all caches
|
|
939
|
+
*/
|
|
940
|
+
clearCaches() {
|
|
941
|
+
this.memoryCache.clear();
|
|
942
|
+
if (this.db) {
|
|
943
|
+
const tx = this.db.transaction(this.storeName, "readwrite");
|
|
944
|
+
tx.objectStore(this.storeName).clear();
|
|
945
|
+
}
|
|
946
|
+
}
|
|
947
|
+
};
|
|
948
|
+
var vestingClassifier = new VestingClassifier();
|
|
949
|
+
|
|
950
|
+
// l1/vestingState.ts
|
|
951
|
+
var VestingStateManager = class {
|
|
952
|
+
currentMode = "all";
|
|
953
|
+
addressCache = /* @__PURE__ */ new Map();
|
|
954
|
+
classificationInProgress = false;
|
|
955
|
+
/**
|
|
956
|
+
* Set the current vesting mode
|
|
957
|
+
*/
|
|
958
|
+
setMode(mode) {
|
|
959
|
+
if (!["all", "vested", "unvested"].includes(mode)) {
|
|
960
|
+
throw new Error(`Invalid vesting mode: ${mode}`);
|
|
961
|
+
}
|
|
962
|
+
this.currentMode = mode;
|
|
963
|
+
}
|
|
964
|
+
getMode() {
|
|
965
|
+
return this.currentMode;
|
|
966
|
+
}
|
|
967
|
+
/**
|
|
968
|
+
* Classify all UTXOs for an address
|
|
969
|
+
*/
|
|
970
|
+
async classifyAddressUtxos(address, utxos, onProgress) {
|
|
971
|
+
if (this.classificationInProgress) return;
|
|
972
|
+
this.classificationInProgress = true;
|
|
973
|
+
try {
|
|
974
|
+
await vestingClassifier.initDB();
|
|
975
|
+
const result = await vestingClassifier.classifyUtxos(utxos, onProgress);
|
|
976
|
+
const vestedBalance = result.vested.reduce(
|
|
977
|
+
(sum, utxo) => sum + BigInt(utxo.value),
|
|
978
|
+
0n
|
|
979
|
+
);
|
|
980
|
+
const unvestedBalance = result.unvested.reduce(
|
|
981
|
+
(sum, utxo) => sum + BigInt(utxo.value),
|
|
982
|
+
0n
|
|
983
|
+
);
|
|
984
|
+
this.addressCache.set(address, {
|
|
985
|
+
classifiedUtxos: {
|
|
986
|
+
vested: result.vested,
|
|
987
|
+
unvested: result.unvested,
|
|
988
|
+
all: [...result.vested, ...result.unvested]
|
|
989
|
+
},
|
|
990
|
+
vestingBalances: {
|
|
991
|
+
vested: vestedBalance,
|
|
992
|
+
unvested: unvestedBalance,
|
|
993
|
+
all: vestedBalance + unvestedBalance
|
|
994
|
+
}
|
|
995
|
+
});
|
|
996
|
+
if (result.errors.length > 0) {
|
|
997
|
+
console.warn(`Vesting classification errors: ${result.errors.length}`);
|
|
998
|
+
result.errors.slice(0, 5).forEach((err) => {
|
|
999
|
+
const txHash = err.utxo.tx_hash || err.utxo.txid;
|
|
1000
|
+
console.warn(` ${txHash}: ${err.error}`);
|
|
1001
|
+
});
|
|
1002
|
+
}
|
|
1003
|
+
} finally {
|
|
1004
|
+
this.classificationInProgress = false;
|
|
1005
|
+
}
|
|
1006
|
+
}
|
|
1007
|
+
/**
|
|
1008
|
+
* Get filtered UTXOs based on current vesting mode
|
|
1009
|
+
*/
|
|
1010
|
+
getFilteredUtxos(address) {
|
|
1011
|
+
const cache = this.addressCache.get(address);
|
|
1012
|
+
if (!cache) return [];
|
|
1013
|
+
switch (this.currentMode) {
|
|
1014
|
+
case "vested":
|
|
1015
|
+
return cache.classifiedUtxos.vested;
|
|
1016
|
+
case "unvested":
|
|
1017
|
+
return cache.classifiedUtxos.unvested;
|
|
1018
|
+
default:
|
|
1019
|
+
return cache.classifiedUtxos.all;
|
|
1020
|
+
}
|
|
1021
|
+
}
|
|
1022
|
+
/**
|
|
1023
|
+
* Get all UTXOs regardless of vesting mode (for transactions)
|
|
1024
|
+
*/
|
|
1025
|
+
getAllUtxos(address) {
|
|
1026
|
+
const cache = this.addressCache.get(address);
|
|
1027
|
+
if (!cache) return [];
|
|
1028
|
+
return cache.classifiedUtxos.all;
|
|
1029
|
+
}
|
|
1030
|
+
/**
|
|
1031
|
+
* Get balance for current vesting mode (in satoshis)
|
|
1032
|
+
*/
|
|
1033
|
+
getBalance(address) {
|
|
1034
|
+
const cache = this.addressCache.get(address);
|
|
1035
|
+
if (!cache) return 0n;
|
|
1036
|
+
return cache.vestingBalances[this.currentMode];
|
|
1037
|
+
}
|
|
1038
|
+
/**
|
|
1039
|
+
* Get all balances for display
|
|
1040
|
+
*/
|
|
1041
|
+
getAllBalances(address) {
|
|
1042
|
+
const cache = this.addressCache.get(address);
|
|
1043
|
+
if (!cache) {
|
|
1044
|
+
return { vested: 0n, unvested: 0n, all: 0n };
|
|
1045
|
+
}
|
|
1046
|
+
return cache.vestingBalances;
|
|
1047
|
+
}
|
|
1048
|
+
/**
|
|
1049
|
+
* Check if address has been classified
|
|
1050
|
+
*/
|
|
1051
|
+
hasClassifiedData(address) {
|
|
1052
|
+
return this.addressCache.has(address);
|
|
1053
|
+
}
|
|
1054
|
+
/**
|
|
1055
|
+
* Check if classification is in progress
|
|
1056
|
+
*/
|
|
1057
|
+
isClassifying() {
|
|
1058
|
+
return this.classificationInProgress;
|
|
1059
|
+
}
|
|
1060
|
+
/**
|
|
1061
|
+
* Clear cache for an address
|
|
1062
|
+
*/
|
|
1063
|
+
clearAddressCache(address) {
|
|
1064
|
+
this.addressCache.delete(address);
|
|
1065
|
+
}
|
|
1066
|
+
/**
|
|
1067
|
+
* Clear all caches
|
|
1068
|
+
*/
|
|
1069
|
+
clearAllCaches() {
|
|
1070
|
+
this.addressCache.clear();
|
|
1071
|
+
vestingClassifier.clearCaches();
|
|
1072
|
+
}
|
|
1073
|
+
};
|
|
1074
|
+
var vestingState = new VestingStateManager();
|
|
1075
|
+
|
|
1076
|
+
// l1/addressHelpers.ts
|
|
1077
|
+
var WalletAddressHelper = class {
|
|
1078
|
+
/**
|
|
1079
|
+
* Find address by BIP32 derivation path
|
|
1080
|
+
* @param wallet - The wallet to search
|
|
1081
|
+
* @param path - Full BIP32 path like "m/84'/1'/0'/0/5"
|
|
1082
|
+
* @returns The address if found, undefined otherwise
|
|
1083
|
+
*/
|
|
1084
|
+
static findByPath(wallet, path) {
|
|
1085
|
+
return wallet.addresses.find((a) => a.path === path);
|
|
1086
|
+
}
|
|
1087
|
+
/**
|
|
1088
|
+
* Get the default address (first external/non-change address)
|
|
1089
|
+
* This replaces `wallet.addresses[0]` pattern for safer access
|
|
1090
|
+
*
|
|
1091
|
+
* @param wallet - The wallet
|
|
1092
|
+
* @returns First non-change address, or first address if all are change
|
|
1093
|
+
*/
|
|
1094
|
+
static getDefault(wallet) {
|
|
1095
|
+
return wallet.addresses.find((a) => !a.isChange) ?? wallet.addresses[0];
|
|
1096
|
+
}
|
|
1097
|
+
/**
|
|
1098
|
+
* Get the default address, or undefined if wallet has no addresses
|
|
1099
|
+
* Safe version that doesn't throw on empty wallet
|
|
1100
|
+
*/
|
|
1101
|
+
static getDefaultOrNull(wallet) {
|
|
1102
|
+
if (!wallet.addresses || wallet.addresses.length === 0) {
|
|
1103
|
+
return void 0;
|
|
1104
|
+
}
|
|
1105
|
+
return wallet.addresses.find((a) => !a.isChange) ?? wallet.addresses[0];
|
|
1106
|
+
}
|
|
1107
|
+
/**
|
|
1108
|
+
* Add new address to wallet (immutable operation)
|
|
1109
|
+
*
|
|
1110
|
+
* THROWS if address with same path but different address string already exists.
|
|
1111
|
+
* This indicates a serious derivation or data corruption issue.
|
|
1112
|
+
*
|
|
1113
|
+
* If the same path+address already exists, returns wallet unchanged (idempotent).
|
|
1114
|
+
*
|
|
1115
|
+
* @param wallet - The wallet to add to
|
|
1116
|
+
* @param newAddress - The address to add
|
|
1117
|
+
* @returns New wallet object with address added
|
|
1118
|
+
* @throws Error if path exists with different address (corruption indicator)
|
|
1119
|
+
*/
|
|
1120
|
+
static add(wallet, newAddress) {
|
|
1121
|
+
if (!newAddress.path) {
|
|
1122
|
+
throw new Error("Cannot add address without a path");
|
|
1123
|
+
}
|
|
1124
|
+
const existing = this.findByPath(wallet, newAddress.path);
|
|
1125
|
+
if (existing) {
|
|
1126
|
+
if (existing.address !== newAddress.address) {
|
|
1127
|
+
throw new Error(
|
|
1128
|
+
`CRITICAL: Attempted to overwrite address for path ${newAddress.path}
|
|
1129
|
+
Existing: ${existing.address}
|
|
1130
|
+
New: ${newAddress.address}
|
|
1131
|
+
This indicates master key corruption or derivation logic error.`
|
|
1132
|
+
);
|
|
1133
|
+
}
|
|
1134
|
+
return wallet;
|
|
1135
|
+
}
|
|
1136
|
+
return {
|
|
1137
|
+
...wallet,
|
|
1138
|
+
addresses: [...wallet.addresses, newAddress]
|
|
1139
|
+
};
|
|
1140
|
+
}
|
|
1141
|
+
/**
|
|
1142
|
+
* Remove address by path (immutable operation)
|
|
1143
|
+
* @param wallet - The wallet to modify
|
|
1144
|
+
* @param path - The path of the address to remove
|
|
1145
|
+
* @returns New wallet object with address removed
|
|
1146
|
+
*/
|
|
1147
|
+
static removeByPath(wallet, path) {
|
|
1148
|
+
return {
|
|
1149
|
+
...wallet,
|
|
1150
|
+
addresses: wallet.addresses.filter((a) => a.path !== path)
|
|
1151
|
+
};
|
|
1152
|
+
}
|
|
1153
|
+
/**
|
|
1154
|
+
* Get all external (non-change) addresses
|
|
1155
|
+
* @param wallet - The wallet
|
|
1156
|
+
* @returns Array of external addresses
|
|
1157
|
+
*/
|
|
1158
|
+
static getExternal(wallet) {
|
|
1159
|
+
return wallet.addresses.filter((a) => !a.isChange);
|
|
1160
|
+
}
|
|
1161
|
+
/**
|
|
1162
|
+
* Get all change addresses
|
|
1163
|
+
* @param wallet - The wallet
|
|
1164
|
+
* @returns Array of change addresses
|
|
1165
|
+
*/
|
|
1166
|
+
static getChange(wallet) {
|
|
1167
|
+
return wallet.addresses.filter((a) => a.isChange);
|
|
1168
|
+
}
|
|
1169
|
+
/**
|
|
1170
|
+
* Check if wallet has an address with the given path
|
|
1171
|
+
* @param wallet - The wallet to check
|
|
1172
|
+
* @param path - The path to look for
|
|
1173
|
+
* @returns true if path exists
|
|
1174
|
+
*/
|
|
1175
|
+
static hasPath(wallet, path) {
|
|
1176
|
+
return wallet.addresses.some((a) => a.path === path);
|
|
1177
|
+
}
|
|
1178
|
+
/**
|
|
1179
|
+
* Validate wallet address array integrity
|
|
1180
|
+
* Checks for duplicate paths which indicate data corruption
|
|
1181
|
+
*
|
|
1182
|
+
* @param wallet - The wallet to validate
|
|
1183
|
+
* @throws Error if duplicate paths found
|
|
1184
|
+
*/
|
|
1185
|
+
static validate(wallet) {
|
|
1186
|
+
const paths = wallet.addresses.map((a) => a.path).filter(Boolean);
|
|
1187
|
+
const uniquePaths = new Set(paths);
|
|
1188
|
+
if (paths.length !== uniquePaths.size) {
|
|
1189
|
+
const duplicates = paths.filter((p, i) => paths.indexOf(p) !== i);
|
|
1190
|
+
throw new Error(
|
|
1191
|
+
`CRITICAL: Wallet has duplicate paths: ${duplicates.join(", ")}
|
|
1192
|
+
This indicates data corruption. Please restore from backup.`
|
|
1193
|
+
);
|
|
1194
|
+
}
|
|
1195
|
+
}
|
|
1196
|
+
/**
|
|
1197
|
+
* Sort addresses with external first, then change, each sorted by index
|
|
1198
|
+
* Useful for display purposes
|
|
1199
|
+
*
|
|
1200
|
+
* @param wallet - The wallet
|
|
1201
|
+
* @returns New wallet with sorted addresses
|
|
1202
|
+
*/
|
|
1203
|
+
static sortAddresses(wallet) {
|
|
1204
|
+
const sorted = [...wallet.addresses].sort((a, b) => {
|
|
1205
|
+
const aIsChange = a.isChange ? 1 : 0;
|
|
1206
|
+
const bIsChange = b.isChange ? 1 : 0;
|
|
1207
|
+
if (aIsChange !== bIsChange) return aIsChange - bIsChange;
|
|
1208
|
+
return a.index - b.index;
|
|
1209
|
+
});
|
|
1210
|
+
return {
|
|
1211
|
+
...wallet,
|
|
1212
|
+
addresses: sorted
|
|
1213
|
+
};
|
|
1214
|
+
}
|
|
1215
|
+
};
|
|
1216
|
+
|
|
1217
|
+
// l1/tx.ts
|
|
1218
|
+
var ec2 = new import_elliptic2.default.ec("secp256k1");
|
|
1219
|
+
var FEE = 1e4;
|
|
1220
|
+
var DUST = 546;
|
|
1221
|
+
var SAT = 1e8;
|
|
1222
|
+
function createScriptPubKey(address) {
|
|
1223
|
+
if (!address || typeof address !== "string") {
|
|
1224
|
+
throw new Error("Invalid address: must be a string");
|
|
1225
|
+
}
|
|
1226
|
+
const decoded = decodeBech32(address);
|
|
1227
|
+
if (!decoded) {
|
|
1228
|
+
throw new Error("Invalid bech32 address: " + address);
|
|
1229
|
+
}
|
|
1230
|
+
const dataHex = Array.from(decoded.data).map((byte) => byte.toString(16).padStart(2, "0")).join("");
|
|
1231
|
+
return "0014" + dataHex;
|
|
1232
|
+
}
|
|
1233
|
+
function createSignatureHash(txPlan, publicKey) {
|
|
1234
|
+
let preimage = "";
|
|
1235
|
+
preimage += "02000000";
|
|
1236
|
+
const txidBytes = txPlan.input.tx_hash.match(/../g).reverse().join("");
|
|
1237
|
+
const voutBytes = ("00000000" + txPlan.input.tx_pos.toString(16)).slice(-8).match(/../g).reverse().join("");
|
|
1238
|
+
const prevouts = txidBytes + voutBytes;
|
|
1239
|
+
const hashPrevouts = import_crypto_js5.default.SHA256(import_crypto_js5.default.SHA256(import_crypto_js5.default.enc.Hex.parse(prevouts))).toString();
|
|
1240
|
+
preimage += hashPrevouts;
|
|
1241
|
+
const sequence = "feffffff";
|
|
1242
|
+
const hashSequence = import_crypto_js5.default.SHA256(import_crypto_js5.default.SHA256(import_crypto_js5.default.enc.Hex.parse(sequence))).toString();
|
|
1243
|
+
preimage += hashSequence;
|
|
1244
|
+
preimage += txPlan.input.tx_hash.match(/../g).reverse().join("");
|
|
1245
|
+
preimage += ("00000000" + txPlan.input.tx_pos.toString(16)).slice(-8).match(/../g).reverse().join("");
|
|
1246
|
+
const pubKeyHash = import_crypto_js5.default.RIPEMD160(import_crypto_js5.default.SHA256(import_crypto_js5.default.enc.Hex.parse(publicKey))).toString();
|
|
1247
|
+
const scriptCode = "1976a914" + pubKeyHash + "88ac";
|
|
1248
|
+
preimage += scriptCode;
|
|
1249
|
+
const amountHex = txPlan.input.value.toString(16).padStart(16, "0");
|
|
1250
|
+
preimage += amountHex.match(/../g).reverse().join("");
|
|
1251
|
+
preimage += sequence;
|
|
1252
|
+
let outputs = "";
|
|
1253
|
+
for (const output of txPlan.outputs) {
|
|
1254
|
+
const outAmountHex = output.value.toString(16).padStart(16, "0");
|
|
1255
|
+
outputs += outAmountHex.match(/../g).reverse().join("");
|
|
1256
|
+
const scriptPubKey = createScriptPubKey(output.address);
|
|
1257
|
+
const scriptLength = (scriptPubKey.length / 2).toString(16).padStart(2, "0");
|
|
1258
|
+
outputs += scriptLength;
|
|
1259
|
+
outputs += scriptPubKey;
|
|
1260
|
+
}
|
|
1261
|
+
const hashOutputs = import_crypto_js5.default.SHA256(import_crypto_js5.default.SHA256(import_crypto_js5.default.enc.Hex.parse(outputs))).toString();
|
|
1262
|
+
preimage += hashOutputs;
|
|
1263
|
+
preimage += "00000000";
|
|
1264
|
+
preimage += "01000000";
|
|
1265
|
+
const hash1 = import_crypto_js5.default.SHA256(import_crypto_js5.default.enc.Hex.parse(preimage));
|
|
1266
|
+
const hash2 = import_crypto_js5.default.SHA256(hash1);
|
|
1267
|
+
return hash2.toString();
|
|
1268
|
+
}
|
|
1269
|
+
function createWitnessData(txPlan, keyPair, publicKey) {
|
|
1270
|
+
const sigHash = createSignatureHash(txPlan, publicKey);
|
|
1271
|
+
const signature = keyPair.sign(sigHash);
|
|
1272
|
+
const halfOrder = ec2.curve.n.shrn(1);
|
|
1273
|
+
if (signature.s.cmp(halfOrder) > 0) {
|
|
1274
|
+
signature.s = ec2.curve.n.sub(signature.s);
|
|
1275
|
+
}
|
|
1276
|
+
const derSig = signature.toDER("hex") + "01";
|
|
1277
|
+
let witness = "";
|
|
1278
|
+
witness += "02";
|
|
1279
|
+
const sigLen = (derSig.length / 2).toString(16).padStart(2, "0");
|
|
1280
|
+
witness += sigLen;
|
|
1281
|
+
witness += derSig;
|
|
1282
|
+
const pubKeyLen = (publicKey.length / 2).toString(16).padStart(2, "0");
|
|
1283
|
+
witness += pubKeyLen;
|
|
1284
|
+
witness += publicKey;
|
|
1285
|
+
return witness;
|
|
1286
|
+
}
|
|
1287
|
+
function buildSegWitTransaction(txPlan, keyPair, publicKey) {
|
|
1288
|
+
let txHex = "";
|
|
1289
|
+
txHex += "02000000";
|
|
1290
|
+
txHex += "00";
|
|
1291
|
+
txHex += "01";
|
|
1292
|
+
txHex += "01";
|
|
1293
|
+
const prevTxHash = txPlan.input.tx_hash;
|
|
1294
|
+
const reversedHash = prevTxHash.match(/../g).reverse().join("");
|
|
1295
|
+
txHex += reversedHash;
|
|
1296
|
+
const vout = txPlan.input.tx_pos;
|
|
1297
|
+
txHex += ("00000000" + vout.toString(16)).slice(-8).match(/../g).reverse().join("");
|
|
1298
|
+
txHex += "00";
|
|
1299
|
+
txHex += "feffffff";
|
|
1300
|
+
const outputCount = txPlan.outputs.length;
|
|
1301
|
+
txHex += ("0" + outputCount.toString(16)).slice(-2);
|
|
1302
|
+
for (const output of txPlan.outputs) {
|
|
1303
|
+
const amountHex = output.value.toString(16).padStart(16, "0");
|
|
1304
|
+
txHex += amountHex.match(/../g).reverse().join("");
|
|
1305
|
+
const scriptPubKey = createScriptPubKey(output.address);
|
|
1306
|
+
const scriptLength = (scriptPubKey.length / 2).toString(16).padStart(2, "0");
|
|
1307
|
+
txHex += scriptLength;
|
|
1308
|
+
txHex += scriptPubKey;
|
|
1309
|
+
}
|
|
1310
|
+
const witnessData = createWitnessData(txPlan, keyPair, publicKey);
|
|
1311
|
+
txHex += witnessData;
|
|
1312
|
+
txHex += "00000000";
|
|
1313
|
+
let txForId = "";
|
|
1314
|
+
txForId += "02000000";
|
|
1315
|
+
txForId += "01";
|
|
1316
|
+
const inputTxidBytes = txPlan.input.tx_hash.match(/../g).reverse().join("");
|
|
1317
|
+
txForId += inputTxidBytes;
|
|
1318
|
+
txForId += ("00000000" + txPlan.input.tx_pos.toString(16)).slice(-8).match(/../g).reverse().join("");
|
|
1319
|
+
txForId += "00";
|
|
1320
|
+
txForId += "feffffff";
|
|
1321
|
+
txForId += ("0" + txPlan.outputs.length.toString(16)).slice(-2);
|
|
1322
|
+
for (const output of txPlan.outputs) {
|
|
1323
|
+
const amountHex = ("0000000000000000" + output.value.toString(16)).slice(-16);
|
|
1324
|
+
const amountLittleEndian = amountHex.match(/../g).reverse().join("");
|
|
1325
|
+
txForId += amountLittleEndian;
|
|
1326
|
+
const scriptPubKey = createScriptPubKey(output.address);
|
|
1327
|
+
const scriptLength = ("0" + (scriptPubKey.length / 2).toString(16)).slice(-2);
|
|
1328
|
+
txForId += scriptLength;
|
|
1329
|
+
txForId += scriptPubKey;
|
|
1330
|
+
}
|
|
1331
|
+
txForId += "00000000";
|
|
1332
|
+
const hash1 = import_crypto_js5.default.SHA256(import_crypto_js5.default.enc.Hex.parse(txForId));
|
|
1333
|
+
const hash2 = import_crypto_js5.default.SHA256(hash1);
|
|
1334
|
+
const txid = hash2.toString().match(/../g).reverse().join("");
|
|
1335
|
+
return {
|
|
1336
|
+
hex: txHex,
|
|
1337
|
+
txid
|
|
1338
|
+
};
|
|
1339
|
+
}
|
|
1340
|
+
function createAndSignTransaction(wallet, txPlan) {
|
|
1341
|
+
const fromAddress = txPlan.input.address;
|
|
1342
|
+
const addressEntry = wallet.addresses.find((a) => a.address === fromAddress);
|
|
1343
|
+
let privateKeyHex;
|
|
1344
|
+
if (addressEntry?.privateKey) {
|
|
1345
|
+
privateKeyHex = addressEntry.privateKey;
|
|
1346
|
+
} else if (wallet.childPrivateKey) {
|
|
1347
|
+
privateKeyHex = wallet.childPrivateKey;
|
|
1348
|
+
} else {
|
|
1349
|
+
privateKeyHex = wallet.masterPrivateKey;
|
|
1350
|
+
}
|
|
1351
|
+
if (!privateKeyHex) {
|
|
1352
|
+
throw new Error("No private key available for address: " + fromAddress);
|
|
1353
|
+
}
|
|
1354
|
+
const keyPair = ec2.keyFromPrivate(privateKeyHex, "hex");
|
|
1355
|
+
const publicKey = keyPair.getPublic(true, "hex");
|
|
1356
|
+
const txPlanForBuild = {
|
|
1357
|
+
input: {
|
|
1358
|
+
tx_hash: txPlan.input.txid,
|
|
1359
|
+
tx_pos: txPlan.input.vout,
|
|
1360
|
+
value: txPlan.input.value
|
|
1361
|
+
},
|
|
1362
|
+
outputs: txPlan.outputs
|
|
1363
|
+
};
|
|
1364
|
+
const tx = buildSegWitTransaction(txPlanForBuild, keyPair, publicKey);
|
|
1365
|
+
return {
|
|
1366
|
+
raw: tx.hex,
|
|
1367
|
+
txid: tx.txid
|
|
1368
|
+
};
|
|
1369
|
+
}
|
|
1370
|
+
function collectUtxosForAmount(utxoList, amountSats, recipientAddress, senderAddress) {
|
|
1371
|
+
const totalAvailable = utxoList.reduce((sum, u) => sum + u.value, 0);
|
|
1372
|
+
if (totalAvailable < amountSats + FEE) {
|
|
1373
|
+
return {
|
|
1374
|
+
success: false,
|
|
1375
|
+
transactions: [],
|
|
1376
|
+
error: `Insufficient funds. Available: ${totalAvailable / SAT} ALPHA, Required: ${(amountSats + FEE) / SAT} ALPHA (including fee)`
|
|
1377
|
+
};
|
|
1378
|
+
}
|
|
1379
|
+
const sortedByValue = [...utxoList].sort((a, b) => a.value - b.value);
|
|
1380
|
+
const sufficientUtxo = sortedByValue.find((u) => u.value >= amountSats + FEE);
|
|
1381
|
+
if (sufficientUtxo) {
|
|
1382
|
+
const changeAmount = sufficientUtxo.value - amountSats - FEE;
|
|
1383
|
+
const tx = {
|
|
1384
|
+
input: {
|
|
1385
|
+
txid: sufficientUtxo.txid ?? sufficientUtxo.tx_hash ?? "",
|
|
1386
|
+
vout: sufficientUtxo.vout ?? sufficientUtxo.tx_pos ?? 0,
|
|
1387
|
+
value: sufficientUtxo.value,
|
|
1388
|
+
address: sufficientUtxo.address ?? senderAddress
|
|
1389
|
+
},
|
|
1390
|
+
outputs: [{ address: recipientAddress, value: amountSats }],
|
|
1391
|
+
fee: FEE,
|
|
1392
|
+
changeAmount,
|
|
1393
|
+
changeAddress: senderAddress
|
|
1394
|
+
};
|
|
1395
|
+
if (changeAmount > DUST) {
|
|
1396
|
+
tx.outputs.push({ value: changeAmount, address: senderAddress });
|
|
1397
|
+
}
|
|
1398
|
+
return {
|
|
1399
|
+
success: true,
|
|
1400
|
+
transactions: [tx]
|
|
1401
|
+
};
|
|
1402
|
+
}
|
|
1403
|
+
const sortedDescending = [...utxoList].sort((a, b) => b.value - a.value);
|
|
1404
|
+
const transactions = [];
|
|
1405
|
+
let remainingAmount = amountSats;
|
|
1406
|
+
for (const utxo of sortedDescending) {
|
|
1407
|
+
if (remainingAmount <= 0) break;
|
|
1408
|
+
const utxoValue = utxo.value;
|
|
1409
|
+
let txAmount = 0;
|
|
1410
|
+
let changeAmount = 0;
|
|
1411
|
+
if (utxoValue >= remainingAmount + FEE) {
|
|
1412
|
+
txAmount = remainingAmount;
|
|
1413
|
+
changeAmount = utxoValue - remainingAmount - FEE;
|
|
1414
|
+
remainingAmount = 0;
|
|
1415
|
+
} else {
|
|
1416
|
+
txAmount = utxoValue - FEE;
|
|
1417
|
+
if (txAmount <= 0) continue;
|
|
1418
|
+
remainingAmount -= txAmount;
|
|
1419
|
+
}
|
|
1420
|
+
const tx = {
|
|
1421
|
+
input: {
|
|
1422
|
+
txid: utxo.txid ?? utxo.tx_hash ?? "",
|
|
1423
|
+
vout: utxo.vout ?? utxo.tx_pos ?? 0,
|
|
1424
|
+
value: utxo.value,
|
|
1425
|
+
address: utxo.address ?? senderAddress
|
|
1426
|
+
},
|
|
1427
|
+
outputs: [{ address: recipientAddress, value: txAmount }],
|
|
1428
|
+
fee: FEE,
|
|
1429
|
+
changeAmount,
|
|
1430
|
+
changeAddress: senderAddress
|
|
1431
|
+
};
|
|
1432
|
+
if (changeAmount > DUST) {
|
|
1433
|
+
tx.outputs.push({ value: changeAmount, address: senderAddress });
|
|
1434
|
+
}
|
|
1435
|
+
transactions.push(tx);
|
|
1436
|
+
}
|
|
1437
|
+
if (remainingAmount > 0) {
|
|
1438
|
+
return {
|
|
1439
|
+
success: false,
|
|
1440
|
+
transactions: [],
|
|
1441
|
+
error: `Unable to collect enough UTXOs. Short by ${remainingAmount / SAT} ALPHA after fees.`
|
|
1442
|
+
};
|
|
1443
|
+
}
|
|
1444
|
+
return {
|
|
1445
|
+
success: true,
|
|
1446
|
+
transactions
|
|
1447
|
+
};
|
|
1448
|
+
}
|
|
1449
|
+
async function createTransactionPlan(wallet, toAddress, amountAlpha, fromAddress) {
|
|
1450
|
+
if (!decodeBech32(toAddress)) {
|
|
1451
|
+
throw new Error("Invalid recipient address");
|
|
1452
|
+
}
|
|
1453
|
+
const defaultAddr = WalletAddressHelper.getDefault(wallet);
|
|
1454
|
+
const senderAddress = fromAddress || defaultAddr.address;
|
|
1455
|
+
const amountSats = Math.floor(amountAlpha * SAT);
|
|
1456
|
+
let utxos;
|
|
1457
|
+
const currentMode = vestingState.getMode();
|
|
1458
|
+
if (vestingState.hasClassifiedData(senderAddress)) {
|
|
1459
|
+
utxos = vestingState.getFilteredUtxos(senderAddress);
|
|
1460
|
+
console.log(`Using ${utxos.length} ${currentMode} UTXOs`);
|
|
1461
|
+
} else {
|
|
1462
|
+
utxos = await getUtxo(senderAddress);
|
|
1463
|
+
console.log(`Using ${utxos.length} UTXOs (vesting not classified yet)`);
|
|
1464
|
+
}
|
|
1465
|
+
if (!Array.isArray(utxos) || utxos.length === 0) {
|
|
1466
|
+
const modeText = currentMode !== "all" ? ` (${currentMode} coins)` : "";
|
|
1467
|
+
throw new Error(`No UTXOs available${modeText} for address: ` + senderAddress);
|
|
1468
|
+
}
|
|
1469
|
+
return collectUtxosForAmount(utxos, amountSats, toAddress, senderAddress);
|
|
1470
|
+
}
|
|
1471
|
+
async function sendAlpha(wallet, toAddress, amountAlpha, fromAddress) {
|
|
1472
|
+
const plan = await createTransactionPlan(wallet, toAddress, amountAlpha, fromAddress);
|
|
1473
|
+
if (!plan.success) {
|
|
1474
|
+
throw new Error(plan.error || "Transaction planning failed");
|
|
1475
|
+
}
|
|
1476
|
+
const results = [];
|
|
1477
|
+
for (const tx of plan.transactions) {
|
|
1478
|
+
const signed = createAndSignTransaction(wallet, tx);
|
|
1479
|
+
const result = await broadcast(signed.raw);
|
|
1480
|
+
results.push({
|
|
1481
|
+
txid: signed.txid,
|
|
1482
|
+
raw: signed.raw,
|
|
1483
|
+
broadcastResult: result
|
|
1484
|
+
});
|
|
1485
|
+
}
|
|
1486
|
+
return results;
|
|
1487
|
+
}
|
|
1488
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
1489
|
+
0 && (module.exports = {
|
|
1490
|
+
CHARSET,
|
|
1491
|
+
VESTING_THRESHOLD,
|
|
1492
|
+
WalletAddressHelper,
|
|
1493
|
+
addressToScriptHash,
|
|
1494
|
+
broadcast,
|
|
1495
|
+
buildSegWitTransaction,
|
|
1496
|
+
collectUtxosForAmount,
|
|
1497
|
+
computeHash160,
|
|
1498
|
+
connect,
|
|
1499
|
+
convertBits,
|
|
1500
|
+
createAndSignTransaction,
|
|
1501
|
+
createBech32,
|
|
1502
|
+
createScriptPubKey,
|
|
1503
|
+
createTransactionPlan,
|
|
1504
|
+
decodeBech32,
|
|
1505
|
+
decrypt,
|
|
1506
|
+
decryptWallet,
|
|
1507
|
+
deriveChildKey,
|
|
1508
|
+
deriveChildKeyBIP32,
|
|
1509
|
+
deriveKeyAtPath,
|
|
1510
|
+
disconnect,
|
|
1511
|
+
domIdToPath,
|
|
1512
|
+
ec,
|
|
1513
|
+
encodeBech32,
|
|
1514
|
+
encrypt,
|
|
1515
|
+
encryptWallet,
|
|
1516
|
+
generateAddressFromMasterKey,
|
|
1517
|
+
generateAddressInfo,
|
|
1518
|
+
generateHDAddress,
|
|
1519
|
+
generateHDAddressBIP32,
|
|
1520
|
+
generateMasterKeyFromSeed,
|
|
1521
|
+
generatePrivateKey,
|
|
1522
|
+
getBalance,
|
|
1523
|
+
getBlockHeader,
|
|
1524
|
+
getCurrentBlockHeight,
|
|
1525
|
+
getIndexFromPath,
|
|
1526
|
+
getTransaction,
|
|
1527
|
+
getTransactionHistory,
|
|
1528
|
+
getUtxo,
|
|
1529
|
+
hash160,
|
|
1530
|
+
hash160ToBytes,
|
|
1531
|
+
hexToWIF,
|
|
1532
|
+
isChangePath,
|
|
1533
|
+
isWebSocketConnected,
|
|
1534
|
+
parsePathComponents,
|
|
1535
|
+
pathToDOMId,
|
|
1536
|
+
privateKeyToAddressInfo,
|
|
1537
|
+
publicKeyToAddress,
|
|
1538
|
+
rpc,
|
|
1539
|
+
sendAlpha,
|
|
1540
|
+
subscribeBlocks,
|
|
1541
|
+
vestingClassifier,
|
|
1542
|
+
vestingState,
|
|
1543
|
+
waitForConnection
|
|
1544
|
+
});
|
|
1545
|
+
//# sourceMappingURL=index.cjs.map
|