mainnet-js 2.7.31 → 3.0.0-next.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.
Files changed (126) hide show
  1. package/dist/index.html +1 -1
  2. package/dist/{mainnet-2.7.31.js → mainnet-3.0.0-next.0.js} +718 -678
  3. package/dist/module/cache/MemoryCache.d.ts +0 -1
  4. package/dist/module/cache/MemoryCache.d.ts.map +1 -1
  5. package/dist/module/cache/MemoryCache.js +5 -7
  6. package/dist/module/cache/MemoryCache.js.map +1 -1
  7. package/dist/module/cache/index.d.ts +1 -0
  8. package/dist/module/cache/index.d.ts.map +1 -1
  9. package/dist/module/cache/index.js +1 -0
  10. package/dist/module/cache/index.js.map +1 -1
  11. package/dist/module/cache/walletCache.d.ts +39 -0
  12. package/dist/module/cache/walletCache.d.ts.map +1 -0
  13. package/dist/module/cache/walletCache.js +141 -0
  14. package/dist/module/cache/walletCache.js.map +1 -0
  15. package/dist/module/history/{electrumTransformer.d.ts → getHistory.d.ts} +3 -3
  16. package/dist/module/history/getHistory.d.ts.map +1 -0
  17. package/dist/module/history/{electrumTransformer.js → getHistory.js} +47 -14
  18. package/dist/module/history/getHistory.js.map +1 -0
  19. package/dist/module/index.d.ts +3 -1
  20. package/dist/module/index.d.ts.map +1 -1
  21. package/dist/module/index.js +3 -1
  22. package/dist/module/index.js.map +1 -1
  23. package/dist/module/interface.d.ts +7 -1
  24. package/dist/module/interface.d.ts.map +1 -1
  25. package/dist/module/interface.js.map +1 -1
  26. package/dist/module/message/interface.d.ts +2 -2
  27. package/dist/module/message/interface.d.ts.map +1 -1
  28. package/dist/module/message/interface.js +0 -3
  29. package/dist/module/message/interface.js.map +1 -1
  30. package/dist/module/message/signed.d.ts +5 -5
  31. package/dist/module/message/signed.d.ts.map +1 -1
  32. package/dist/module/message/signed.js +14 -14
  33. package/dist/module/message/signed.js.map +1 -1
  34. package/dist/module/network/ElectrumNetworkProvider.d.ts +2 -2
  35. package/dist/module/network/ElectrumNetworkProvider.d.ts.map +1 -1
  36. package/dist/module/network/ElectrumNetworkProvider.js +2 -1
  37. package/dist/module/network/ElectrumNetworkProvider.js.map +1 -1
  38. package/dist/module/network/NetworkProvider.d.ts +2 -2
  39. package/dist/module/network/NetworkProvider.d.ts.map +1 -1
  40. package/dist/module/network/constant.js +4 -4
  41. package/dist/module/network/constant.js.map +1 -1
  42. package/dist/module/transaction/Wif.d.ts +21 -19
  43. package/dist/module/transaction/Wif.d.ts.map +1 -1
  44. package/dist/module/transaction/Wif.js +18 -15
  45. package/dist/module/transaction/Wif.js.map +1 -1
  46. package/dist/module/util/checkUtxos.d.ts +2 -2
  47. package/dist/module/util/checkUtxos.d.ts.map +1 -1
  48. package/dist/module/util/checkUtxos.js +11 -12
  49. package/dist/module/util/checkUtxos.js.map +1 -1
  50. package/dist/module/util/deriveCashaddr.d.ts.map +1 -1
  51. package/dist/module/util/deriveCashaddr.js +6 -0
  52. package/dist/module/util/deriveCashaddr.js.map +1 -1
  53. package/dist/module/util/deriveNetwork.js +1 -1
  54. package/dist/module/util/deriveNetwork.js.map +1 -1
  55. package/dist/module/util/hd.d.ts +3 -0
  56. package/dist/module/util/hd.d.ts.map +1 -0
  57. package/dist/module/util/hd.js +11 -0
  58. package/dist/module/util/hd.js.map +1 -0
  59. package/dist/module/util/index.d.ts +1 -0
  60. package/dist/module/util/index.d.ts.map +1 -1
  61. package/dist/module/util/index.js +1 -0
  62. package/dist/module/util/index.js.map +1 -1
  63. package/dist/module/util/sumUtxoValue.d.ts +3 -3
  64. package/dist/module/util/sumUtxoValue.d.ts.map +1 -1
  65. package/dist/module/util/sumUtxoValue.js.map +1 -1
  66. package/dist/module/wallet/Base.d.ts +37 -93
  67. package/dist/module/wallet/Base.d.ts.map +1 -1
  68. package/dist/module/wallet/Base.js +83 -250
  69. package/dist/module/wallet/Base.js.map +1 -1
  70. package/dist/module/wallet/HDWallet.d.ts +164 -0
  71. package/dist/module/wallet/HDWallet.d.ts.map +1 -0
  72. package/dist/module/wallet/HDWallet.js +486 -0
  73. package/dist/module/wallet/HDWallet.js.map +1 -0
  74. package/dist/module/wallet/Watch.d.ts +151 -0
  75. package/dist/module/wallet/Watch.d.ts.map +1 -0
  76. package/dist/module/wallet/Watch.js +307 -0
  77. package/dist/module/wallet/Watch.js.map +1 -0
  78. package/dist/module/wallet/Wif.d.ts +23 -29
  79. package/dist/module/wallet/Wif.d.ts.map +1 -1
  80. package/dist/module/wallet/Wif.js +204 -267
  81. package/dist/module/wallet/Wif.js.map +1 -1
  82. package/dist/module/wallet/createWallet.d.ts +7 -1
  83. package/dist/module/wallet/createWallet.d.ts.map +1 -1
  84. package/dist/module/wallet/createWallet.js +26 -17
  85. package/dist/module/wallet/createWallet.js.map +1 -1
  86. package/dist/module/wallet/interface.d.ts +3 -3
  87. package/dist/module/wallet/interface.d.ts.map +1 -1
  88. package/dist/module/wallet/model.d.ts +3 -3
  89. package/dist/module/wallet/model.d.ts.map +1 -1
  90. package/dist/module/wallet/model.js +2 -18
  91. package/dist/module/wallet/model.js.map +1 -1
  92. package/dist/tsconfig.tsbuildinfo +1 -1
  93. package/package.json +1 -1
  94. package/src/cache/MemoryCache.ts +5 -5
  95. package/src/cache/index.ts +1 -0
  96. package/src/cache/walletCache.ts +252 -0
  97. package/src/history/{electrumTransformer.test.ts → getHistory.test.ts} +6 -19
  98. package/src/history/{electrumTransformer.ts → getHistory.ts} +63 -15
  99. package/src/index.ts +3 -1
  100. package/src/interface.ts +8 -1
  101. package/src/message/interface.ts +2 -28
  102. package/src/message/signed.test.ts +56 -48
  103. package/src/message/signed.ts +15 -18
  104. package/src/network/ElectrumNetworkProvider.ts +4 -4
  105. package/src/network/NetworkProvider.ts +2 -2
  106. package/src/network/Rpc.test.ts +1 -1
  107. package/src/network/constant.ts +4 -4
  108. package/src/transaction/Wif.ts +41 -35
  109. package/src/util/checkUtxos.ts +21 -26
  110. package/src/util/deriveCashaddr.ts +8 -0
  111. package/src/util/deriveNetwork.ts +1 -1
  112. package/src/util/derivePublicKeyHash.test.ts +0 -13
  113. package/src/util/hd.ts +16 -0
  114. package/src/util/index.ts +1 -0
  115. package/src/util/sumUtxoValue.ts +5 -5
  116. package/src/wallet/Base.ts +123 -332
  117. package/src/wallet/HDWallet.test.ts +372 -0
  118. package/src/wallet/HDWallet.ts +764 -0
  119. package/src/wallet/Watch.ts +447 -0
  120. package/src/wallet/Wif.ts +258 -283
  121. package/src/wallet/createWallet.ts +28 -18
  122. package/src/wallet/interface.ts +3 -3
  123. package/src/wallet/model.test.ts +2 -2
  124. package/src/wallet/model.ts +6 -23
  125. package/dist/module/history/electrumTransformer.d.ts.map +0 -1
  126. package/dist/module/history/electrumTransformer.js.map +0 -1
