mainnet-js 3.0.0 → 3.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.
Files changed (38) hide show
  1. package/dist/index.html +1 -1
  2. package/dist/{mainnet-3.0.0.js → mainnet-3.1.0.js} +5 -5
  3. package/dist/module/history/getHistory.js +10 -10
  4. package/dist/module/history/getHistory.js.map +1 -1
  5. package/dist/module/network/ElectrumNetworkProvider.d.ts +6 -3
  6. package/dist/module/network/ElectrumNetworkProvider.d.ts.map +1 -1
  7. package/dist/module/network/ElectrumNetworkProvider.js +18 -11
  8. package/dist/module/network/ElectrumNetworkProvider.js.map +1 -1
  9. package/dist/module/network/NetworkProvider.d.ts +14 -6
  10. package/dist/module/network/NetworkProvider.d.ts.map +1 -1
  11. package/dist/module/network/index.d.ts +1 -1
  12. package/dist/module/network/index.d.ts.map +1 -1
  13. package/dist/module/network/interface.d.ts +4 -3
  14. package/dist/module/network/interface.d.ts.map +1 -1
  15. package/dist/module/wallet/Base.d.ts.map +1 -1
  16. package/dist/module/wallet/Base.js +1 -2
  17. package/dist/module/wallet/Base.js.map +1 -1
  18. package/dist/module/wallet/HDWallet.d.ts +9 -3
  19. package/dist/module/wallet/HDWallet.d.ts.map +1 -1
  20. package/dist/module/wallet/HDWallet.js +43 -24
  21. package/dist/module/wallet/HDWallet.js.map +1 -1
  22. package/dist/module/wallet/Util.d.ts +5 -3
  23. package/dist/module/wallet/Util.d.ts.map +1 -1
  24. package/dist/module/wallet/Util.js +28 -22
  25. package/dist/module/wallet/Util.js.map +1 -1
  26. package/dist/tsconfig.tsbuildinfo +1 -1
  27. package/package.json +1 -1
  28. package/src/history/getHistory.ts +15 -15
  29. package/src/network/ElectrumNetworkProvider.ts +61 -26
  30. package/src/network/NetworkProvider.ts +44 -6
  31. package/src/network/index.ts +5 -1
  32. package/src/network/interface.ts +8 -3
  33. package/src/wallet/Base.ts +1 -4
  34. package/src/wallet/HDWallet.test.ts +239 -0
  35. package/src/wallet/HDWallet.ts +49 -26
  36. package/src/wallet/Util.test.ts +5 -6
  37. package/src/wallet/Util.ts +67 -46
  38. package/src/wallet/Wif.test.ts +3 -9
@@ -414,6 +414,12 @@ export class HDWallet extends BaseWallet {
414
414
  if (newIndex > getCurrentIndex()) {
415
415
  setCurrentIndex(newIndex);
416
416
  }
417
+
418
+ // Maintain the gap: extend watched range if it shrank
419
+ const gap = statuses.length - getCurrentIndex();
420
+ if (gap < gapSize) {
421
+ await this.watchAddressType(isChange, gapSize);
422
+ }
417
423
  }
418
424
 
419
425
  // Notify wallet watchers of the status change
