mainnet-js 0.4.27 → 0.4.31

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 (75) hide show
  1. package/dist/index.html +1 -1
  2. package/dist/main/constant.d.ts +1 -0
  3. package/dist/main/constant.js +17 -1
  4. package/dist/main/constant.js.map +1 -1
  5. package/dist/main/util/balanceObjectFromSatoshi.js +2 -1
  6. package/dist/main/util/balanceObjectFromSatoshi.js.map +1 -1
  7. package/dist/main/util/floor.d.ts +1 -0
  8. package/dist/main/util/floor.js +9 -0
  9. package/dist/main/util/floor.js.map +1 -0
  10. package/dist/main/util/getAddrsByXpubKey.d.ts +22 -0
  11. package/dist/main/util/getAddrsByXpubKey.js +79 -0
  12. package/dist/main/util/getAddrsByXpubKey.js.map +1 -0
  13. package/dist/main/util/getXPubKey.d.ts +1 -0
  14. package/dist/main/util/getXPubKey.js +26 -0
  15. package/dist/main/util/getXPubKey.js.map +1 -0
  16. package/dist/main/util/index.d.ts +5 -3
  17. package/dist/main/util/index.js +13 -6
  18. package/dist/main/util/index.js.map +1 -1
  19. package/dist/main/wallet/Base.d.ts +3 -1
  20. package/dist/main/wallet/Base.js +3 -1
  21. package/dist/main/wallet/Base.js.map +1 -1
  22. package/dist/main/wallet/Wif.d.ts +6 -0
  23. package/dist/main/wallet/Wif.js +92 -7
  24. package/dist/main/wallet/Wif.js.map +1 -1
  25. package/dist/main/wallet/interface.d.ts +5 -0
  26. package/dist/main/wallet/model.d.ts +16 -0
  27. package/dist/main/wallet/model.js +18 -1
  28. package/dist/main/wallet/model.js.map +1 -1
  29. package/dist/mainnet-0.4.31.js +2 -0
  30. package/dist/{mainnet-0.4.27.js.LICENSE.txt → mainnet-0.4.31.js.LICENSE.txt} +0 -0
  31. package/dist/module/constant.d.ts +1 -0
  32. package/dist/module/constant.js +16 -0
  33. package/dist/module/constant.js.map +1 -1
  34. package/dist/module/util/balanceObjectFromSatoshi.js +2 -1
  35. package/dist/module/util/balanceObjectFromSatoshi.js.map +1 -1
  36. package/dist/module/util/floor.d.ts +1 -0
  37. package/dist/module/util/floor.js +5 -0
  38. package/dist/module/util/floor.js.map +1 -0
  39. package/dist/module/util/getAddrsByXpubKey.d.ts +22 -0
  40. package/dist/module/util/getAddrsByXpubKey.js +71 -0
  41. package/dist/module/util/getAddrsByXpubKey.js.map +1 -0
  42. package/dist/module/util/getXPubKey.d.ts +1 -0
  43. package/dist/module/util/getXPubKey.js +22 -0
  44. package/dist/module/util/getXPubKey.js.map +1 -0
  45. package/dist/module/util/index.d.ts +5 -3
  46. package/dist/module/util/index.js +5 -3
  47. package/dist/module/util/index.js.map +1 -1
  48. package/dist/module/wallet/Base.d.ts +3 -1
  49. package/dist/module/wallet/Base.js +3 -1
  50. package/dist/module/wallet/Base.js.map +1 -1
  51. package/dist/module/wallet/Wif.d.ts +6 -0
  52. package/dist/module/wallet/Wif.js +95 -10
  53. package/dist/module/wallet/Wif.js.map +1 -1
  54. package/dist/module/wallet/interface.d.ts +5 -0
  55. package/dist/module/wallet/model.d.ts +16 -0
  56. package/dist/module/wallet/model.js +16 -0
  57. package/dist/module/wallet/model.js.map +1 -1
  58. package/dist/tsconfig.browser.tsbuildinfo +1 -1
  59. package/dist/tsconfig.tsbuildinfo +1 -1
  60. package/package.json +1 -1
  61. package/src/constant.ts +17 -0
  62. package/src/util/balanceObjectFromSatoshi.ts +2 -1
  63. package/src/util/floor.test.ts +21 -0
  64. package/src/util/floor.ts +4 -0
  65. package/src/util/getAddrsByXpubKey.test.ts +115 -0
  66. package/src/util/getAddrsByXpubKey.ts +98 -0
  67. package/src/util/getXPubKey.ts +36 -0
  68. package/src/util/index.ts +10 -3
  69. package/src/wallet/Base.ts +4 -1
  70. package/src/wallet/Wif.test.ts +197 -2
  71. package/src/wallet/Wif.ts +123 -18
  72. package/src/wallet/createWallet.test.ts +2 -0
  73. package/src/wallet/interface.ts +6 -1
  74. package/src/wallet/model.ts +22 -0
  75. package/dist/mainnet-0.4.27.js +0 -2