@@ -0,0 +1,764 @@
1
+ import {
2
+ assertSuccess,
3
+ binToHex,
4
+ CashAddressNetworkPrefix,
5
+ decodeHdPrivateKey,
6
+ decodeHdPublicKey,
7
+ deriveHdPath,
8
+ deriveHdPrivateNodeFromSeed,
9
+ deriveHdPublicNode,
10
+ deriveSeedFromBip39Mnemonic,
11
+ encodeHdPrivateKey,
12
+ encodeHdPublicKey,
13
+ generateBip39Mnemonic,
14
+ HdPrivateNodeValid,
15
+ HdPublicNodeValid,
16
+ sha256,
17
+ utf8ToBin,
18
+ } from "@bitauth/libauth";
19
+ import { WalletCache, WalletCacheI } from "../cache/index.js";
20
+ import { Config } from "../config.js";
21
+ import { NetworkType, prefixFromNetworkMap, UnitEnum } from "../enum.js";
22
+ import { getHistory } from "../history/getHistory.js";
23
+ import { TransactionHistoryItem } from "../history/interface.js";
24
+ import { Utxo } from "../interface.js";
25
+ import { checkForEmptySeed } from "../util/checkForEmptySeed.js";
26
+ import { _checkContextSafety, BaseWallet } from "./Base.js";
27
+ import { WalletTypeEnum } from "./enum.js";
28
+ import { CancelFn, SendRequestOptionsI, WalletInfoI } from "./interface.js";
29
+ import {
30
+ OpReturnData,
31
+ SendRequest,
32
+ SendRequestArray,
33
+ TokenSendRequest,
34
+ } from "./model.js";
35
+ import { arrayRange, getNextUnusedIndex } from "../util/hd.js";
36
+
37
+ export const GAP_SIZE = 20;
38
+
39
+ export interface HDWalletEvents {
40
+ /**
41
+ * Emitted when data has been received over the socket.
42
+ * @eventProperty
43
+ */
44
+ data: [string];
45
+ }
46
+
47
+ export interface HDWalletOptions {
48
+ name?: string;
49
+ depositIndex?: number;
50
+ changeIndex?: number;
51
+ mnemonic?: string;
52
+ derivation?: string;
53
+ xPriv?: string;
54
+ xPub?: string;
55
+ }
56
+
57
+ export class HDWallet extends BaseWallet {
58
+ static networkPrefix = CashAddressNetworkPrefix.mainnet;
59
+
60
+ readonly mnemonic!: string;
61
+ readonly derivation: string = Config.DefaultParentDerivationPath;
62
+ readonly walletId!: string;
63
+ declare readonly walletCache: WalletCacheI;
64
+
65
+ readonly xPriv!: string;
66
+ readonly xPub!: string;
67
+
68
+ readonly xPrivNode!: HdPrivateNodeValid;
69
+ readonly xPubNode!: HdPublicNodeValid;
70
+
71
+ // max index used for deposit address derivation
72
+ depositIndex: number = 0;
73
+ // max index used for change address derivation
74
+ changeIndex: number = 0;
75
+
76
+ depositStatusCallbacks: Array<(status: string) => void> = [];
77
+ changeStatusCallbacks: Array<(status: string) => void> = [];
78
+
79
+ depositWatchCancels: Array<CancelFn> = [];
80
+ changeWatchCancels: Array<CancelFn> = [];
81
+
82
+ depositStatuses: Array<string | null> = [];
83
+ changeStatuses: Array<string | null> = [];
84
+
85
+ depositUtxos: Array<Utxo[]> = [];
86
+ changeUtxos: Array<Utxo[]> = [];
87
+
88
+ watchPromise?: Promise<any[]> = undefined;
89
+
90
+ public get networkPrefix(): CashAddressNetworkPrefix {
91
+ return prefixFromNetworkMap[this.network];
92
+ }
93
+
94
+ /// Create an uninitialized HDWallet, this method should not be called directly
95
+ /// Instead static methods such as `newRandom` or `fromSeed` should be used
96
+ constructor(network: NetworkType = NetworkType.Mainnet) {
97
+ super(network);
98
+
99
+ // @ts-ignore
100
+ this.walletType = WalletTypeEnum.Hd;
101
+ }
102
+
103
+ /// Initialize the wallet given the options mnemonic, xPriv or xPub
104
+ /// If none provided, a new random mnemonic will be generated
105
+ /// If mnemonic or xPriv provided, the wallet will be able to sign transactions
106
+ /// If xPub provided, the wallet will be watch-only
107
+ /// This internal method is called by the various static constructors
108
+ protected async initialize({
109
+ name = "",
110
+ depositIndex = 0,
111
+ changeIndex = 0,
112
+ mnemonic = undefined,
113
+ derivation = undefined,
114
+ xPriv = undefined,
115
+ xPub = undefined,
116
+ }: HDWalletOptions = {}) {
117
+ // newRandom
118
+ if (!xPriv && !xPub && !mnemonic) {
119
+ mnemonic = generateBip39Mnemonic();
120
+ }
121
+
122
+ this.depositIndex = depositIndex;
123
+ this.changeIndex = changeIndex;
124
+
125
+ // @ts-ignore
126
+ this.xPub = xPub ? xPub : "";
127
+
128
+ if (mnemonic?.length) {
129
+ // @ts-ignore
130
+ this.derivation = derivation
131
+ ? derivation
132
+ : Config.DefaultParentDerivationPath;
133
+ // @ts-ignore
134
+ this.mnemonic = mnemonic ? mnemonic : generateBip39Mnemonic();
135
+
136
+ if (this.mnemonic.length == 0) {
137
+ throw Error("refusing to create wallet from empty mnemonic");
138
+ }
139
+
140
+ const seed = deriveSeedFromBip39Mnemonic(this.mnemonic);
141
+ checkForEmptySeed(seed);
142
+
143
+ const rootNode = deriveHdPrivateNodeFromSeed(seed, {
144
+ assumeValidity: true,
145
+ throwErrors: true,
146
+ });
147
+ const node = deriveHdPath(rootNode, this.derivation);
148
+ // @ts-ignore
149
+ this.xPriv = assertSuccess(
150
+ encodeHdPrivateKey({
151
+ ...node,
152
+ network: this.network === NetworkType.Mainnet ? "mainnet" : "testnet",
153
+ node: node,
154
+ })
155
+ ).hdPrivateKey;
156
+ // @ts-ignore
157
+ this.xPrivNode = node;
158
+
159
+ // @ts-ignore
160
+ this.xPubNode = deriveHdPublicNode(node);
161
+ // @ts-ignore
162
+ this.xPub = assertSuccess(
163
+ encodeHdPublicKey({
164
+ node: this.xPubNode,
165
+ network: this.network === NetworkType.Mainnet ? "mainnet" : "testnet",
166
+ })
167
+ ).hdPublicKey;
168
+ } else if (xPriv) {
169
+ // @ts-ignore
170
+ this.xPriv = xPriv;
171
+
172
+ const decoded = assertSuccess(decodeHdPrivateKey(xPriv));
173
+ if (
174
+ decoded.network !==
175
+ (this.network === NetworkType.Mainnet ? "mainnet" : "testnet")
176
+ ) {
177
+ throw new Error(
178
+ `xPriv network (${decoded.network}) does not match wallet network (${
179
+ this.network === NetworkType.Mainnet ? "mainnet" : "testnet"
180
+ })`
181
+ );
182
+ }
183
+ // @ts-ignore
184
+ this.xPrivNode = decoded.node;
185
+
186
+ // @ts-ignore
187
+ this.xPubNode = deriveHdPublicNode(decoded.node);
188
+ // @ts-ignore
189
+ this.xPub = assertSuccess(
190
+ encodeHdPublicKey({
191
+ node: this.xPubNode,
192
+ network: this.network === NetworkType.Mainnet ? "mainnet" : "testnet",
193
+ })
194
+ ).hdPublicKey;
195
+ } else if (xPub) {
196
+ const decoded = assertSuccess(decodeHdPublicKey(xPub));
197
+ if (
198
+ decoded.network !==
199
+ (this.network === NetworkType.Mainnet ? "mainnet" : "testnet")
200
+ ) {
201
+ throw new Error(
202
+ `xPriv network (${decoded.network}) does not match wallet network (${
203
+ this.network === NetworkType.Mainnet ? "mainnet" : "testnet"
204
+ })`
205
+ );
206
+ }
207
+
208
+ // @ts-ignore
209
+ this.xPubNode = decoded.node;
210
+ // @ts-ignore
211
+ this.xPub = xPub;
212
+ } else {
213
+ throw new Error(
214
+ "mnemonic, xPriv or xPub must be provided to create an HDWallet"
215
+ );
216
+ }
217
+
218
+ this.name = name;
219
+
220
+ // @ts-ignore
221
+ this.walletId = binToHex(
222
+ sha256.hash(
223
+ utf8ToBin(
224
+ `${
225
+ (this.mnemonic ? this.mnemonic + this.derivation : undefined) ??
226
+ this.xPriv ??
227
+ this.xPub
228
+ }-${this.network}`
229
+ )
230
+ )
231
+ );
232
+ // @ts-ignore
233
+ this.walletCache = new WalletCache(
234
+ this.walletId,
235
+ this.xPrivNode ?? this.xPubNode,
236
+ this.networkPrefix
237
+ );
238
+
239
+ // init wallet cache
240
+ await this.walletCache.init();
241
+ // start watching addresses asynchronously
242
+ this.makeWatchPromise().catch(() => {});
243
+
244
+ return this;
245
+ }
246
+
247
+ /// Stops the wallet from watching for address changes
248
+ /// After calling this method, the wallet will no longer update and is considered defunct
249
+ public async stop() {
250
+ await Promise.all(
251
+ [...this.depositWatchCancels, ...this.changeWatchCancels].map(
252
+ (cancelFn) => cancelFn?.()
253
+ )
254
+ );
255
+ }
256
+
257
+ /// Scan more addresses for activity beyond the current gap limit, extending the watched range as needed
258
+ public async scanMoreAddresses(amount: number = GAP_SIZE) {
259
+ await this.watchPromise;
260
+
261
+ await this.makeWatchPromise(amount);
262
+ await this.watchPromise;
263
+ }
264
+
265
+ /// Internal method to start watching addresses for activity, extending the watched range as needed
266
+ private async makeWatchPromise(gapSize: number = GAP_SIZE) {
267
+ await this.watchPromise;
268
+
269
+ this.watchPromise = Promise.all([
270
+ (async () => {
271
+ const depositIndex = this.depositIndex;
272
+ const depositStartIndex =
273
+ (this.depositStatuses
274
+ .filter((s) => s)
275
+ .map((_, i) => i)
276
+ .at(-1) ?? -1) + 1;
277
+ const depositStopIndex = Math.max(
278
+ this.depositIndex,
279
+ depositStartIndex + gapSize
280
+ );
281
+
282
+ const depositAddresses = arrayRange(
283
+ depositStartIndex,
284
+ depositStopIndex
285
+ ).map((i) => this.walletCache.getByIndex(i, false).address);
286
+
287
+ await Promise.all(
288
+ depositAddresses.map(
289
+ async (addr, index) =>
290
+ new Promise<void>(async (resolve) => {
291
+ index = depositStartIndex + index;
292
+
293
+ if (this.depositStatuses[index] !== undefined) {
294
+ resolve();
295
+ }
296
+
297
+ const { status: prevStatus, utxos: prevUtxos } =
298
+ this.walletCache.getByIndex(index, false);
299
+ this.depositStatuses[index] = prevStatus;
300
+ this.depositUtxos[index] = prevUtxos;
301
+
302
+ const callback = async (
303
+ args: [address: string, status: string | null]
304
+ ) => {
305
+ const [address, status] = args;
306
+ if (address != addr) {
307
+ return;
308
+ }
309
+
310
+ if (status === null) {
311
+ this.depositUtxos[index] = [];
312
+ }
313
+
314
+ if (
315
+ status !== null &&
316
+ status !== this.depositStatuses[index]
317
+ ) {
318
+ const utxos = (await this.provider.getUtxos(addr)).map(
319
+ (utxo) => {
320
+ utxo.address = addr;
321
+ return utxo;
322
+ }
323
+ );
324
+ this.depositUtxos[index] = utxos;
325
+ this.walletCache.setStatusAndUtxos(addr, status, utxos);
326
+
327
+ const newDepositIndex = Math.max(depositIndex, index + 1);
328
+ if (newDepositIndex > depositIndex) {
329
+ this.depositIndex = Math.max(
330
+ newDepositIndex,
331
+ this.depositIndex
332
+ );
333
+ this.makeWatchPromise();
334
+ }
335
+ }
336
+ this.depositStatuses[index] = status;
337
+ resolve();
338
+ };
339
+
340
+ this.depositWatchCancels[index] =
341
+ await this.provider.subscribeToAddress(addr, callback as any);
342
+ })
343
+ )
344
+ );
345
+ return depositAddresses.length;
346
+ })(),
347
+
348
+ (async () => {
349
+ const changeIndex = this.changeIndex;
350
+ const changeStartIndex =
351
+ (this.changeStatuses
352
+ .filter((s) => s)
353
+ .map((_, i) => i)
354
+ .at(-1) ?? -1) + 1;
355
+ const changeStopIndex = Math.max(
356
+ this.changeIndex,
357
+ changeStartIndex + gapSize
358
+ );
359
+
360
+ const changeAddresses = arrayRange(
361
+ changeStartIndex,
362
+ changeStopIndex
363
+ ).map((i) => this.walletCache.getByIndex(i, true).address);
364
+
365
+ await Promise.all(
366
+ changeAddresses.map(
367
+ async (addr, index) =>
368
+ new Promise<void>(async (resolve) => {
369
+ index = changeStartIndex + index;
370
+
371
+ if (this.changeStatuses[index] !== undefined) {
372
+ resolve();
373
+ }
374
+
375
+ const { status: prevStatus, utxos: prevUtxos } =
376
+ this.walletCache.getByIndex(index, false);
377
+ this.changeStatuses[index] = prevStatus;
378
+ this.changeUtxos[index] = prevUtxos;
379
+
380
+ const callback = async (
381
+ args: [address: string, status: string | null]
382
+ ) => {
383
+ const [address, status] = args;
384
+ if (address != addr) {
385
+ return;
386
+ }
387
+
388
+ if (status === null) {
389
+ this.changeUtxos[index] = [];
390
+ }
391
+
392
+ if (
393
+ status !== null &&
394
+ status !== this.changeStatuses[index]
395
+ ) {
396
+ const utxos = (await this.provider.getUtxos(addr)).map(
397
+ (utxo) => {
398
+ utxo.address = addr;
399
+ return utxo;
400
+ }
401
+ );
402
+ this.changeUtxos[index] = utxos;
403
+ this.walletCache.setStatusAndUtxos(addr, status, utxos);
404
+
405
+ const newChangeIndex = Math.max(changeIndex, index + 1);
406
+ if (newChangeIndex > changeIndex) {
407
+ this.changeIndex = Math.max(
408
+ newChangeIndex,
409
+ this.changeIndex
410
+ );
411
+ this.makeWatchPromise();
412
+ }
413
+ }
414
+ this.changeStatuses[index] = status;
415
+ resolve();
416
+ };
417
+
418
+ this.changeWatchCancels[index] =
419
+ await this.provider.subscribeToAddress(addr, callback as any);
420
+ })
421
+ )
422
+ );
423
+
424
+ return changeAddresses.length;
425
+ })(),
426
+ ]);
427
+ }
428
+
429
+ // Return wallet info
430
+ public getInfo(): WalletInfoI {
431
+ return {
432
+ isTestnet: this.isTestnet,
433
+ name: this.name,
434
+ network: this.network as any,
435
+ seed: this.mnemonic,
436
+ walletId: this.toString(),
437
+ walletDbEntry: this.toDbString(),
438
+ };
439
+ }
440
+
441
+ /**
442
+ * utxos Get unspent outputs for the wallet
443
+ *
444
+ */
445
+ public async getUtxos() {
446
+ await this.watchPromise;
447
+
448
+ return [...this.depositUtxos, ...this.changeUtxos].flat();
449
+ }
450
+
451
+ /// Get next unused deposit address, or the address at the specified index
452
+ public getDepositAddress(index: number = -1): string {
453
+ index = getNextUnusedIndex(index, this.depositStatuses);
454
+
455
+ return this.walletCache.getByIndex(index, false).address;
456
+ }
457
+
458
+ /// Get next unused token deposit address, or the token address at the specified index
459
+ public getTokenDepositAddress(index: number = -1): string {
460
+ index = getNextUnusedIndex(index, this.depositStatuses);
461
+
462
+ return this.walletCache.getByIndex(index, false).tokenAddress;
463
+ }
464
+
465
+ /// Get next unused change address, or the address at the specified index
466
+ public getChangeAddress(index: number = -1): string {
467
+ index = getNextUnusedIndex(index, this.changeStatuses);
468
+
469
+ return this.walletCache.getByIndex(index, true).address;
470
+ }
471
+
472
+ /// Get next unused token change address, or the token address at the specified index
473
+ public getChangeTokenAddress(index: number = -1): string {
474
+ index = getNextUnusedIndex(index, this.changeStatuses);
475
+
476
+ return this.walletCache.getByIndex(index, true).tokenAddress;
477
+ }
478
+
479
+ /**
480
+ * fromSeed - create a wallet using the seed phrase and derivation path
481
+ *
482
+ * unless specified the derivation path m/44'/0'/0'/0/0 will be used
483
+ *
484
+ * @param seed BIP39 12 word seed phrase
485
+ * @param derivationPath BIP44 HD wallet derivation path to get a single the private key from hierarchy
486
+ *
487
+ * @returns instantiated wallet
488
+ */
489
+ public static async fromSeed<T extends typeof HDWallet>(
490
+ this: T,
491
+ seed: string,
492
+ derivationPath?: string,
493
+ depositIndex?: number,
494
+ changeIndex?: number
495
+ ): Promise<InstanceType<T>> {
496
+ return new this().initialize({
497
+ mnemonic: seed,
498
+ derivation: derivationPath,
499
+ depositIndex: depositIndex,
500
+ changeIndex: changeIndex,
501
+ }) as Promise<InstanceType<T>>;
502
+ }
503
+
504
+ /**
505
+ * newRandom - create a random wallet
506
+ *
507
+ * if `name` parameter is specified, the wallet will also be persisted to DB
508
+ *
509
+ * @param name user friendly wallet alias
510
+ * @param dbName name under which the wallet will be stored in the database
511
+ *
512
+ * @returns instantiated wallet
513
+ */
514
+ public static async newRandom<T extends typeof HDWallet>(
515
+ this: T,
516
+ name: string = "",
517
+ dbName?: string
518
+ ): Promise<InstanceType<T>> {
519
+ dbName = dbName ? dbName : this.networkPrefix;
520
+ if (name.length > 0) {
521
+ return this.named(name, dbName);
522
+ }
523
+
524
+ return new this().initialize() as Promise<InstanceType<T>>;
525
+ }
526
+
527
+ /**
528
+ * fromXPub - create a watch-only wallet using the HD Wallet Public key
529
+ *
530
+ * @param xPub HD Wallet Public Key
531
+ *
532
+ * @returns instantiated wallet
533
+ */
534
+ public static async fromXPub<T extends typeof HDWallet>(
535
+ this: T,
536
+ xPub: string
537
+ ): Promise<InstanceType<T>> {
538
+ return new this().initialize({
539
+ xPub: xPub,
540
+ }) as Promise<InstanceType<T>>;
541
+ }
542
+
543
+ /**
544
+ * fromXPriv - create a wallet using the HD Wallet Private key
545
+ *
546
+ * @param xPub HD Wallet Private Key
547
+ *
548
+ * @returns instantiated wallet
549
+ */
550
+ public static async fromXPriv<T extends typeof HDWallet>(
551
+ this: T,
552
+ xPriv: string
553
+ ): Promise<InstanceType<T>> {
554
+ return new this().initialize({
555
+ xPriv: xPriv,
556
+ }) as Promise<InstanceType<T>>;
557
+ }
558
+
559
+ /**
560
+ * fromId - create a wallet from encoded walletId string
561
+ *
562
+ * @param walletId walletId options to steer the creation process
563
+ *
564
+ * @returns wallet instantiated accordingly to the walletId rules
565
+ */
566
+ public static async fromId<T extends typeof HDWallet>(
567
+ this: T,
568
+ walletId: string
569
+ ): Promise<InstanceType<T>> {
570
+ return new this().fromId(walletId) as InstanceType<T>;
571
+ }
572
+
573
+ /// override the base class fromId method implementation
574
+ protected async fromId(walletId: string): Promise<this> {
575
+ const [walletType, networkGiven, arg1, arg2, arg3, arg4] =
576
+ walletId.split(":");
577
+
578
+ if (this.network != networkGiven) {
579
+ throw Error(`Network prefix ${networkGiven} to a ${this.network} wallet`);
580
+ }
581
+
582
+ if (walletType === WalletTypeEnum.Named) {
583
+ if (arg2) {
584
+ // named:testnet:wallet_1:my_database
585
+ return this.named(arg1, arg2);
586
+ } else {
587
+ // named:testnet:wallet_1
588
+ return this.named(arg1);
589
+ }
590
+ }
591
+
592
+ if (walletType !== WalletTypeEnum.Hd) {
593
+ throw Error(
594
+ `fromId called on a ${walletType} wallet, expected a ${WalletTypeEnum.Hd} wallet`
595
+ );
596
+ }
597
+
598
+ if (arg1.startsWith("priv", 1)) {
599
+ return this.initialize({
600
+ xPriv: arg1,
601
+ depositIndex: parseInt(arg2) || 0,
602
+ changeIndex: parseInt(arg3) || 0,
603
+ });
604
+ }
605
+
606
+ if (arg1.startsWith("pub", 1)) {
607
+ return this.initialize({
608
+ xPub: arg1,
609
+ depositIndex: parseInt(arg2) || 0,
610
+ changeIndex: parseInt(arg3) || 0,
611
+ });
612
+ }
613
+
614
+ return this.initialize({
615
+ mnemonic: arg1,
616
+ derivation: arg2,
617
+ depositIndex: parseInt(arg3) || 0,
618
+ changeIndex: parseInt(arg4) || 0,
619
+ });
620
+ }
621
+
622
+ /**
623
+ * encodeTransaction Encode and sign a transaction given a list of sendRequests, options and estimate fees.
624
+ * @param {SendRequest[]} sendRequests SendRequests
625
+ * @param {boolean} discardChange=false
626
+ * @param {SendRequestOptionsI} options Options of the send requests
627
+ */
628
+ public async encodeTransaction(
629
+ requests:
630
+ | SendRequest
631
+ | TokenSendRequest
632
+ | OpReturnData
633
+ | Array<SendRequest | TokenSendRequest | OpReturnData>
634
+ | SendRequestArray[],
635
+ discardChange: boolean = false,
636
+ options?: SendRequestOptionsI,
637
+ privateKey?: Uint8Array
638
+ ) {
639
+ if (!this.xPriv && !privateKey && options?.buildUnsigned !== true) {
640
+ throw new Error(`Missing private key`);
641
+ }
642
+
643
+ return super.encodeTransaction(
644
+ requests,
645
+ discardChange,
646
+ options,
647
+ privateKey
648
+ );
649
+ }
650
+
651
+ //#region Serialization
652
+ // Returns the serialized wallet as a string
653
+ // If storing in a database, set asNamed to false to store secrets
654
+ // In all other cases, the a named wallet is deserialized from the database
655
+ // by the name key
656
+ public toString() {
657
+ if (this.name) {
658
+ return `named:${this.network}:${this.name}`;
659
+ }
660
+
661
+ return this.toDbString();
662
+ }
663
+
664
+ /**
665
+ * toDbString - store the serialized version of the wallet in the database, not just the name
666
+ *
667
+ * @throws {Error} if called on BaseWallet
668
+ */
669
+ public toDbString() {
670
+ if (this.walletType == WalletTypeEnum.Hd) {
671
+ if (this.mnemonic?.length > 0) {
672
+ return `${this.walletType}:${this.network}:${this.mnemonic}:${this.derivation}:${this.depositIndex}:${this.changeIndex}`;
673
+ }
674
+
675
+ if (this.xPriv?.length > 0) {
676
+ return `${this.walletType}:${this.network}:${this.xPriv}:${this.depositIndex}:${this.changeIndex}`;
677
+ }
678
+
679
+ if (this.xPub?.length > 0) {
680
+ return `${this.walletType}:${this.network}:${this.xPub}:${this.depositIndex}:${this.changeIndex}`;
681
+ }
682
+
683
+ throw Error("HDWallet has no mnemonic, xPriv or xPub to serialize");
684
+ }
685
+
686
+ throw Error("toDbString unsupported wallet type");
687
+ }
688
+ //#endregion Serialization
689
+
690
+ /**
691
+ * getHistory gets transaction history of this wallet with most data decoded and ready to present to user
692
+ * @note balance calculations are valid only if querying to the blockchain tip (`toHeight` === -1, `count` === -1)
693
+ * @note this method is heavy on network calls, if invoked in browser use of cache is advised, @see `Config.UseLocalStorageCache`
694
+ * @note this method tries to recreate the history tab view of Electron Cash wallet, however, it may not be 100% accurate if the tnransaction value changes are the same in the same block (ordering)
695
+ *
696
+ * @param unit optional, BCH or currency unit to present balance and balance changes. If unit is currency like USD or EUR, balances will be subject to possible rounding errors. Default 0
697
+ * @param fromHeight optional, if set, history will be limited. Default 0
698
+ * @param toHeight optional, if set, history will be limited. Default -1, meaning that all history items will be returned, including mempool
699
+ * @param start optional, if set, the result set will be paginated with offset `start`
700
+ * @param count optional, if set, the result set will be paginated with `count`. Default -1, meaning that all history items will be returned
701
+ *
702
+ * @returns an array of transaction history items, with input values and addresses encoded in cashaddress format. @see `TransactionHistoryItem` type
703
+ */
704
+ public async getHistory({
705
+ unit = "sat",
706
+ fromHeight = 0,
707
+ toHeight = -1,
708
+ start = 0,
709
+ count = -1,
710
+ }: {
711
+ unit?: UnitEnum;
712
+ fromHeight?: number;
713
+ toHeight?: number;
714
+ start?: number;
715
+ count?: number;
716
+ }): Promise<TransactionHistoryItem[]> {
717
+ const addresses = [
718
+ ...this.depositStatuses
719
+ .map((status, i) =>
720
+ status !== null && i < this.depositIndex
721
+ ? this.walletCache.getByIndex(i, false).address
722
+ : undefined
723
+ )
724
+ .filter((address) => address !== undefined),
725
+ ...this.changeStatuses
726
+ .map((status, i) =>
727
+ status !== null && i < this.depositIndex
728
+ ? this.walletCache.getByIndex(i, true).address
729
+ : undefined
730
+ )
731
+ .filter((address) => address !== undefined),
732
+ ];
733
+
734
+ return getHistory({
735
+ addresses: addresses,
736
+ provider: this.provider,
737
+ unit,
738
+ fromHeight,
739
+ toHeight,
740
+ start,
741
+ count,
742
+ });
743
+ }
744
+ }
745
+
746
+ /**
747
+ * Class to manage a testnet wallet.
748
+ */
749
+ export class TestNetHDWallet extends HDWallet {
750
+ static networkPrefix = CashAddressNetworkPrefix.testnet;
751
+ constructor() {
752
+ super(NetworkType.Testnet);
753
+ }
754
+ }
755
+
756
+ /**
757
+ * Class to manage a regtest wallet.
758
+ */
759
+ export class RegTestHDWallet extends HDWallet {
760
+ static networkPrefix = CashAddressNetworkPrefix.regtest;
761
+ constructor() {
762
+ super(NetworkType.Regtest);
763
+ }
764
+ }