@@ -463,46 +469,63 @@ export class HDWallet extends BaseWallet {
463
469
  * @returns Cancel function to stop watching
464
470
  */
465
471
  public async watchStatus(
466
- callback: (status: string | null, address: string) => void
472
+ callback: (status: string | null, address: string) => void,
473
+ debounce: number = 100
467
474
  ): Promise<CancelFn> {
468
475
  await this.watchPromise;
469
476
 
470
- this.walletWatchCallbacks.push(callback);
477
+ let debounceTimer: ReturnType<typeof setTimeout> | undefined;
478
+ let pendingStatus: string | null = null;
479
+ let pendingAddress: string = "";
480
+
481
+ const debouncedCallback = (status: string | null, address: string) => {
482
+ pendingStatus = status;
483
+ pendingAddress = address;
484
+ if (debounceTimer) clearTimeout(debounceTimer);
485
+ debounceTimer = setTimeout(() => {
486
+ debounceTimer = undefined;
487
+ callback(pendingStatus, pendingAddress);
488
+ }, debounce);
489
+ };
490
+
491
+ this.walletWatchCallbacks.push(debouncedCallback);
471
492
 
472
493
  return async () => {
473
- const index = this.walletWatchCallbacks.indexOf(callback);
494
+ if (debounceTimer) clearTimeout(debounceTimer);
495
+ const index = this.walletWatchCallbacks.indexOf(debouncedCallback);
474
496
  if (index > -1) {
475
497
  this.walletWatchCallbacks.splice(index, 1);
476
498
  }
477
499
  };
478
500
  }
479
501
 
480
- public async watchBalance(
481
- callback: (balance: bigint) => void
502
+ /**
503
+ * Watch wallet for new transactions (HD wallet override)
504
+ *
505
+ * Uses unfiltered history so that seenTxHashes always covers all known
506
+ * transactions, including those from newly discovered addresses when
507
+ * depositIndex/changeIndex extends and widens getRawHistory's scope.
508
+ */
509
+ public override async watchTransactionHashes(
510
+ callback: (txHash: string) => void
482
511
  ): Promise<CancelFn> {
483
- let debounceTimer: ReturnType<typeof setTimeout> | undefined = undefined;
512
+ const seenTxHashes = new Set<string>();
513
+
484
514
  return this.watchStatus(async () => {
485
- if (debounceTimer) clearTimeout(debounceTimer);
486
- debounceTimer = setTimeout(async () => {
487
- debounceTimer = undefined;
488
- const balance = await this.getBalance();
489
- callback(balance);
490
- }, 100);
491
- });
492
- }
515
+ const history = await this.getRawHistory();
493
516
 
494
- public async watchTokenBalance(
495
- category: string,
496
- callback: (balance: bigint) => void
497
- ): Promise<CancelFn> {
498
- let debounceTimer: ReturnType<typeof setTimeout> | undefined = undefined;
499
- return await this.watchStatus(async () => {
500
- if (debounceTimer) clearTimeout(debounceTimer);
501
- debounceTimer = setTimeout(async () => {
502
- debounceTimer = undefined;
503
- const balance = await this.getTokenBalance(category);
504
- callback(balance);
505
- }, 100);
517
+ const newTxHashes: string[] = [];
518
+
519
+ for (const tx of history) {
520
+ if (!seenTxHashes.has(tx.tx_hash)) {
521
+ seenTxHashes.add(tx.tx_hash);
522
+ newTxHashes.push(tx.tx_hash);
523
+ }
524
+ }
525
+
526
+ if (newTxHashes.length > 0) {
527
+ newTxHashes.forEach((txHash) => callback(txHash));
528
+ }
506
529
  });
507
530
  }
508
531
 
@@ -1,5 +1,4 @@
1
1
  import { initProviders, disconnectProviders } from "../network";
2
- import { ElectrumRawTransaction } from "../network/interface";
3
2
  import { RegTestWallet, Wallet } from "./Wif";
4
3
 
5
4
  beforeAll(async () => {
@@ -41,9 +40,9 @@ describe("Utility tests", () => {
41
40
  test("Should get raw transaction", async () => {
42
41
  let wallet = await RegTestWallet.fromId(process.env.ALICE_ID!);
43
42
  const utxo = (await wallet.getUtxos())[0];
44
- const transaction = (await wallet.provider!.getRawTransactionObject(
43
+ const transaction = await wallet.provider!.getRawTransactionObject(
45
44
  utxo.txid
46
- )) as ElectrumRawTransaction;
45
+ );
47
46
  expect((await wallet.util.decodeTransaction(transaction.hash)).hash).toBe(
48
47
  utxo.txid
49
48
  );
@@ -67,15 +66,15 @@ describe("Utility tests", () => {
67
66
  "0e3e2357e806b6cdb1f70b54c3a3a17b6714ee1f0e68bebb44a74b1efd512098"
68
67
  );
69
68
 
70
- expect(decoded.vin[0].address).toBeUndefined();
71
- expect(decoded.vin[0].value).toBeUndefined();
69
+ // non-enriched vin entries don't carry vout fields (enforced by types)
70
+ expect((decoded.vin[0] as any).address).toBeUndefined();
72
71
  });
73
72
 
74
73
  test("Should decode a transaction and fetch input values and addresses", async () => {
75
74
  const txHash =
76
75
  "dc8f059900807c36941313f10b43ec049e23dfede4e09f8fbccc3871ed359fbe";
77
76
  const decoded = await Wallet.util.decodeTransaction(txHash, true);
78
- expect(decoded.vin[0].address).toBeDefined();
77
+ expect(decoded.vin[0].scriptPubKey.addresses[0]).toBeDefined();
79
78
  expect(decoded.vin[0].value).toBeDefined();
80
79
 
81
80
  // uncomment next line
@@ -15,9 +15,9 @@ import { getNetworkProvider } from "../network/default.js";
15
15
  import {
16
16
  ElectrumRawTransaction,
17
17
  ElectrumRawTransactionVin,
18
- ElectrumRawTransactionVinScriptSig,
18
+ ElectrumRawTransactionVinWithValues,
19
19
  ElectrumRawTransactionVout,
20
- ElectrumRawTransactionVoutScriptPubKey,
20
+ ElectrumRawTransactionWithInputValues,
21
21
  } from "../network/interface.js";
22
22
  import NetworkProvider from "../network/NetworkProvider.js";
23
23
  import { getTransactionHash } from "../util/transaction.js";
@@ -49,10 +49,18 @@ export class Util {
49
49
  return getTransactionHash(rawTransactionHex);
50
50
  }
51
51
 
52
+ public async decodeTransaction(
53
+ transactionHashOrHex: string,
54
+ loadInputValues: true
55
+ ): Promise<ElectrumRawTransactionWithInputValues>;
56
+ public async decodeTransaction(
57
+ transactionHashOrHex: string,
58
+ loadInputValues?: false
59
+ ): Promise<ElectrumRawTransaction>;
52
60
  public async decodeTransaction(
53
61
  transactionHashOrHex: string,
54
62
  loadInputValues: boolean = false
55
- ): Promise<ElectrumRawTransaction> {
63
+ ): Promise<ElectrumRawTransaction | ElectrumRawTransactionWithInputValues> {
56
64
  let transactionHex: string;
57
65
  let transactionBin: Uint8Array;
58
66
  let txHash: string;
@@ -89,14 +97,15 @@ export class Util {
89
97
  const transactionMap = new Map<string, ElectrumRawTransaction>();
90
98
  transactions.forEach((val) => transactionMap.set(val.hash, val));
91
99
 
92
- transaction.vin.forEach((input) => {
93
- const output = transactionMap
94
- .get(input.txid)!
95
- .vout.find((val) => val.n === input.vout)!;
96
- input.address = output.scriptPubKey.addresses[0];
97
- input.value = output.value;
98
- input.tokenData = output.tokenData;
99
- });
100
+ const enrichedVin: ElectrumRawTransactionVinWithValues[] =
101
+ transaction.vin.map((input) => {
102
+ const output = transactionMap
103
+ .get(input.txid)!
104
+ .vout.find((val) => val.n === input.vout)!;
105
+ return { ...input, ...output };
106
+ });
107
+
108
+ return { ...transaction, vin: enrichedVin };
100
109
  }
101
110
 
102
111
  return transaction;
@@ -107,22 +116,30 @@ export class Util {
107
116
  txHash: string,
108
117
  txHex: string
109
118
  ): ElectrumRawTransaction {
110
- let result: ElectrumRawTransaction = {} as any;
111
-
112
- result.vin = transaction.inputs.map((input): ElectrumRawTransactionVin => {
113
- return {
114
- scriptSig: {
115
- hex: binToHex(input.unlockingBytecode),
116
- } as ElectrumRawTransactionVinScriptSig,
117
- sequence: input.sequenceNumber,
118
- txid: binToHex(input.outpointTransactionHash),
119
- vout: input.outpointIndex,
120
- };
121
- });
122
-
123
- result.vout = transaction.outputs.map(
124
- (output, index): ElectrumRawTransactionVout => {
125
- return {
119
+ return {
120
+ blockhash: "",
121
+ blocktime: 0,
122
+ confirmations: 0,
123
+ time: 0,
124
+ hash: txHash,
125
+ hex: txHex,
126
+ txid: txHash,
127
+ locktime: transaction.locktime,
128
+ version: transaction.version,
129
+ size: txHex.length / 2,
130
+ vin: transaction.inputs.map(
131
+ (input): ElectrumRawTransactionVin => ({
132
+ scriptSig: {
133
+ asm: "",
134
+ hex: binToHex(input.unlockingBytecode),
135
+ },
136
+ sequence: input.sequenceNumber,
137
+ txid: binToHex(input.outpointTransactionHash),
138
+ vout: input.outpointIndex,
139
+ })
140
+ ),
141
+ vout: transaction.outputs.map(
142
+ (output, index): ElectrumRawTransactionVout => ({
126
143
  n: index,
127
144
  scriptPubKey: {
128
145
  addresses: [
@@ -132,7 +149,7 @@ export class Util {
132
149
  output.lockingBytecode
133
150
  ).payload,
134
151
  prefix: prefixFromNetworkMap[this.network],
135
- })
152
+ }).address
136
153
  : assertSuccess(
137
154
  lockingBytecodeToCashAddress({
138
155
  bytecode: output.lockingBytecode,
@@ -140,31 +157,35 @@ export class Util {
140
157
  })
141
158
  ).address,
142
159
  ],
160
+ asm: "",
143
161
  hex: binToHex(output.lockingBytecode),
144
- } as ElectrumRawTransactionVoutScriptPubKey,
162
+ reqSigs: 1,
163
+ type: "",
164
+ },
145
165
  value: Number(output.valueSatoshis) / Number(bchParam.subUnits),
146
- };
147
- }
148
- );
149
-
150
- result.locktime = transaction.locktime;
151
- result.version = transaction.version;
152
- result.hash = txHash;
153
- result.hex = txHex;
154
- result.txid = txHash;
155
- result.size = txHex.length / 2;
156
-
157
- return result;
166
+ })
167
+ ),
168
+ };
158
169
  }
159
170
 
171
+ public static async decodeTransaction(
172
+ transactionHashOrHex: string,
173
+ loadInputValues: true,
174
+ network?: NetworkType
175
+ ): Promise<ElectrumRawTransactionWithInputValues>;
176
+ public static async decodeTransaction(
177
+ transactionHashOrHex: string,
178
+ loadInputValues?: false,
179
+ network?: NetworkType
180
+ ): Promise<ElectrumRawTransaction>;
160
181
  public static async decodeTransaction(
161
182
  transactionHashOrHex: string,
162
183
  loadInputValues: boolean = false,
163
184
  network?: NetworkType
164
- ): Promise<ElectrumRawTransaction> {
165
- return new this(network).decodeTransaction(
166
- transactionHashOrHex,
167
- loadInputValues
168
- );
185
+ ): Promise<ElectrumRawTransaction | ElectrumRawTransactionWithInputValues> {
186
+ if (loadInputValues) {
187
+ return new this(network).decodeTransaction(transactionHashOrHex, true);
188
+ }
189
+ return new this(network).decodeTransaction(transactionHashOrHex);
169
190
  }
170
191
  }
@@ -995,9 +995,7 @@ describe(`Wallet extrema behavior regression testing`, () => {
995
995
  OpReturnData.from("MEMO\x10LÖL😅"),
996
996
  { cashaddr: wallet.cashaddr!, value: 546n },
997
997
  ]);
998
- transaction = (await wallet.provider!.getRawTransactionObject(
999
- result.txId!
1000
- )) as ElectrumRawTransaction;
998
+ transaction = await wallet.provider!.getRawTransactionObject(result.txId!);
1001
999
  expect(transaction.vout[0].scriptPubKey.asm).toContain("OP_RETURN");
1002
1000
  expect(transaction.vout[0].scriptPubKey.hex.slice(4)).toBe(
1003
1001
  binToHex(utf8ToBin("MEMO\x10LÖL😅"))
@@ -1007,9 +1005,7 @@ describe(`Wallet extrema behavior regression testing`, () => {
1007
1005
  [wallet.cashaddr!, 546n],
1008
1006
  ["OP_RETURN", Uint8Array.from([0x00, 0x01, 0x02])],
1009
1007
  ]);
1010
- transaction = (await wallet.provider!.getRawTransactionObject(
1011
- result.txId!
1012
- )) as ElectrumRawTransaction;
1008
+ transaction = await wallet.provider!.getRawTransactionObject(result.txId!);
1013
1009
  expect(transaction.vout[1].scriptPubKey.asm).toContain("OP_RETURN");
1014
1010
  expect([
1015
1011
  ...hexToBin(transaction.vout[1].scriptPubKey.hex.slice(4)),
@@ -1019,9 +1015,7 @@ describe(`Wallet extrema behavior regression testing`, () => {
1019
1015
  OpReturnData.from(""),
1020
1016
  OpReturnData.from(Uint8Array.from([])),
1021
1017
  ]);
1022
- transaction = (await wallet.provider!.getRawTransactionObject(
1023
- result.txId!
1024
- )) as ElectrumRawTransaction;
1018
+ transaction = await wallet.provider!.getRawTransactionObject(result.txId!);
1025
1019
  expect(transaction.vout[0].scriptPubKey.asm).toContain("OP_RETURN");
1026
1020
  expect([...hexToBin(transaction.vout[0].scriptPubKey.hex)]).toStrictEqual([
1027
1021
  0x6a, 0x00,