package/src/util/index.ts CHANGED
@@ -1,16 +1,23 @@
1
1
  export { amountInSatoshi } from "./amountInSatoshi";
2
2
  export { asSendRequestObject } from "./asSendRequestObject";
3
- export { btoa, atob } from "./base64";
3
+ export { atob, btoa } from "./base64";
4
4
  export { convert, convertObject } from "./convert";
5
5
  export { delay } from "./delay";
6
+ export { derivedNetwork } from "./deriveNetwork";
7
+ export { derivePublicKeyHash } from "./derivePublicKeyHash";
8
+ export {
9
+ getAddrsByXpubKey,
10
+ getAddrsByXpubKeyObject,
11
+ getXpubKeyInfo,
12
+ getXpubKeyInfoObject,
13
+ } from "../util/getAddrsByXpubKey";
6
14
  export { getRuntimePlatform, RuntimePlatform } from "./getRuntimePlatform";
7
15
  export { getUsdRate } from "./getUsdRate";
8
16
  export { ExchangeRate } from "../rate/ExchangeRate";
9
- export { derivedNetwork } from "./deriveNetwork";
10
- export { derivePublicKeyHash } from "./derivePublicKeyHash";
11
17
  export { sanitizeAddress } from "./sanitizeAddress";
12
18
  export { sanitizeUnit } from "./sanitizeUnit";
13
19
  export { getRandomInt } from "./randomInt";
20
+ export { getXPubKey } from "../util/getXPubKey";
14
21
  import * as randomValues from "./randomValues";
15
22
  export { randomValues };
16
23
  export { sumUtxoValue } from "./sumUtxoValue";
