mainnet-js 0.4.28 → 0.4.32
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/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 +33 -10
- package/dist/main/wallet/Wif.js +188 -77
- 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.28.js → mainnet-0.4.32.js} +2 -2
- package/dist/{mainnet-0.4.28.js.LICENSE.txt → mainnet-0.4.32.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/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 +33 -10
- package/dist/module/wallet/Wif.js +191 -80
- 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/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 +215 -2
- package/src/wallet/Wif.ts +284 -130
- package/src/wallet/createWallet.test.ts +2 -0
- package/src/wallet/interface.ts +6 -1
- package/src/wallet/model.ts +22 -0
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) {
|
|
@@ -707,6 +808,8 @@ export class Wallet extends BaseWallet {
|
|
|
707
808
|
|
|
708
809
|
/**
|
|
709
810
|
* send Send some amount to an address
|
|
811
|
+
* this function processes the send requests, encodes the transaction, sends it to the network
|
|
812
|
+
* @returns (depending on the options parameter) the transaction id, new address balance and a link to the transaction on the blockchain explorer
|
|
710
813
|
*
|
|
711
814
|
* This is a first class function with REST analog, maintainers should strive to keep backward-compatibility
|
|
712
815
|
*
|
|
@@ -719,14 +822,24 @@ export class Wallet extends BaseWallet {
|
|
|
719
822
|
| SendRequestArray[],
|
|
720
823
|
options?: SendRequestOptionsI
|
|
721
824
|
): Promise<SendResponse> {
|
|
722
|
-
let
|
|
723
|
-
|
|
724
|
-
sendRequests,
|
|
825
|
+
let encodedTransaction = await this.encodeTransaction(
|
|
826
|
+
requests,
|
|
725
827
|
undefined,
|
|
726
828
|
options
|
|
727
829
|
);
|
|
830
|
+
|
|
831
|
+
const awaitTransactionPropagation =
|
|
832
|
+
!options ||
|
|
833
|
+
options.awaitTransactionPropagation === undefined ||
|
|
834
|
+
options.awaitTransactionPropagation;
|
|
835
|
+
|
|
836
|
+
const txId = await this.submitTransaction(
|
|
837
|
+
encodedTransaction,
|
|
838
|
+
awaitTransactionPropagation
|
|
839
|
+
);
|
|
840
|
+
|
|
728
841
|
let resp = new SendResponse({});
|
|
729
|
-
resp.txId =
|
|
842
|
+
resp.txId = txId;
|
|
730
843
|
const queryBalance =
|
|
731
844
|
!options || options.queryBalance === undefined || options.queryBalance;
|
|
732
845
|
if (queryBalance) {
|
|
@@ -736,6 +849,14 @@ export class Wallet extends BaseWallet {
|
|
|
736
849
|
return resp;
|
|
737
850
|
}
|
|
738
851
|
|
|
852
|
+
/**
|
|
853
|
+
* sendMax Send all available funds to a destination cash address
|
|
854
|
+
*
|
|
855
|
+
* @param {string} cashaddr destination cash address
|
|
856
|
+
* @param {SendRequestOptionsI} options Options of the send requests
|
|
857
|
+
*
|
|
858
|
+
* @returns (depending on the options parameter) the transaction id, new address balance and a link to the transaction on the blockchain explorer
|
|
859
|
+
*/
|
|
739
860
|
public async sendMax(
|
|
740
861
|
cashaddr: string,
|
|
741
862
|
options?: SendRequestOptionsI
|
|
@@ -752,7 +873,18 @@ export class Wallet extends BaseWallet {
|
|
|
752
873
|
};
|
|
753
874
|
}
|
|
754
875
|
|
|
755
|
-
|
|
876
|
+
/**
|
|
877
|
+
* sendMaxRaw (internal) Send all available funds to a destination cash address
|
|
878
|
+
*
|
|
879
|
+
* @param {string} cashaddr destination cash address
|
|
880
|
+
* @param {SendRequestOptionsI} options Options of the send requests
|
|
881
|
+
*
|
|
882
|
+
* @returns the transaction id sent to the network
|
|
883
|
+
*/
|
|
884
|
+
private async sendMaxRaw(
|
|
885
|
+
cashaddr: string,
|
|
886
|
+
options?: SendRequestOptionsI
|
|
887
|
+
): Promise<string> {
|
|
756
888
|
let maxSpendableAmount = await this.getMaxAmountToSend({
|
|
757
889
|
outputCount: 1,
|
|
758
890
|
options: options,
|
|
@@ -765,7 +897,129 @@ export class Wallet extends BaseWallet {
|
|
|
765
897
|
value: maxSpendableAmount.sat,
|
|
766
898
|
unit: "sat",
|
|
767
899
|
});
|
|
768
|
-
|
|
900
|
+
|
|
901
|
+
const encodedTransaction = await this.encodeTransaction(
|
|
902
|
+
[sendRequest],
|
|
903
|
+
true,
|
|
904
|
+
options
|
|
905
|
+
);
|
|
906
|
+
const awaitTransactionPropagation =
|
|
907
|
+
!options ||
|
|
908
|
+
options.awaitTransactionPropagation === undefined ||
|
|
909
|
+
options.awaitTransactionPropagation;
|
|
910
|
+
|
|
911
|
+
const txId = await this.submitTransaction(
|
|
912
|
+
encodedTransaction,
|
|
913
|
+
awaitTransactionPropagation
|
|
914
|
+
);
|
|
915
|
+
|
|
916
|
+
return txId;
|
|
917
|
+
}
|
|
918
|
+
|
|
919
|
+
/**
|
|
920
|
+
* encodeTransaction given a list of sendRequests, options and estimate fees.
|
|
921
|
+
* @param {SendRequest[]} sendRequests SendRequests
|
|
922
|
+
* @param {boolean} discardChange=false
|
|
923
|
+
* @param {SendRequestOptionsI} options Options of the send requests
|
|
924
|
+
*/
|
|
925
|
+
public async encodeTransaction(
|
|
926
|
+
requests:
|
|
927
|
+
| SendRequest
|
|
928
|
+
| OpReturnData
|
|
929
|
+
| Array<SendRequest | OpReturnData>
|
|
930
|
+
| SendRequestArray[],
|
|
931
|
+
discardChange: boolean = false,
|
|
932
|
+
options?: SendRequestOptionsI
|
|
933
|
+
) {
|
|
934
|
+
let sendRequests = asSendRequestObject(requests);
|
|
935
|
+
|
|
936
|
+
if (!this.privateKey) {
|
|
937
|
+
throw new Error(
|
|
938
|
+
`Wallet ${this.name} is missing either a network or private key`
|
|
939
|
+
);
|
|
940
|
+
}
|
|
941
|
+
if (!this.cashaddr) {
|
|
942
|
+
throw Error("attempted to send without a cashaddr");
|
|
943
|
+
}
|
|
944
|
+
|
|
945
|
+
if (options && options.slpAware) {
|
|
946
|
+
this._slpAware = true;
|
|
947
|
+
}
|
|
948
|
+
|
|
949
|
+
if (options && options.slpSemiAware) {
|
|
950
|
+
this._slpSemiAware = true;
|
|
951
|
+
}
|
|
952
|
+
|
|
953
|
+
// get inputs from options or query all inputs
|
|
954
|
+
let utxos: UtxoI[];
|
|
955
|
+
if (options && options.utxoIds) {
|
|
956
|
+
utxos = options.utxoIds.map((utxoId) =>
|
|
957
|
+
UtxoItem.fromId(utxoId).asElectrum()
|
|
958
|
+
);
|
|
959
|
+
} else {
|
|
960
|
+
utxos = await this.getAddressUtxos(this.cashaddr);
|
|
961
|
+
}
|
|
962
|
+
|
|
963
|
+
const bestHeight = await this.provider!.getBlockHeight()!;
|
|
964
|
+
const spendAmount = await sumSendRequestAmounts(sendRequests);
|
|
965
|
+
|
|
966
|
+
if (utxos.length === 0) {
|
|
967
|
+
throw Error("There were no Unspent Outputs");
|
|
968
|
+
}
|
|
969
|
+
if (typeof spendAmount !== "bigint") {
|
|
970
|
+
throw Error("Couldn't get spend amount when building transaction");
|
|
971
|
+
}
|
|
972
|
+
|
|
973
|
+
const relayFeePerByteInSatoshi = await getRelayFeeCache(this.provider!);
|
|
974
|
+
const feeEstimate = await getFeeAmount({
|
|
975
|
+
utxos: utxos,
|
|
976
|
+
sendRequests: sendRequests,
|
|
977
|
+
privateKey: this.privateKey,
|
|
978
|
+
relayFeePerByteInSatoshi: relayFeePerByteInSatoshi,
|
|
979
|
+
slpOutputs: [],
|
|
980
|
+
});
|
|
981
|
+
|
|
982
|
+
const fundingUtxos = await getSuitableUtxos(
|
|
983
|
+
utxos,
|
|
984
|
+
BigInt(spendAmount) + BigInt(feeEstimate),
|
|
985
|
+
bestHeight
|
|
986
|
+
);
|
|
987
|
+
if (fundingUtxos.length === 0) {
|
|
988
|
+
throw Error(
|
|
989
|
+
"The available inputs couldn't satisfy the request with fees"
|
|
990
|
+
);
|
|
991
|
+
}
|
|
992
|
+
const fee = await getFeeAmount({
|
|
993
|
+
utxos: fundingUtxos,
|
|
994
|
+
sendRequests: sendRequests,
|
|
995
|
+
privateKey: this.privateKey,
|
|
996
|
+
relayFeePerByteInSatoshi: relayFeePerByteInSatoshi,
|
|
997
|
+
slpOutputs: [],
|
|
998
|
+
});
|
|
999
|
+
const encodedTransaction = await buildEncodedTransaction(
|
|
1000
|
+
fundingUtxos,
|
|
1001
|
+
sendRequests,
|
|
1002
|
+
this.privateKey,
|
|
1003
|
+
fee,
|
|
1004
|
+
discardChange
|
|
1005
|
+
);
|
|
1006
|
+
|
|
1007
|
+
return encodedTransaction;
|
|
1008
|
+
}
|
|
1009
|
+
|
|
1010
|
+
// Submit a raw transaction
|
|
1011
|
+
public async submitTransaction(
|
|
1012
|
+
transaction: Uint8Array,
|
|
1013
|
+
awaitPropagation: boolean = true
|
|
1014
|
+
): Promise<string> {
|
|
1015
|
+
if (!this.provider) {
|
|
1016
|
+
throw Error("Wallet network provider was not initialized");
|
|
1017
|
+
}
|
|
1018
|
+
let rawTransaction = binToHex(transaction);
|
|
1019
|
+
return await this.provider.sendRawTransaction(
|
|
1020
|
+
rawTransaction,
|
|
1021
|
+
awaitPropagation
|
|
1022
|
+
);
|
|
769
1023
|
}
|
|
770
1024
|
|
|
771
1025
|
// gets transaction history of this wallet
|
|
@@ -776,11 +1030,16 @@ export class Wallet extends BaseWallet {
|
|
|
776
1030
|
// gets last transaction of this wallet
|
|
777
1031
|
public async getLastTransaction(
|
|
778
1032
|
confirmedOnly: boolean = false
|
|
779
|
-
): Promise<ElectrumRawTransaction> {
|
|
1033
|
+
): Promise<ElectrumRawTransaction | null> {
|
|
780
1034
|
let history: TxI[] = await this.getHistory();
|
|
781
1035
|
if (confirmedOnly) {
|
|
782
1036
|
history = history.filter((val) => val.height > 0);
|
|
783
1037
|
}
|
|
1038
|
+
|
|
1039
|
+
if (!history.length) {
|
|
1040
|
+
return null;
|
|
1041
|
+
}
|
|
1042
|
+
|
|
784
1043
|
const [lastTx] = history.slice(-1);
|
|
785
1044
|
return this.provider!.getRawTransactionObject(lastTx.tx_hash);
|
|
786
1045
|
}
|
|
@@ -901,111 +1160,6 @@ export class Wallet extends BaseWallet {
|
|
|
901
1160
|
this.publicKeyHash = derivePublicKeyHash(this.cashaddr!);
|
|
902
1161
|
return this;
|
|
903
1162
|
}
|
|
904
|
-
|
|
905
|
-
/**
|
|
906
|
-
* _processSendRequests given a list of sendRequests, estimate fees, build the transaction and submit it.
|
|
907
|
-
* This function is an internal wrapper and may change.
|
|
908
|
-
* @param {SendRequest[]} sendRequests SendRequests
|
|
909
|
-
* @param {} discardChange=false
|
|
910
|
-
* @param {SendRequestOptionsI} options Options of the send requests
|
|
911
|
-
*/
|
|
912
|
-
private async _processSendRequests(
|
|
913
|
-
sendRequests: Array<SendRequest | OpReturnData>,
|
|
914
|
-
discardChange = false,
|
|
915
|
-
options?: SendRequestOptionsI
|
|
916
|
-
) {
|
|
917
|
-
if (!this.privateKey) {
|
|
918
|
-
throw new Error(
|
|
919
|
-
`Wallet ${this.name} is missing either a network or private key`
|
|
920
|
-
);
|
|
921
|
-
}
|
|
922
|
-
if (!this.cashaddr) {
|
|
923
|
-
throw Error("attempted to send without a cashaddr");
|
|
924
|
-
}
|
|
925
|
-
|
|
926
|
-
if (options && options.slpAware) {
|
|
927
|
-
this._slpAware = true;
|
|
928
|
-
}
|
|
929
|
-
|
|
930
|
-
// get inputs from options or query all inputs
|
|
931
|
-
let utxos: UtxoI[];
|
|
932
|
-
if (options && options.utxoIds) {
|
|
933
|
-
utxos = options.utxoIds.map((utxoId) =>
|
|
934
|
-
UtxoItem.fromId(utxoId).asElectrum()
|
|
935
|
-
);
|
|
936
|
-
} else {
|
|
937
|
-
utxos = await this.getAddressUtxos(this.cashaddr);
|
|
938
|
-
}
|
|
939
|
-
|
|
940
|
-
const bestHeight = await this.provider!.getBlockHeight()!;
|
|
941
|
-
const spendAmount = await sumSendRequestAmounts(sendRequests);
|
|
942
|
-
|
|
943
|
-
if (utxos.length === 0) {
|
|
944
|
-
throw Error("There were no Unspent Outputs");
|
|
945
|
-
}
|
|
946
|
-
if (typeof spendAmount !== "bigint") {
|
|
947
|
-
throw Error("Couldn't get spend amount when building transaction");
|
|
948
|
-
}
|
|
949
|
-
|
|
950
|
-
const relayFeePerByteInSatoshi = await getRelayFeeCache(this.provider!);
|
|
951
|
-
const feeEstimate = await getFeeAmount({
|
|
952
|
-
utxos: utxos,
|
|
953
|
-
sendRequests: sendRequests,
|
|
954
|
-
privateKey: this.privateKey,
|
|
955
|
-
relayFeePerByteInSatoshi: relayFeePerByteInSatoshi,
|
|
956
|
-
slpOutputs: [],
|
|
957
|
-
});
|
|
958
|
-
|
|
959
|
-
const fundingUtxos = await getSuitableUtxos(
|
|
960
|
-
utxos,
|
|
961
|
-
BigInt(spendAmount) + BigInt(feeEstimate),
|
|
962
|
-
bestHeight
|
|
963
|
-
);
|
|
964
|
-
if (fundingUtxos.length === 0) {
|
|
965
|
-
throw Error(
|
|
966
|
-
"The available inputs couldn't satisfy the request with fees"
|
|
967
|
-
);
|
|
968
|
-
}
|
|
969
|
-
const fee = await getFeeAmount({
|
|
970
|
-
utxos: fundingUtxos,
|
|
971
|
-
sendRequests: sendRequests,
|
|
972
|
-
privateKey: this.privateKey,
|
|
973
|
-
relayFeePerByteInSatoshi: relayFeePerByteInSatoshi,
|
|
974
|
-
slpOutputs: [],
|
|
975
|
-
});
|
|
976
|
-
const encodedTransaction = await buildEncodedTransaction(
|
|
977
|
-
fundingUtxos,
|
|
978
|
-
sendRequests,
|
|
979
|
-
this.privateKey,
|
|
980
|
-
fee,
|
|
981
|
-
discardChange
|
|
982
|
-
);
|
|
983
|
-
|
|
984
|
-
const awaitTransactionPropagation =
|
|
985
|
-
!options ||
|
|
986
|
-
options.awaitTransactionPropagation === undefined ||
|
|
987
|
-
options.awaitTransactionPropagation;
|
|
988
|
-
|
|
989
|
-
return await this._submitTransaction(
|
|
990
|
-
encodedTransaction,
|
|
991
|
-
awaitTransactionPropagation
|
|
992
|
-
);
|
|
993
|
-
}
|
|
994
|
-
|
|
995
|
-
// Submit a raw transaction
|
|
996
|
-
private async _submitTransaction(
|
|
997
|
-
transaction: Uint8Array,
|
|
998
|
-
awaitPropagation: boolean = true
|
|
999
|
-
): Promise<string> {
|
|
1000
|
-
if (!this.provider) {
|
|
1001
|
-
throw Error("Wallet network provider was not initialized");
|
|
1002
|
-
}
|
|
1003
|
-
let rawTransaction = binToHex(transaction);
|
|
1004
|
-
return await this.provider.sendRawTransaction(
|
|
1005
|
-
rawTransaction,
|
|
1006
|
-
awaitPropagation
|
|
1007
|
-
);
|
|
1008
|
-
}
|
|
1009
1163
|
//#endregion Private implementation details
|
|
1010
1164
|
|
|
1011
1165
|
//#region Signing
|
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
|
+
}
|