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.
- package/dist/index.html +1 -1
- package/dist/main/constant.d.ts +1 -0
- package/dist/main/constant.js +17 -1
- package/dist/main/constant.js.map +1 -1
- package/dist/main/util/balanceObjectFromSatoshi.js +2 -1
- package/dist/main/util/balanceObjectFromSatoshi.js.map +1 -1
- package/dist/main/util/floor.d.ts +1 -0
- package/dist/main/util/floor.js +9 -0
- package/dist/main/util/floor.js.map +1 -0
- package/dist/main/util/getAddrsByXpubKey.d.ts +22 -0
- package/dist/main/util/getAddrsByXpubKey.js +79 -0
- package/dist/main/util/getAddrsByXpubKey.js.map +1 -0
- package/dist/main/util/getXPubKey.d.ts +1 -0
- package/dist/main/util/getXPubKey.js +26 -0
- package/dist/main/util/getXPubKey.js.map +1 -0
- package/dist/main/util/index.d.ts +5 -3
- package/dist/main/util/index.js +13 -6
- package/dist/main/util/index.js.map +1 -1
- package/dist/main/wallet/Base.d.ts +3 -1
- package/dist/main/wallet/Base.js +3 -1
- package/dist/main/wallet/Base.js.map +1 -1
- package/dist/main/wallet/Wif.d.ts +6 -0
- package/dist/main/wallet/Wif.js +92 -7
- package/dist/main/wallet/Wif.js.map +1 -1
- package/dist/main/wallet/interface.d.ts +5 -0
- package/dist/main/wallet/model.d.ts +16 -0
- package/dist/main/wallet/model.js +18 -1
- package/dist/main/wallet/model.js.map +1 -1
- package/dist/mainnet-0.4.31.js +2 -0
- package/dist/{mainnet-0.4.27.js.LICENSE.txt → mainnet-0.4.31.js.LICENSE.txt} +0 -0
- package/dist/module/constant.d.ts +1 -0
- package/dist/module/constant.js +16 -0
- package/dist/module/constant.js.map +1 -1
- package/dist/module/util/balanceObjectFromSatoshi.js +2 -1
- package/dist/module/util/balanceObjectFromSatoshi.js.map +1 -1
- package/dist/module/util/floor.d.ts +1 -0
- package/dist/module/util/floor.js +5 -0
- package/dist/module/util/floor.js.map +1 -0
- package/dist/module/util/getAddrsByXpubKey.d.ts +22 -0
- package/dist/module/util/getAddrsByXpubKey.js +71 -0
- package/dist/module/util/getAddrsByXpubKey.js.map +1 -0
- package/dist/module/util/getXPubKey.d.ts +1 -0
- package/dist/module/util/getXPubKey.js +22 -0
- package/dist/module/util/getXPubKey.js.map +1 -0
- package/dist/module/util/index.d.ts +5 -3
- package/dist/module/util/index.js +5 -3
- package/dist/module/util/index.js.map +1 -1
- package/dist/module/wallet/Base.d.ts +3 -1
- package/dist/module/wallet/Base.js +3 -1
- package/dist/module/wallet/Base.js.map +1 -1
- package/dist/module/wallet/Wif.d.ts +6 -0
- package/dist/module/wallet/Wif.js +95 -10
- package/dist/module/wallet/Wif.js.map +1 -1
- package/dist/module/wallet/interface.d.ts +5 -0
- package/dist/module/wallet/model.d.ts +16 -0
- package/dist/module/wallet/model.js +16 -0
- package/dist/module/wallet/model.js.map +1 -1
- package/dist/tsconfig.browser.tsbuildinfo +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +1 -1
- package/src/constant.ts +17 -0
- package/src/util/balanceObjectFromSatoshi.ts +2 -1
- package/src/util/floor.test.ts +21 -0
- package/src/util/floor.ts +4 -0
- package/src/util/getAddrsByXpubKey.test.ts +115 -0
- package/src/util/getAddrsByXpubKey.ts +98 -0
- package/src/util/getXPubKey.ts +36 -0
- package/src/util/index.ts +10 -3
- package/src/wallet/Base.ts +4 -1
- package/src/wallet/Wif.test.ts +197 -2
- package/src/wallet/Wif.ts +123 -18
- package/src/wallet/createWallet.test.ts +2 -0
- package/src/wallet/interface.ts +6 -1
- package/src/wallet/model.ts +22 -0
- 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 {
|
|
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";
|
package/src/wallet/Base.ts
CHANGED
|
@@ -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
|
|
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
|
package/src/wallet/Wif.test.ts
CHANGED
|
@@ -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 {
|
|
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://
|
|
166
|
-
testnet: "https://
|
|
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
|
-
|
|
325
|
-
|
|
326
|
-
|
|
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
|
-
|
|
369
|
-
|
|
370
|
-
|
|
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
|
-
|
|
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) {
|
package/src/wallet/interface.ts
CHANGED
|
@@ -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 {
|
package/src/wallet/model.ts
CHANGED
|
@@ -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
|
+
}
|