@@ -14,6 +14,8 @@ import { WalletTypeEnum } from "./enum";
14
14
  export class BaseWallet implements WalletI {
15
15
  provider?: any;
16
16
  derivationPath: string = "m/44'/0'/0'/0/0";
17
+ parentDerivationPath: string = "m/44'/0'/0'";
18
+ parentXPubKey?: string;
17
19
  mnemonic?: string;
18
20
  address?: string;
19
21
  privateKey?: any;
@@ -69,6 +71,7 @@ export class BaseWallet implements WalletI {
69
71
  return {
70
72
  seed: this.mnemonic,
71
73
  derivationPath: this.derivationPath,
74
+ parentDerivationPath: this.parentDerivationPath,
72
75
  };
73
76
  }
74
77
  //#endregion Accessors
@@ -182,7 +185,7 @@ export class BaseWallet implements WalletI {
182
185
  /**
183
186
  * replaceNamed - replace (recover) named wallet with a new walletId
184
187
  *
185
- * If wallet with a provided name does not exist yet, it will be creted with a `walletId` supplied
188
+ * If wallet with a provided name does not exist yet, it will be created with a `walletId` supplied
186
189
  * If wallet exists it will be overwritten without exception
187
190
  *
188
191
  * @param name user friendly wallet alias
@@ -3,7 +3,7 @@ import { bchParam } from "../chain";
3
3
  import { BalanceResponse } from "../util/balanceObjectFromSatoshi";
4
4
  import { UnitEnum } from "../enum";
5
5
  import { initProviders, disconnectProviders } from "../network/Connection";
6
- import { DUST_UTXO_THRESHOLD as DUST } from "../constant";
6
+ import { DERIVATION_PATHS, DUST_UTXO_THRESHOLD as DUST } from "../constant";
7
7
  import { delay } from "../util/delay";
8
8
  import { OpReturnData, SendResponse } from "./model";
9
9
  import { ElectrumRawTransaction } from "../network/interface";
@@ -183,6 +183,9 @@ describe(`Mnemonic wallet creation`, () => {
183
183
  "04aaeb52dd7494c361049de67cc680e83ebcbbbdbeb13637d92cd845f70308af5e9370164133294e5fd1679672fe7866c307daf97281a28f66dca7cbb52919824f",
184
184
  publicKeyHash: "d986ed01b7a22225a70edbf2ba7cfb63a15cb3aa",
185
185
 
186
+ parentXPubKey:
187
+ "xpub6BosfCnifzxcFwrSzQiqu2DBVTshkCXacvNsWGYJVVhhawA7d4R5WSWGFNbi8Aw6ZRc1brxMyWMzG3DSSSSoekkudhUd9yLb6qx39T9nMdj",
188
+ parentDerivationPath: "m/44'/0'/0'",
186
189
  derivationPath: "m/44'/0'/0'/0/0",
187
190
  seed: "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about",
188
191
 
@@ -208,6 +211,7 @@ describe(`Mnemonic wallet creation`, () => {
208
211
  "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about"
209
212
  );
210
213
  expect(w.getSeed().derivationPath).toBe("m/44'/145'/0'/0/0");
214
+ expect(w.getSeed().parentDerivationPath).toBe("m/44'/145'/0'");
211
215
  });
212
216
  test("Expect '11x abandon about' to have the correct key, seed and path from regtest wallet", async () => {
213
217
  let w = await RegTestWallet.fromId(
@@ -226,6 +230,172 @@ describe(`Mnemonic wallet creation`, () => {
226
230
  });
227
231
  });
228
232
 
233
+ describe(`XPubKey path derivation`, () => {
234
+ test("Expect '11x abandon about' to have the correct xpubs for common derivation paths, seed and path", async () => {
235
+ let w = await Wallet.fromId(
236
+ "seed:mainnet:abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about"
237
+ );
238
+ let commonPaths = await w.deriveHdPaths(DERIVATION_PATHS);
239
+ expect(commonPaths).toStrictEqual([
240
+ {
241
+ path: "m/0",
242
+ xPubKey:
243
+ "xpub68jrRzQfUmwSaf5Y37Yd5uwfnMRxiR14M3HBonDr91GB7GKEh7R9Mvu2UeCtbASfXZ9FdNo9FwFx6a37HNXUDiXVQFXuadXmevRBa3y7rL8",
244
+ },
245
+ {
246
+ path: "m/0'",
247
+ xPubKey:
248
+ "xpub68jrRzQopSUQm76hJ6TNtiJMJfhj38u1X12xCzExrw388hcN443UVnYpswdUkV7vPJ3KayiCdp3Q5E23s4wvkucohVTh7eSstJdBFyn2DMx",
249
+ },
250
+ {
251
+ path: "m/0'/0",
252
+ xPubKey:
253
+ "xpub6A7PsGUCo9qsn1jhZVB68WKWU9bTt1Wu7fzRqhczRbJ3u3xsF1bJmWBL1MvygTtrfmvNw1adLzmRjQHtDCJDXAHFa4K3wELpGGqEXL4e6d4",
254
+ },
255
+ {
256
+ path: "m/0'/0'",
257
+ xPubKey:
258
+ "xpub6A7PsGUM8pNqwy9AceVuFK6KxY88FhvFMRGP9fjEDKA3P4WpR1zyHH3Lmczj7eorx4RbDC4Qttd8C7HhLA2W9LsxxZzXo1DMCwJFb3zZKZ8",
259
+ },
260
+ {
261
+ path: "m/0'/0'/0'",
262
+ xPubKey:
263
+ "xpub6BiChRN7aqq51RA7RnAmKhqKdGckPncrHWLrj1xoj6ZMfdMJ1dX4Ysh9V3yEhpFCpC3BapjR83xPKY693XXTEU6qgWU3qZs78WBHA15uhYf",
264
+ },
265
+ {
266
+ path: "m/44'/0'/0'",
267
+ xPubKey:
268
+ "xpub6BosfCnifzxcFwrSzQiqu2DBVTshkCXacvNsWGYJVVhhawA7d4R5WSWGFNbi8Aw6ZRc1brxMyWMzG3DSSSSoekkudhUd9yLb6qx39T9nMdj",
269
+ },
270
+ {
271
+ path: "m/44'/0'/0'/0",
272
+ xPubKey:
273
+ "xpub6ELHKXNimKbxMCytPh7EdC2QXx46T9qLDJWGnTraz1H9kMMFdcduoU69wh9cxP12wDxqAAfbaESWGYt5rREsX1J8iR2TEunvzvddduAPYcY",
274
+ },
275
+ {
276
+ path: "m/44'/145'/0'",
277
+ xPubKey:
278
+ "xpub6ByHsPNSQXTWZ7PLESMY2FufyYWtLXagSUpMQq7Un96SiThZH2iJB1X7pwviH1WtKVeDP6K8d6xxFzzoaFzF3s8BKCZx8oEDdDkNnp4owAZ",
279
+ },
280
+ {
281
+ path: "m/44'/145'/0'/0",
282
+ xPubKey:
283
+ "xpub6F2iaK2JUPcgrZ6RTGH6t8VybLPu1XzfrHsDsaKvK6NfULznU6i6aw6ZoefDW2DpNruSLw73RwQg46qvpqB3eryeJJ2tkFCF4Z6gbr8Pjja",
284
+ },
285
+ {
286
+ path: "m/44'/245'/0",
287
+ xPubKey:
288
+ "xpub6Ch34ms5osevEtkEZX81n8EG4c6vgHWGH1gQXBG2uf2Tihb1eed4H1wozLfZB31mV9JD7mymYTQxcLKFFjZHdM5NGdH2Ud1ksSkfwSFjjCg",
289
+ },
290
+ {
291
+ path: "m/44'/245'/0'",
292
+ xPubKey:
293
+ "xpub6Ch34msE9YBtQV7pZrLyRXHwocrpJtNN4KDG8bbyxyhGmEM5MirtqkiH4h9dvnVJ3MekET3w2Fkvej3fyo8WLz9bRPyDynDf6NXNfuydhv1",
294
+ },
295
+ {
296
+ path: "m/44'/245'/0'/0",
297
+ xPubKey:
298
+ "xpub6FFeETss5Zwkw78NDAibKEaGxigU3bgYzLihcqbQqqTyb6jorR9mgR9AexYydxmiPU8koAf5ndaQPjPWK3sDz1wjBjf2TkLbD982S9PWd9Z",
299
+ },
300
+ ]);
301
+ });
302
+ test("Expect '11x abandon about' to return 'protected' for root path", async () => {
303
+ expect.assertions(1);
304
+ try {
305
+ let w = await Wallet.fromId(
306
+ "seed:mainnet:abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about"
307
+ );
308
+ let commonPaths = await w.deriveHdPaths(["m"]);
309
+ } catch (e: any) {
310
+ expect(e.message).toBe(
311
+ "Storing or sharing of parent public key may lead to loss of funds. Storing or sharing *root* parent public keys is strongly discouraged, although all parent keys have risk. See: https://github.com/bitcoin/bips/blob/master/bip-0032.mediawiki#implications"
312
+ );
313
+ }
314
+ });
315
+
316
+ test("Expect '11x abandon about' to have the correct xpubs for common derivation paths, seed and path", async () => {
317
+ let w = await Wallet.fromId(
318
+ "seed:mainnet:abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about"
319
+ );
320
+ let commonPaths = await w.getXPubKeys();
321
+ expect(commonPaths).toStrictEqual([
322
+ {
323
+ path: "m/0",
324
+ xPubKey:
325
+ "xpub68jrRzQfUmwSaf5Y37Yd5uwfnMRxiR14M3HBonDr91GB7GKEh7R9Mvu2UeCtbASfXZ9FdNo9FwFx6a37HNXUDiXVQFXuadXmevRBa3y7rL8",
326
+ },
327
+ {
328
+ path: "m/0'",
329
+ xPubKey:
330
+ "xpub68jrRzQopSUQm76hJ6TNtiJMJfhj38u1X12xCzExrw388hcN443UVnYpswdUkV7vPJ3KayiCdp3Q5E23s4wvkucohVTh7eSstJdBFyn2DMx",
331
+ },
332
+ {
333
+ path: "m/0'/0",
334
+ xPubKey:
335
+ "xpub6A7PsGUCo9qsn1jhZVB68WKWU9bTt1Wu7fzRqhczRbJ3u3xsF1bJmWBL1MvygTtrfmvNw1adLzmRjQHtDCJDXAHFa4K3wELpGGqEXL4e6d4",
336
+ },
337
+ {
338
+ path: "m/0'/0'",
339
+ xPubKey:
340
+ "xpub6A7PsGUM8pNqwy9AceVuFK6KxY88FhvFMRGP9fjEDKA3P4WpR1zyHH3Lmczj7eorx4RbDC4Qttd8C7HhLA2W9LsxxZzXo1DMCwJFb3zZKZ8",
341
+ },
342
+ {
343
+ path: "m/0'/0'/0'",
344
+ xPubKey:
345
+ "xpub6BiChRN7aqq51RA7RnAmKhqKdGckPncrHWLrj1xoj6ZMfdMJ1dX4Ysh9V3yEhpFCpC3BapjR83xPKY693XXTEU6qgWU3qZs78WBHA15uhYf",
346
+ },
347
+ {
348
+ path: "m/44'/0'/0'",
349
+ xPubKey:
350
+ "xpub6BosfCnifzxcFwrSzQiqu2DBVTshkCXacvNsWGYJVVhhawA7d4R5WSWGFNbi8Aw6ZRc1brxMyWMzG3DSSSSoekkudhUd9yLb6qx39T9nMdj",
351
+ },
352
+ {
353
+ path: "m/44'/0'/0'/0",
354
+ xPubKey:
355
+ "xpub6ELHKXNimKbxMCytPh7EdC2QXx46T9qLDJWGnTraz1H9kMMFdcduoU69wh9cxP12wDxqAAfbaESWGYt5rREsX1J8iR2TEunvzvddduAPYcY",
356
+ },
357
+ {
358
+ path: "m/44'/145'/0'",
359
+ xPubKey:
360
+ "xpub6ByHsPNSQXTWZ7PLESMY2FufyYWtLXagSUpMQq7Un96SiThZH2iJB1X7pwviH1WtKVeDP6K8d6xxFzzoaFzF3s8BKCZx8oEDdDkNnp4owAZ",
361
+ },
362
+ {
363
+ path: "m/44'/145'/0'/0",
364
+ xPubKey:
365
+ "xpub6F2iaK2JUPcgrZ6RTGH6t8VybLPu1XzfrHsDsaKvK6NfULznU6i6aw6ZoefDW2DpNruSLw73RwQg46qvpqB3eryeJJ2tkFCF4Z6gbr8Pjja",
366
+ },
367
+ {
368
+ path: "m/44'/245'/0",
369
+ xPubKey:
370
+ "xpub6Ch34ms5osevEtkEZX81n8EG4c6vgHWGH1gQXBG2uf2Tihb1eed4H1wozLfZB31mV9JD7mymYTQxcLKFFjZHdM5NGdH2Ud1ksSkfwSFjjCg",
371
+ },
372
+ {
373
+ path: "m/44'/245'/0'",
374
+ xPubKey:
375
+ "xpub6Ch34msE9YBtQV7pZrLyRXHwocrpJtNN4KDG8bbyxyhGmEM5MirtqkiH4h9dvnVJ3MekET3w2Fkvej3fyo8WLz9bRPyDynDf6NXNfuydhv1",
376
+ },
377
+ {
378
+ path: "m/44'/245'/0'/0",
379
+ xPubKey:
380
+ "xpub6FFeETss5Zwkw78NDAibKEaGxigU3bgYzLihcqbQqqTyb6jorR9mgR9AexYydxmiPU8koAf5ndaQPjPWK3sDz1wjBjf2TkLbD982S9PWd9Z",
381
+ },
382
+ ]);
383
+ });
384
+ test("Expect '11x abandon about' to return 'protected' for root path", async () => {
385
+ expect.assertions(1);
386
+ try {
387
+ let w = await Wallet.fromId(
388
+ "seed:mainnet:abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about"
389
+ );
390
+ let commonPaths = await w.getXPubKeys(["m"]);
391
+ } catch (e: any) {
392
+ expect(e.message).toBe(
393
+ "Storing or sharing of parent public key may lead to loss of funds. Storing or sharing *root* parent public keys is strongly discouraged, although all parent keys have risk. See: https://github.com/bitcoin/bips/blob/master/bip-0032.mediawiki#implications"
394
+ );
395
+ }
396
+ });
397
+ });
398
+
229
399
  describe(`Watch only Wallets`, () => {
230
400
  test("Create a watch only testnet wallet from string id", async () => {
231
401
  let w = await TestNetWallet.fromId(
@@ -242,6 +412,8 @@ describe(`Watch only Wallets`, () => {
242
412
  isTestnet: true,
243
413
  name: "",
244
414
  network: "testnet",
415
+ parentDerivationPath: undefined,
416
+ parentXPubKey: undefined,
245
417
  privateKey: undefined,
246
418
  privateKeyWif: undefined,
247
419
  publicKey: undefined,
@@ -311,7 +483,6 @@ describe(`Watch only Wallets`, () => {
311
483
  let aliceBalance = await alice.send([
312
484
  { cashaddr: alice.cashaddr!, value: 526, unit: "sat" },
313
485
  ]);
314
- expect(aliceBalance.explorerUrl!).toContain("explorer.bitcoin.com");
315
486
  expect(aliceBalance.balance!.sat!).toBeGreaterThan(5000);
316
487
  }
317
488
  });
@@ -881,4 +1052,28 @@ describe(`Wallet extrema behavior regression testing`, () => {
881
1052
  0x6a, 0x4c, 0x00,
882
1053
  ]);
883
1054
  });
1055
+
1056
+ test("Test slpSemiAware", async () => {
1057
+ const alice = await RegTestWallet.fromId(process.env.ALICE_ID!);
1058
+ const bob = await RegTestWallet.newRandom();
1059
+ await alice.send([
1060
+ { cashaddr: bob.getDepositAddress(), unit: UnitEnum.SAT, value: 546 },
1061
+ { cashaddr: bob.getDepositAddress(), unit: UnitEnum.SAT, value: 1000 },
1062
+ ]);
1063
+ expect(await bob.getBalance("sat")).toBe(1546);
1064
+ bob.slpSemiAware();
1065
+ expect(await bob.getBalance("sat")).toBe(1000);
1066
+
1067
+ expect(
1068
+ (await bob.getMaxAmountToSend({ options: { slpSemiAware: true } })).sat
1069
+ ).toBe(780);
1070
+ await bob.sendMax(alice.getDepositAddress());
1071
+ expect(await bob.getBalance("sat")).toBe(0);
1072
+
1073
+ bob.slpSemiAware(false);
1074
+ expect(await bob.getBalance("sat")).toBe(546);
1075
+ expect(
1076
+ (await bob.getMaxAmountToSend({ options: { slpSemiAware: false } })).sat
1077
+ ).toBeLessThanOrEqual(546);
1078
+ });
884
1079
  });
package/src/wallet/Wif.ts CHANGED
@@ -1,11 +1,18 @@
1
1
  //#region Imports
2
2
  // Stable
3
- import { instantiateSecp256k1, instantiateSha256 } from "@bitauth/libauth";
3
+ import {
4
+ deriveHdPublicNodeIdentifier,
5
+ encodeHdPublicKey,
6
+ HdKeyNetwork,
7
+ instantiateSecp256k1,
8
+ instantiateSha256,
9
+ } from "@bitauth/libauth";
4
10
 
5
11
  // Unstable?
6
12
  import {
7
13
  binToHex,
8
14
  CashAddressNetworkPrefix,
15
+ deriveHdPublicNode,
9
16
  decodePrivateKeyWif,
10
17
  encodePrivateKeyWif,
11
18
  deriveHdPrivateNodeFromSeed,
@@ -40,6 +47,7 @@ import {
40
47
  SendResponse,
41
48
  UtxoItem,
42
49
  UtxoResponse,
50
+ XPubKey,
43
51
  } from "./model";
44
52
 
45
53
  import {
@@ -92,6 +100,8 @@ import { generateRandomBytes } from "../util/randomBytes";
92
100
  import { SignedMessageI, SignedMessage } from "../message";
93
101
  import ElectrumNetworkProvider from "../network/ElectrumNetworkProvider";
94
102
  import { amountInSatoshi } from "../util/amountInSatoshi";
103
+ import { getXPubKey } from "../util/getXPubKey";
104
+ import { DERIVATION_PATHS, DUST_UTXO_THRESHOLD } from "../constant";
95
105
 
96
106
  //#endregion Imports
97
107
 
@@ -104,6 +114,8 @@ const sha256Promise = instantiateSha256();
104
114
  export class Wallet extends BaseWallet {
105
115
  cashaddr?: string;
106
116
  derivationPath: string = "m/44'/0'/0'/0/0";
117
+ parentDerivationPath: string = "m/44'/0'/0'";
118
+ parentXPubKey?: string;
107
119
  privateKey?: Uint8Array;
108
120
  publicKeyCompressed?: Uint8Array;
109
121
  privateKeyWif?: string;
@@ -111,7 +123,8 @@ export class Wallet extends BaseWallet {
111
123
  publicKeyHash?: Uint8Array;
112
124
  networkPrefix: CashAddressNetworkPrefix;
113
125
  _slp?: Slp;
114
- _slpAware: boolean = false;
126
+ _slpAware: boolean = false; // a flag which activates utxo checking against an external slp indexer
127
+ _slpSemiAware: boolean = false; // a flag which requires an utxo to have more than 546 sats to be spendable and counted in the balance
115
128
  _util?: Util;
116
129
  static signedMessage: SignedMessageI = new SignedMessage();
117
130
 
@@ -150,6 +163,11 @@ export class Wallet extends BaseWallet {
150
163
  return this;
151
164
  }
152
165
 
166
+ public slpSemiAware(value: boolean = true): Wallet {
167
+ this._slpSemiAware = value;
168
+ return this;
169
+ }
170
+
153
171
  public getNetworkProvider(network: Network = Network.MAINNET) {
154
172
  return getNetworkProvider(network);
155
173
  }
@@ -162,8 +180,8 @@ export class Wallet extends BaseWallet {
162
180
  */
163
181
  public explorerUrl(txId: string) {
164
182
  const explorerUrlMap = {
165
- mainnet: "https://explorer.bitcoin.com/bch/tx/",
166
- testnet: "https://explorer.bitcoin.com/tbch/tx/",
183
+ mainnet: "https://blockchair.com/bitcoin-cash/transaction/",
184
+ testnet: "https://www.blockchain.com/bch-testnet/tx/",
167
185
  regtest: "",
168
186
  };
169
187
 
@@ -179,6 +197,10 @@ export class Wallet extends BaseWallet {
179
197
  network: this.network as any,
180
198
  seed: this.mnemonic ? this.getSeed().seed : undefined,
181
199
  derivationPath: this.mnemonic ? this.getSeed().derivationPath : undefined,
200
+ parentDerivationPath: this.mnemonic
201
+ ? this.getSeed().parentDerivationPath
202
+ : undefined,
203
+ parentXPubKey: this.parentXPubKey ? this.parentXPubKey : undefined,
182
204
  publicKey: this.publicKey ? binToHex(this.publicKey!) : undefined,
183
205
  publicKeyHash: binToHex(this.publicKeyHash!),
184
206
  privateKey: this.privateKey ? binToHex(this.privateKey!) : undefined,
@@ -312,19 +334,25 @@ export class Wallet extends BaseWallet {
312
334
  }
313
335
 
314
336
  private async _generateMnemonic() {
315
- const crypto = await instantiateBIP32Crypto();
316
337
  this.mnemonic = generateMnemonic();
317
338
  let seed = mnemonicToSeedSync(this.mnemonic!);
339
+ let network = this.isTestnet ? "testnet" : "mainnet";
340
+ this.parentXPubKey = await getXPubKey(
341
+ seed,
342
+ this.parentDerivationPath,
343
+ network
344
+ );
345
+
346
+ const crypto = await instantiateBIP32Crypto();
318
347
  let hdNode = deriveHdPrivateNodeFromSeed(crypto, seed);
319
348
  if (!hdNode.valid) {
320
349
  throw Error("Invalid private key derived from mnemonic seed");
321
350
  }
322
351
 
323
- let zerothChild = deriveHdPath(
324
- crypto,
325
- hdNode,
326
- this.derivationPath
327
- ) as HdPrivateNodeValid;
352
+ let zerothChild = deriveHdPath(crypto, hdNode, this.derivationPath);
353
+ if (typeof zerothChild === "string") {
354
+ throw Error(zerothChild);
355
+ }
328
356
  this.privateKey = zerothChild.privateKey;
329
357
 
330
358
  this.walletType = WalletTypeEnum.Seed;
@@ -346,6 +374,18 @@ export class Wallet extends BaseWallet {
346
374
  return super.fromId(walletId);
347
375
  };
348
376
 
377
+ public async getXPubKeys(paths?) {
378
+ if (this.mnemonic) {
379
+ if (paths) {
380
+ let xPubKeys = await this.deriveHdPaths(paths);
381
+ return [xPubKeys];
382
+ } else {
383
+ return await this.deriveHdPaths(DERIVATION_PATHS);
384
+ }
385
+ } else {
386
+ throw Error("xpubkeys can only be derived from seed type wallets.");
387
+ }
388
+ }
349
389
  // Initialize wallet from a mnemonic phrase
350
390
  protected async fromSeed(
351
391
  mnemonic: string,
@@ -362,20 +402,73 @@ export class Wallet extends BaseWallet {
362
402
  }
363
403
  if (derivationPath) {
364
404
  this.derivationPath = derivationPath;
405
+
406
+ // If the derivation path is for the first account child, set the parent derivation path
407
+ let path = derivationPath.split("/");
408
+ if (path.slice(-2).join("/") == "0/0") {
409
+ this.parentDerivationPath = path.slice(0, -2).join("/");
410
+ }
365
411
  }
366
412
 
367
- let zerothChild = deriveHdPath(
368
- crypto,
369
- hdNode,
370
- this.derivationPath
371
- ) as HdPrivateNodeValid;
413
+ let zerothChild = deriveHdPath(crypto, hdNode, this.derivationPath);
414
+ if (typeof zerothChild === "string") {
415
+ throw Error(zerothChild);
416
+ }
372
417
  this.privateKey = zerothChild.privateKey;
373
418
 
419
+ let network = this.isTestnet ? "testnet" : "mainnet";
420
+ this.parentXPubKey = await getXPubKey(
421
+ seed,
422
+ this.parentDerivationPath,
423
+ network
424
+ );
425
+
374
426
  this.walletType = WalletTypeEnum.Seed;
375
427
  await this.deriveInfo();
376
428
  return this;
377
429
  }
378
430
 
431
+ // Get common xpub paths from zerothChild privateKey
432
+ public async deriveHdPaths(hdPaths: string[]): Promise<any[]> {
433
+ const crypto = await instantiateBIP32Crypto();
434
+ let seed = mnemonicToSeedSync(this.mnemonic!);
435
+ let hdNode = deriveHdPrivateNodeFromSeed(crypto, seed);
436
+ if (!hdNode.valid) {
437
+ throw Error("Invalid private key derived from mnemonic seed");
438
+ }
439
+
440
+ let result: any[] = [];
441
+
442
+ for (const path of hdPaths) {
443
+ if (path === "m") {
444
+ throw Error(
445
+ "Storing or sharing of parent public key may lead to loss of funds. Storing or sharing *root* parent public keys is strongly discouraged, although all parent keys have risk. See: https://github.com/bitcoin/bips/blob/master/bip-0032.mediawiki#implications"
446
+ );
447
+ }
448
+ let childNode = deriveHdPath(crypto, hdNode, path);
449
+ if (typeof childNode === "string") {
450
+ throw Error(childNode);
451
+ }
452
+ let node = deriveHdPublicNode(crypto, childNode);
453
+ if (typeof node === "string") {
454
+ throw Error(node);
455
+ }
456
+ let xPubKey = encodeHdPublicKey(crypto, {
457
+ network: this.network as HdKeyNetwork,
458
+ node: node,
459
+ });
460
+ let key = new XPubKey({
461
+ path: path,
462
+ xPubKey: xPubKey,
463
+ });
464
+
465
+ result.push(await key.ready());
466
+ }
467
+ return await Promise.all(result).then((result) => {
468
+ return result;
469
+ });
470
+ }
471
+
379
472
  // Initialize a watch only wallet from a cash addr
380
473
  protected async watchOnly(address: string): Promise<this> {
381
474
  this.walletType = WalletTypeEnum.Watch;
@@ -410,8 +503,7 @@ export class Wallet extends BaseWallet {
410
503
  const sha256 = await sha256Promise;
411
504
  let wifResult = decodePrivateKeyWif(sha256, secret);
412
505
 
413
- const hasError = typeof wifResult === "string";
414
- if (hasError) {
506
+ if (typeof wifResult === "string") {
415
507
  throw Error(wifResult as string);
416
508
  }
417
509
  let resultData: PrivateKeyI = wifResult as PrivateKeyI;
@@ -498,6 +590,11 @@ export class Wallet extends BaseWallet {
498
590
  (slpOutpoint) => `${bchutxo.txid}:${bchutxo.vout}` === slpOutpoint
499
591
  ) === -1
500
592
  );
593
+ } else if (this._slpSemiAware) {
594
+ const bchUtxos: UtxoI[] = await this.provider!.getUtxos(address);
595
+ return bchUtxos.filter(
596
+ (bchutxo) => bchutxo.satoshis > DUST_UTXO_THRESHOLD
597
+ );
501
598
  } else {
502
599
  return await this.provider!.getUtxos(address);
503
600
  }
@@ -550,7 +647,7 @@ export class Wallet extends BaseWallet {
550
647
  // Gets balance from fulcrum
551
648
  public async getBalanceFromProvider(): Promise<number> {
552
649
  // TODO not sure why getting the balance from a provider doesn't work
553
- if (this._slpAware) {
650
+ if (this._slpAware || this._slpSemiAware) {
554
651
  return await this.getBalanceFromUtxos();
555
652
  } else {
556
653
  return await this.provider!.getBalance(this.cashaddr!);
@@ -663,6 +760,10 @@ export class Wallet extends BaseWallet {
663
760
  this._slpAware = true;
664
761
  }
665
762
 
763
+ if (params.options && params.options.slpSemiAware) {
764
+ this._slpSemiAware = true;
765
+ }
766
+
666
767
  // get inputs
667
768
  let utxos: UtxoI[];
668
769
  if (params.options && params.options.utxoIds) {
@@ -927,6 +1028,10 @@ export class Wallet extends BaseWallet {
927
1028
  this._slpAware = true;
928
1029
  }
929
1030
 
1031
+ if (options && options.slpSemiAware) {
1032
+ this._slpSemiAware = true;
1033
+ }
1034
+
930
1035
  // get inputs from options or query all inputs
931
1036
  let utxos: UtxoI[];
932
1037
  if (options && options.utxoIds) {
@@ -66,6 +66,8 @@ describe(`Named Wallets`, () => {
66
66
  isTestnet: true,
67
67
  name: "",
68
68
  network: "testnet",
69
+ parentDerivationPath: undefined,
70
+ parentXPubKey: undefined,
69
71
  privateKey: undefined,
70
72
  privateKeyWif: undefined,
71
73
  publicKey: undefined,
@@ -23,6 +23,7 @@ export interface WalletResponseI {
23
23
  privkey?: string;
24
24
  seed?: string;
25
25
  derivationPath?: string;
26
+ parentDerivationPath?: string;
26
27
  }
27
28
 
28
29
  export interface WalletInfoI {
@@ -32,6 +33,8 @@ export interface WalletInfoI {
32
33
  network: NetworkEnum;
33
34
  seed?: string;
34
35
  derivationPath?: string;
36
+ parentDerivationPath?: string;
37
+ parentXPubKey?: string;
35
38
  publicKey?: string;
36
39
  publicKeyHash?: string;
37
40
  privateKey?: string;
@@ -43,7 +46,8 @@ export interface WalletInfoI {
43
46
  export interface SendRequestOptionsI {
44
47
  utxoIds?: string[];
45
48
  changeAddress?: string;
46
- slpAware?: boolean;
49
+ slpAware?: boolean; // a flag which activates utxo checking against an external slp indexer
50
+ slpSemiAware?: boolean; // a flag which requires an utxo to have more than 546 sats to be spendable and counted in the balance
47
51
  queryBalance?: boolean;
48
52
  awaitTransactionPropagation?: boolean;
49
53
  }
@@ -51,6 +55,7 @@ export interface SendRequestOptionsI {
51
55
  export interface MnemonicI {
52
56
  seed: string;
53
57
  derivationPath: string;
58
+ parentDerivationPath: string;
54
59
  }
55
60
 
56
61
  export interface WalletI {
@@ -159,3 +159,25 @@ export class SendResponse {
159
159
  this.explorerUrl = explorerUrl;
160
160
  }
161
161
  }
162
+
163
+ export class XPubKey {
164
+ path: string;
165
+ xPubKey: string;
166
+
167
+ constructor({ path, xPubKey }: { path: string; xPubKey: string }) {
168
+ this.path = path;
169
+ this.xPubKey = xPubKey;
170
+ }
171
+
172
+ public async ready() {
173
+ await this.xPubKey;
174
+ return this.asObject();
175
+ }
176
+
177
+ public asObject() {
178
+ return {
179
+ path: this.path,
180
+ xPubKey: this.xPubKey,
181
+ };
182
+ }
183
+ }