mainnet-js 2.6.6 → 2.7.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.
- package/dist/index.html +1 -1
- package/dist/{mainnet-2.6.6.js → mainnet-2.7.0.js} +446 -156
- package/dist/module/cli.js +0 -4
- package/dist/module/cli.js.map +1 -1
- package/dist/module/index.d.ts +1 -3
- package/dist/module/index.d.ts.map +1 -1
- package/dist/module/index.js +1 -3
- package/dist/module/index.js.map +1 -1
- package/dist/module/network/ElectrumNetworkProvider.d.ts +21 -27
- package/dist/module/network/ElectrumNetworkProvider.d.ts.map +1 -1
- package/dist/module/network/ElectrumNetworkProvider.js +92 -102
- package/dist/module/network/ElectrumNetworkProvider.js.map +1 -1
- package/dist/module/network/NetworkProvider.d.ts +9 -22
- package/dist/module/network/NetworkProvider.d.ts.map +1 -1
- package/dist/module/network/constant.d.ts +0 -21
- package/dist/module/network/constant.d.ts.map +1 -1
- package/dist/module/network/constant.js +0 -21
- package/dist/module/network/constant.js.map +1 -1
- package/dist/module/network/default.d.ts +2 -2
- package/dist/module/network/default.d.ts.map +1 -1
- package/dist/module/network/default.js +15 -45
- package/dist/module/network/default.js.map +1 -1
- package/dist/module/network/interface.d.ts +2 -8
- package/dist/module/network/interface.d.ts.map +1 -1
- package/dist/module/network/util.d.ts.map +1 -1
- package/dist/module/network/util.js +4 -5
- package/dist/module/network/util.js.map +1 -1
- package/dist/module/rate/ExchangeRate.js +2 -1
- package/dist/module/rate/ExchangeRate.js.map +1 -1
- package/dist/module/transaction/Wif.d.ts.map +1 -1
- package/dist/module/transaction/Wif.js +1 -1
- package/dist/module/transaction/Wif.js.map +1 -1
- package/dist/module/wallet/Base.d.ts +282 -88
- package/dist/module/wallet/Base.d.ts.map +1 -1
- package/dist/module/wallet/Base.js +1058 -215
- package/dist/module/wallet/Base.js.map +1 -1
- package/dist/module/wallet/Util.d.ts +7 -54
- package/dist/module/wallet/Util.d.ts.map +1 -1
- package/dist/module/wallet/Util.js +12 -79
- package/dist/module/wallet/Util.js.map +1 -1
- package/dist/module/wallet/Wif.d.ts +46 -251
- package/dist/module/wallet/Wif.d.ts.map +1 -1
- package/dist/module/wallet/Wif.js +126 -1026
- package/dist/module/wallet/Wif.js.map +1 -1
- package/dist/module/wallet/createWallet.d.ts +2 -1
- package/dist/module/wallet/createWallet.d.ts.map +1 -1
- package/dist/module/wallet/createWallet.js +2 -3
- package/dist/module/wallet/createWallet.js.map +1 -1
- package/dist/module/wallet/interface.d.ts +2 -4
- package/dist/module/wallet/interface.d.ts.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +6 -12
- package/src/cli.ts +0 -4
- package/src/index.ts +1 -5
- package/src/network/ElectrumNetworkProvider.ts +133 -188
- package/src/network/NetworkProvider.ts +9 -30
- package/src/network/Rpc.test.ts +14 -5
- package/src/network/constant.ts +0 -23
- package/src/network/default.ts +26 -66
- package/src/network/electrum.test.ts +2 -4
- package/src/network/interface.ts +2 -9
- package/src/network/util.ts +6 -7
- package/src/rate/ExchangeRate.test.ts +1 -1
- package/src/rate/ExchangeRate.ts +2 -1
- package/{polyfill/json.js → src/test/json.test.ts} +7 -1
- package/src/transaction/Wif.ts +2 -1
- package/src/wallet/Base.ts +1520 -273
- package/src/wallet/Cashtokens.test.headless.js +1 -1
- package/src/wallet/Cashtokens.test.ts +7 -8
- package/src/wallet/Util.ts +20 -102
- package/src/wallet/Wif.bip39.test.ts +3 -3
- package/src/wallet/Wif.test.ts +31 -25
- package/src/wallet/Wif.ts +174 -1493
- package/src/wallet/Wif.watchOnly.test.ts +5 -5
- package/src/wallet/createWallet.ts +11 -10
- package/src/wallet/interface.ts +3 -4
- package/webpack.config.cjs +4 -55
- package/dist/module/qr/Qr.d.ts +0 -9
- package/dist/module/qr/Qr.d.ts.map +0 -1
- package/dist/module/qr/Qr.js +0 -22
- package/dist/module/qr/Qr.js.map +0 -1
- package/dist/module/qr/interface.d.ts +0 -6
- package/dist/module/qr/interface.d.ts.map +0 -1
- package/dist/module/qr/interface.js +0 -2
- package/dist/module/qr/interface.js.map +0 -1
- package/dist/module/util/eventsource.d.ts +0 -3
- package/dist/module/util/eventsource.d.ts.map +0 -1
- package/dist/module/util/eventsource.js +0 -11
- package/dist/module/util/eventsource.js.map +0 -1
- package/polyfill/README.md +0 -1
- package/polyfill/eventsource.js +0 -6
- package/polyfill/support/types.js +0 -286
- package/polyfill/util.cjs +0 -249
- package/src/network/default.test.ts +0 -37
- package/src/qr/Qr.test.ts +0 -14
- package/src/qr/Qr.ts +0 -24
- package/src/qr/interface.ts +0 -5
- package/src/util/eventsource.ts +0 -12
package/src/wallet/Wif.ts
CHANGED
|
@@ -1,201 +1,88 @@
|
|
|
1
1
|
//#region Imports
|
|
2
|
-
|
|
3
|
-
import {
|
|
4
|
-
decodeCashAddress,
|
|
5
|
-
encodeHdPublicKey,
|
|
6
|
-
HdKeyNetwork,
|
|
7
|
-
hexToBin,
|
|
8
|
-
secp256k1,
|
|
9
|
-
} from "@bitauth/libauth";
|
|
2
|
+
import { encodeHdPublicKey, HdKeyNetwork, secp256k1 } from "@bitauth/libauth";
|
|
10
3
|
|
|
11
|
-
// Unstable?
|
|
12
4
|
import {
|
|
13
5
|
binToHex,
|
|
14
6
|
CashAddressNetworkPrefix,
|
|
15
|
-
CashAddressType,
|
|
16
|
-
deriveHdPublicNode,
|
|
17
7
|
decodePrivateKeyWif,
|
|
18
|
-
encodeCashAddress,
|
|
19
|
-
encodePrivateKeyWif,
|
|
20
|
-
deriveHdPrivateNodeFromSeed,
|
|
21
8
|
deriveHdPath,
|
|
9
|
+
deriveHdPrivateNodeFromSeed,
|
|
10
|
+
deriveHdPublicNode,
|
|
11
|
+
encodePrivateKeyWif,
|
|
22
12
|
generatePrivateKey,
|
|
23
13
|
} from "@bitauth/libauth";
|
|
24
14
|
|
|
25
|
-
import {
|
|
26
|
-
import { NetworkType
|
|
15
|
+
import { generateMnemonic, mnemonicToSeedSync } from "@scure/bip39";
|
|
16
|
+
import { NetworkType } from "../enum.js";
|
|
27
17
|
|
|
28
|
-
import {
|
|
18
|
+
import { PrivateKeyI } from "../interface.js";
|
|
29
19
|
|
|
30
|
-
import {
|
|
31
|
-
import {
|
|
32
|
-
|
|
33
|
-
import { BaseWallet } from "./Base.js";
|
|
34
|
-
import { FeePaidByEnum, WalletTypeEnum } from "./enum.js";
|
|
35
|
-
import {
|
|
36
|
-
CancelWatchFn,
|
|
37
|
-
SendRequestOptionsI,
|
|
38
|
-
WaitForTransactionOptions,
|
|
39
|
-
WaitForTransactionResponse,
|
|
40
|
-
WalletInfoI,
|
|
41
|
-
} from "./interface.js";
|
|
20
|
+
import { WalletTypeEnum } from "./enum.js";
|
|
21
|
+
import { MnemonicI, SendRequestOptionsI, WalletInfoI } from "./interface.js";
|
|
42
22
|
|
|
43
23
|
import {
|
|
44
|
-
fromUtxoId,
|
|
45
24
|
OpReturnData,
|
|
46
25
|
SendRequest,
|
|
47
26
|
SendRequestArray,
|
|
48
|
-
SendRequestType,
|
|
49
27
|
SendResponse,
|
|
50
28
|
SourceOutput,
|
|
51
|
-
TokenBurnRequest,
|
|
52
|
-
TokenGenesisRequest,
|
|
53
|
-
TokenMintRequest,
|
|
54
29
|
TokenSendRequest,
|
|
55
30
|
XPubKey,
|
|
56
31
|
} from "./model.js";
|
|
57
32
|
|
|
58
|
-
import {
|
|
59
|
-
buildEncodedTransaction,
|
|
60
|
-
getSuitableUtxos,
|
|
61
|
-
getFeeAmount,
|
|
62
|
-
signUnsignedTransaction,
|
|
63
|
-
getFeeAmountSimple,
|
|
64
|
-
} from "../transaction/Wif.js";
|
|
33
|
+
import { signUnsignedTransaction } from "../transaction/Wif.js";
|
|
65
34
|
|
|
66
|
-
import {
|
|
67
|
-
import {
|
|
68
|
-
balanceFromSatoshi,
|
|
69
|
-
balanceResponseFromSatoshi,
|
|
70
|
-
BalanceResponse,
|
|
71
|
-
} from "../util/balanceObjectFromSatoshi.js";
|
|
72
|
-
import { checkWifNetwork } from "../util/checkWifNetwork.js";
|
|
73
|
-
import {
|
|
74
|
-
deriveCashaddr,
|
|
75
|
-
deriveTokenaddr,
|
|
76
|
-
toTokenaddr,
|
|
77
|
-
} from "../util/deriveCashaddr.js";
|
|
78
|
-
import {
|
|
79
|
-
derivePrefix,
|
|
80
|
-
derivePublicKeyHash,
|
|
81
|
-
} from "../util/derivePublicKeyHash.js";
|
|
82
|
-
import { checkForEmptySeed } from "../util/checkForEmptySeed.js";
|
|
83
|
-
import { sanitizeUnit } from "../util/sanitizeUnit.js";
|
|
84
|
-
import { sumTokenAmounts, sumUtxoValue } from "../util/sumUtxoValue.js";
|
|
85
|
-
import { sumSendRequestAmounts } from "../util/sumSendRequestAmounts.js";
|
|
86
|
-
import { ElectrumRawTransaction } from "../network/interface.js";
|
|
87
|
-
import { getRelayFeeCache } from "../network/getRelayFeeCache.js";
|
|
88
|
-
import {
|
|
89
|
-
RegTestUtil,
|
|
90
|
-
RegTestWatchUtil,
|
|
91
|
-
RegTestWifUtil,
|
|
92
|
-
TestNetUtil,
|
|
93
|
-
TestNetWatchUtil,
|
|
94
|
-
TestNetWifUtil,
|
|
95
|
-
Util,
|
|
96
|
-
WatchUtil,
|
|
97
|
-
WifUtil,
|
|
98
|
-
} from "./Util.js";
|
|
99
|
-
import { getNetworkProvider } from "../network/index.js";
|
|
100
|
-
import { generateRandomBytes } from "../util/randomBytes.js";
|
|
101
|
-
import { SignedMessageI, SignedMessage } from "../message/index.js";
|
|
35
|
+
import { DERIVATION_PATHS } from "../constant.js";
|
|
36
|
+
import { SignedMessage, SignedMessageI } from "../message/index.js";
|
|
102
37
|
import ElectrumNetworkProvider from "../network/ElectrumNetworkProvider.js";
|
|
103
|
-
import {
|
|
38
|
+
import { checkForEmptySeed } from "../util/checkForEmptySeed.js";
|
|
39
|
+
import { checkWifNetwork } from "../util/checkWifNetwork.js";
|
|
40
|
+
import { deriveCashaddr, deriveTokenaddr } from "../util/deriveCashaddr.js";
|
|
41
|
+
import { derivePublicKeyHash } from "../util/derivePublicKeyHash.js";
|
|
104
42
|
import { getXPubKey } from "../util/getXPubKey.js";
|
|
105
|
-
import {
|
|
43
|
+
import { generateRandomBytes } from "../util/randomBytes.js";
|
|
106
44
|
|
|
107
|
-
import { getAddressHistory } from "../history/electrumTransformer.js";
|
|
108
|
-
import { IdentitySnapshot } from "./bcmr-v2.schema.js";
|
|
109
|
-
import { BCMR } from "./Bcmr.js";
|
|
110
|
-
import { qrAddress } from "../qr/Qr.js";
|
|
111
|
-
import { ImageI } from "../qr/interface.js";
|
|
112
45
|
import { Config } from "../config.js";
|
|
113
|
-
import {
|
|
114
|
-
|
|
115
|
-
|
|
46
|
+
import {
|
|
47
|
+
BalanceResponse,
|
|
48
|
+
balanceResponseFromSatoshi,
|
|
49
|
+
} from "../util/balanceObjectFromSatoshi.js";
|
|
50
|
+
import { BaseWallet } from "./Base.js";
|
|
116
51
|
//#endregion Imports
|
|
117
52
|
|
|
118
|
-
const placeholderPrivateKey =
|
|
119
|
-
"0000000000000000000000000000000000000000000000000000000000000001";
|
|
120
|
-
|
|
121
53
|
/**
|
|
122
54
|
* Class to manage a bitcoin cash wallet.
|
|
123
55
|
*/
|
|
124
56
|
export class Wallet extends BaseWallet {
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
cashaddr?: string;
|
|
129
|
-
tokenaddr?: string;
|
|
57
|
+
declare provider: ElectrumNetworkProvider;
|
|
58
|
+
declare cashaddr: string;
|
|
59
|
+
declare tokenaddr: string;
|
|
130
60
|
derivationPath: string = Config.DefaultParentDerivationPath + "/0/0";
|
|
131
61
|
parentDerivationPath: string = Config.DefaultParentDerivationPath;
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
_util?: Util;
|
|
62
|
+
mnemonic!: string;
|
|
63
|
+
parentXPubKey!: string;
|
|
64
|
+
privateKey!: Uint8Array;
|
|
65
|
+
publicKeyCompressed!: Uint8Array;
|
|
66
|
+
privateKeyWif!: string;
|
|
67
|
+
publicKey!: Uint8Array;
|
|
68
|
+
declare publicKeyHash: Uint8Array;
|
|
69
|
+
declare name: string;
|
|
141
70
|
static signedMessage: SignedMessageI = new SignedMessage();
|
|
142
71
|
|
|
143
72
|
//#region Accessors
|
|
144
|
-
//
|
|
145
|
-
public
|
|
146
|
-
if (!this.
|
|
147
|
-
|
|
73
|
+
// Get mnemonic and derivation path for wallet
|
|
74
|
+
public getSeed(): MnemonicI {
|
|
75
|
+
if (!this.mnemonic) {
|
|
76
|
+
throw Error("Wallet mnemonic seed phrase not set");
|
|
148
77
|
}
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
public slpSemiAware(value: boolean = true): Wallet {
|
|
159
|
-
this._slpSemiAware = value;
|
|
160
|
-
return this;
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
public getNetworkProvider(network: Network = Network.MAINNET) {
|
|
164
|
-
return getNetworkProvider(network);
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
/**
|
|
168
|
-
* getTokenDepositAddress - get a cashtoken aware wallet deposit address
|
|
169
|
-
*
|
|
170
|
-
* @returns The cashtoken aware deposit address as a string
|
|
171
|
-
*/
|
|
172
|
-
public getTokenDepositAddress(): string {
|
|
173
|
-
return this.tokenaddr!;
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
/**
|
|
177
|
-
* getDepositQr - get an address qrcode, encoded for display on the web
|
|
178
|
-
*
|
|
179
|
-
* @returns The qrcode for the token aware address
|
|
180
|
-
*/
|
|
181
|
-
public getTokenDepositQr(): ImageI {
|
|
182
|
-
return qrAddress(this.getTokenDepositAddress());
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
/**
|
|
186
|
-
* explorerUrl Web url to a transaction on a block explorer
|
|
187
|
-
*
|
|
188
|
-
* @param txId transaction Id
|
|
189
|
-
* @returns Url string
|
|
190
|
-
*/
|
|
191
|
-
public explorerUrl(txId: string) {
|
|
192
|
-
const explorerUrlMap = {
|
|
193
|
-
mainnet: "https://blockchair.com/bitcoin-cash/transaction/",
|
|
194
|
-
testnet: "https://www.blockchain.com/bch-testnet/tx/",
|
|
195
|
-
regtest: "",
|
|
78
|
+
if (!this.derivationPath) {
|
|
79
|
+
throw Error("Wallet derivation path not set");
|
|
80
|
+
}
|
|
81
|
+
return {
|
|
82
|
+
seed: this.mnemonic,
|
|
83
|
+
derivationPath: this.derivationPath,
|
|
84
|
+
parentDerivationPath: this.parentDerivationPath,
|
|
196
85
|
};
|
|
197
|
-
|
|
198
|
-
return explorerUrlMap[this.network] + txId;
|
|
199
86
|
}
|
|
200
87
|
|
|
201
88
|
// Return wallet info
|
|
@@ -212,9 +99,9 @@ export class Wallet extends BaseWallet {
|
|
|
212
99
|
? this.getSeed().parentDerivationPath
|
|
213
100
|
: undefined,
|
|
214
101
|
parentXPubKey: this.parentXPubKey ? this.parentXPubKey : undefined,
|
|
215
|
-
publicKey: this.publicKey ? binToHex(this.publicKey
|
|
216
|
-
publicKeyHash: binToHex(this.publicKeyHash
|
|
217
|
-
privateKey: this.privateKey ? binToHex(this.privateKey
|
|
102
|
+
publicKey: this.publicKey ? binToHex(this.publicKey) : undefined,
|
|
103
|
+
publicKeyHash: binToHex(this.publicKeyHash),
|
|
104
|
+
privateKey: this.privateKey ? binToHex(this.privateKey) : undefined,
|
|
218
105
|
privateKeyWif: this.privateKeyWif,
|
|
219
106
|
walletId: this.toString(),
|
|
220
107
|
walletDbEntry: this.toDbString(),
|
|
@@ -224,7 +111,7 @@ export class Wallet extends BaseWallet {
|
|
|
224
111
|
// returns the public key hash for an address
|
|
225
112
|
public getPublicKey(hex = false): string | Uint8Array {
|
|
226
113
|
if (this.publicKey) {
|
|
227
|
-
return hex ? binToHex(this.publicKey
|
|
114
|
+
return hex ? binToHex(this.publicKey) : this.publicKey;
|
|
228
115
|
} else {
|
|
229
116
|
throw Error(
|
|
230
117
|
"The public key for this wallet is not known, perhaps the wallet was created to watch the *hash* of a public key? i.e. a cashaddress."
|
|
@@ -236,7 +123,7 @@ export class Wallet extends BaseWallet {
|
|
|
236
123
|
public getPublicKeyCompressed(hex = false): string | Uint8Array {
|
|
237
124
|
if (this.publicKeyCompressed) {
|
|
238
125
|
return hex
|
|
239
|
-
? binToHex(this.publicKeyCompressed
|
|
126
|
+
? binToHex(this.publicKeyCompressed)
|
|
240
127
|
: this.publicKeyCompressed;
|
|
241
128
|
} else {
|
|
242
129
|
throw Error(
|
|
@@ -244,18 +131,6 @@ export class Wallet extends BaseWallet {
|
|
|
244
131
|
);
|
|
245
132
|
}
|
|
246
133
|
}
|
|
247
|
-
|
|
248
|
-
// returns the public key hash for an address
|
|
249
|
-
public getPublicKeyHash(hex = false): string | Uint8Array {
|
|
250
|
-
if (this.publicKeyHash) {
|
|
251
|
-
return hex ? binToHex(this.publicKeyHash!) : this.publicKeyHash;
|
|
252
|
-
} else {
|
|
253
|
-
throw Error(
|
|
254
|
-
"The public key hash for this wallet is not known. If this wallet was created from the constructor directly, calling the deriveInfo() function may help. "
|
|
255
|
-
);
|
|
256
|
-
}
|
|
257
|
-
}
|
|
258
|
-
|
|
259
134
|
//#endregion
|
|
260
135
|
|
|
261
136
|
//#region Constructors and Statics
|
|
@@ -264,8 +139,24 @@ export class Wallet extends BaseWallet {
|
|
|
264
139
|
network = NetworkType.Mainnet,
|
|
265
140
|
walletType = WalletTypeEnum.Seed
|
|
266
141
|
) {
|
|
267
|
-
super(
|
|
268
|
-
this.
|
|
142
|
+
super(network);
|
|
143
|
+
this.name = name;
|
|
144
|
+
this.walletType = walletType;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
//#region Statics
|
|
148
|
+
/**
|
|
149
|
+
* fromId - create a wallet from encoded walletId string
|
|
150
|
+
*
|
|
151
|
+
* @param walletId walletId options to steer the creation process
|
|
152
|
+
*
|
|
153
|
+
* @returns wallet instantiated accordingly to the walletId rules
|
|
154
|
+
*/
|
|
155
|
+
public static async fromId<T extends typeof Wallet>(
|
|
156
|
+
this: T,
|
|
157
|
+
walletId: string
|
|
158
|
+
): Promise<InstanceType<T>> {
|
|
159
|
+
return new this().fromId(walletId) as InstanceType<T>;
|
|
269
160
|
}
|
|
270
161
|
|
|
271
162
|
/**
|
|
@@ -283,47 +174,42 @@ export class Wallet extends BaseWallet {
|
|
|
283
174
|
}
|
|
284
175
|
|
|
285
176
|
/**
|
|
286
|
-
*
|
|
177
|
+
* fromSeed - create a wallet using the seed phrase and derivation path
|
|
287
178
|
*
|
|
288
|
-
*
|
|
289
|
-
*
|
|
179
|
+
* unless specified the derivation path m/44'/245'/0'/0/0 will be userd
|
|
180
|
+
* this derivation path is standard for Electron Cash SLP and other SLP enabled wallets
|
|
290
181
|
*
|
|
291
|
-
* @param
|
|
182
|
+
* @param seed BIP39 12 word seed phrase
|
|
183
|
+
* @param derivationPath BIP44 HD wallet derivation path to get a single the private key from hierarchy
|
|
292
184
|
*
|
|
293
185
|
* @returns instantiated wallet
|
|
294
186
|
*/
|
|
295
|
-
public static async
|
|
187
|
+
public static async fromSeed<T extends typeof Wallet>(
|
|
296
188
|
this: T,
|
|
297
|
-
|
|
189
|
+
seed: string,
|
|
190
|
+
derivationPath?: string
|
|
298
191
|
): Promise<InstanceType<T>> {
|
|
299
|
-
|
|
300
|
-
const networkType = networkPrefixMap[prefix] as NetworkType;
|
|
301
|
-
return new this("", networkType, WalletTypeEnum.Watch).watchOnly(
|
|
302
|
-
address
|
|
303
|
-
) as InstanceType<T>;
|
|
192
|
+
return new this().fromSeed(seed, derivationPath) as InstanceType<T>;
|
|
304
193
|
}
|
|
305
194
|
|
|
306
195
|
/**
|
|
307
|
-
*
|
|
196
|
+
* newRandom - create a random wallet
|
|
308
197
|
*
|
|
309
|
-
*
|
|
310
|
-
* however it still allows to use many utility functions such as getting and watching balance, etc.
|
|
198
|
+
* if `name` parameter is specified, the wallet will also be persisted to DB
|
|
311
199
|
*
|
|
312
|
-
* @param
|
|
200
|
+
* @param name user friendly wallet alias
|
|
201
|
+
* @param dbName name under which the wallet will be stored in the database
|
|
313
202
|
*
|
|
314
203
|
* @returns instantiated wallet
|
|
315
204
|
*/
|
|
316
|
-
public static async
|
|
205
|
+
public static async newRandom<T extends typeof Wallet>(
|
|
317
206
|
this: T,
|
|
318
|
-
|
|
207
|
+
name: string = "",
|
|
208
|
+
dbName?: string
|
|
319
209
|
): Promise<InstanceType<T>> {
|
|
320
|
-
|
|
321
|
-
const networkType = networkPrefixMap[prefix] as NetworkType;
|
|
322
|
-
return new this("", networkType, WalletTypeEnum.Watch).watchOnly(
|
|
323
|
-
address
|
|
324
|
-
) as InstanceType<T>;
|
|
210
|
+
return new this().newRandom(name, dbName) as InstanceType<T>;
|
|
325
211
|
}
|
|
326
|
-
//#endregion Constructors
|
|
212
|
+
//#endregion Constructors
|
|
327
213
|
|
|
328
214
|
//#region Protected implementations
|
|
329
215
|
protected async generate(): Promise<this> {
|
|
@@ -343,7 +229,9 @@ export class Wallet extends BaseWallet {
|
|
|
343
229
|
|
|
344
230
|
private async _generateWif() {
|
|
345
231
|
if (!this.privateKey) {
|
|
346
|
-
this.privateKey = generatePrivateKey(
|
|
232
|
+
this.privateKey = generatePrivateKey(
|
|
233
|
+
() => generateRandomBytes(32) as Uint8Array
|
|
234
|
+
);
|
|
347
235
|
}
|
|
348
236
|
return this.deriveInfo();
|
|
349
237
|
}
|
|
@@ -352,7 +240,7 @@ export class Wallet extends BaseWallet {
|
|
|
352
240
|
this.mnemonic = generateMnemonic(Config.getWordlist());
|
|
353
241
|
if (this.mnemonic.length == 0)
|
|
354
242
|
throw Error("refusing to create wallet from empty mnemonic");
|
|
355
|
-
const seed = mnemonicToSeedSync(this.mnemonic
|
|
243
|
+
const seed = mnemonicToSeedSync(this.mnemonic);
|
|
356
244
|
checkForEmptySeed(seed);
|
|
357
245
|
const network = this.isTestnet ? "testnet" : "mainnet";
|
|
358
246
|
this.parentXPubKey = getXPubKey(seed, this.parentDerivationPath, network);
|
|
@@ -373,18 +261,46 @@ export class Wallet extends BaseWallet {
|
|
|
373
261
|
}
|
|
374
262
|
|
|
375
263
|
protected fromId = async (walletId: string): Promise<this> => {
|
|
376
|
-
const [walletType, networkGiven, arg1]: string[] =
|
|
264
|
+
const [walletType, networkGiven, arg1, arg2]: string[] =
|
|
265
|
+
walletId.split(":");
|
|
377
266
|
|
|
378
|
-
if (this.network
|
|
267
|
+
if (this.network !== networkGiven) {
|
|
379
268
|
throw Error(`Network prefix ${networkGiven} to a ${this.network} wallet`);
|
|
380
269
|
}
|
|
381
270
|
|
|
382
271
|
// "wif:regtest:cNfsPtqN2bMRS7vH5qd8tR8GMvgXyL5BjnGAKgZ8DYEiCrCCQcP6"
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
272
|
+
switch (walletType) {
|
|
273
|
+
case "wif":
|
|
274
|
+
return this.fromWIF(arg1);
|
|
275
|
+
|
|
276
|
+
case "watch":
|
|
277
|
+
if (arg2) {
|
|
278
|
+
// watch:testnet:bchtest:qq1234567
|
|
279
|
+
return this.watchOnly(`${arg1}:${arg2}`);
|
|
280
|
+
}
|
|
281
|
+
// watch:testnet:qq1234567
|
|
282
|
+
return this.watchOnly(`${arg1}`);
|
|
283
|
+
|
|
284
|
+
case "named":
|
|
285
|
+
if (arg2) {
|
|
286
|
+
// named:testnet:wallet_1:my_database
|
|
287
|
+
return this.named(arg1, arg2);
|
|
288
|
+
} else {
|
|
289
|
+
// named:testnet:wallet_1
|
|
290
|
+
return this.named(arg1);
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
case "seed":
|
|
294
|
+
if (arg2) {
|
|
295
|
+
// seed:testnet:table later ... stove kitten pluck:m/44'/0'/0'/0/0
|
|
296
|
+
return this.fromSeed(arg1, arg2);
|
|
297
|
+
}
|
|
298
|
+
// seed:testnet:table later ... stove kitten pluck
|
|
299
|
+
return this.fromSeed(arg1);
|
|
386
300
|
|
|
387
|
-
|
|
301
|
+
default:
|
|
302
|
+
throw Error(`Unknown wallet type '${walletType}'`);
|
|
303
|
+
}
|
|
388
304
|
};
|
|
389
305
|
|
|
390
306
|
public async getXPubKeys(paths?) {
|
|
@@ -446,7 +362,7 @@ export class Wallet extends BaseWallet {
|
|
|
446
362
|
public async deriveHdPaths(hdPaths: string[]): Promise<any[]> {
|
|
447
363
|
if (!this.mnemonic)
|
|
448
364
|
throw Error("refusing to create wallet from empty mnemonic");
|
|
449
|
-
const seed = mnemonicToSeedSync(this.mnemonic
|
|
365
|
+
const seed = mnemonicToSeedSync(this.mnemonic);
|
|
450
366
|
checkForEmptySeed(seed);
|
|
451
367
|
const hdNode = deriveHdPrivateNodeFromSeed(seed, {
|
|
452
368
|
assumeValidity: true, // TODO: we should switch to libauth's BIP39 implementation and set this to false
|
|
@@ -490,53 +406,6 @@ export class Wallet extends BaseWallet {
|
|
|
490
406
|
});
|
|
491
407
|
}
|
|
492
408
|
|
|
493
|
-
// Initialize a watch only wallet from a cash addr
|
|
494
|
-
protected async watchOnly(address: string): Promise<this> {
|
|
495
|
-
this.walletType = WalletTypeEnum.Watch;
|
|
496
|
-
const addressComponents = address.split(":");
|
|
497
|
-
let addressPrefix, addressBase;
|
|
498
|
-
if (addressComponents.length === 1) {
|
|
499
|
-
addressBase = addressComponents.shift() as string;
|
|
500
|
-
addressPrefix = derivePrefix(addressBase);
|
|
501
|
-
} else {
|
|
502
|
-
addressPrefix = addressComponents.shift() as string;
|
|
503
|
-
addressBase = addressComponents.shift() as string;
|
|
504
|
-
if (addressPrefix in networkPrefixMap) {
|
|
505
|
-
if (networkPrefixMap[addressPrefix] != this.network) {
|
|
506
|
-
throw Error(
|
|
507
|
-
`a ${addressPrefix} address cannot be watched from a ${this.network} Walconst`
|
|
508
|
-
);
|
|
509
|
-
}
|
|
510
|
-
}
|
|
511
|
-
}
|
|
512
|
-
|
|
513
|
-
const prefixedAddress = `${addressPrefix}:${addressBase}`;
|
|
514
|
-
|
|
515
|
-
// check if a token aware address was provided
|
|
516
|
-
const addressData = decodeCashAddress(prefixedAddress);
|
|
517
|
-
if (typeof addressData === "string") throw addressData;
|
|
518
|
-
|
|
519
|
-
this.publicKeyHash = addressData.payload;
|
|
520
|
-
|
|
521
|
-
let nonTokenAwareType = addressData.type;
|
|
522
|
-
if (nonTokenAwareType == CashAddressType.p2pkhWithTokens)
|
|
523
|
-
nonTokenAwareType = CashAddressType.p2pkh;
|
|
524
|
-
if (nonTokenAwareType == CashAddressType.p2shWithTokens)
|
|
525
|
-
nonTokenAwareType = CashAddressType.p2sh;
|
|
526
|
-
if (nonTokenAwareType == CashAddressType.p2pkh)
|
|
527
|
-
this.publicKeyHash = addressData.payload;
|
|
528
|
-
|
|
529
|
-
this.cashaddr = encodeCashAddress({
|
|
530
|
-
prefix: addressData.prefix as CashAddressNetworkPrefix,
|
|
531
|
-
type: nonTokenAwareType,
|
|
532
|
-
payload: addressData.payload,
|
|
533
|
-
}).address;
|
|
534
|
-
this.address = this.cashaddr;
|
|
535
|
-
this.tokenaddr = deriveTokenaddr(addressData.payload, this.networkPrefix);
|
|
536
|
-
|
|
537
|
-
return this;
|
|
538
|
-
}
|
|
539
|
-
|
|
540
409
|
// Initialize wallet from Wallet Import Format
|
|
541
410
|
protected async fromWIF(secret: string): Promise<this> {
|
|
542
411
|
checkWifNetwork(secret, this.network);
|
|
@@ -554,32 +423,19 @@ export class Wallet extends BaseWallet {
|
|
|
554
423
|
return this;
|
|
555
424
|
}
|
|
556
425
|
|
|
426
|
+
/**
|
|
427
|
+
* newRandom (internal) if the wallet is named, get or create it; otherwise create a random
|
|
428
|
+
* unnamed wallet
|
|
429
|
+
* @param {string} name name of the wallet
|
|
430
|
+
* @param {string} dbName database name the wallet is stored in
|
|
431
|
+
*/
|
|
557
432
|
protected async newRandom(name: string, dbName?: string): Promise<this> {
|
|
558
433
|
dbName = dbName ? dbName : this.networkPrefix;
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
dbName?: string,
|
|
565
|
-
forceNew: boolean = false
|
|
566
|
-
): Promise<this> {
|
|
567
|
-
dbName = dbName ? dbName : this.networkPrefix;
|
|
568
|
-
return super.named(name, dbName, forceNew);
|
|
569
|
-
}
|
|
570
|
-
|
|
571
|
-
protected async replaceNamed(
|
|
572
|
-
name: string,
|
|
573
|
-
walletId: string,
|
|
574
|
-
dbName?: string
|
|
575
|
-
): Promise<this> {
|
|
576
|
-
dbName = dbName ? dbName : this.networkPrefix;
|
|
577
|
-
return super.replaceNamed(name, walletId, dbName);
|
|
578
|
-
}
|
|
579
|
-
|
|
580
|
-
protected async namedExists(name: string, dbName?: string): Promise<boolean> {
|
|
581
|
-
dbName = dbName ? dbName : this.networkPrefix;
|
|
582
|
-
return super.namedExists(name, dbName);
|
|
434
|
+
if (name.length > 0) {
|
|
435
|
+
return this.named(name, dbName);
|
|
436
|
+
} else {
|
|
437
|
+
return this.generate();
|
|
438
|
+
}
|
|
583
439
|
}
|
|
584
440
|
//#endregion Protected Implementations
|
|
585
441
|
|
|
@@ -587,25 +443,33 @@ export class Wallet extends BaseWallet {
|
|
|
587
443
|
// Returns the serialized wallet as a string
|
|
588
444
|
// If storing in a database, set asNamed to false to store secrets
|
|
589
445
|
// In all other cases, the a named wallet is deserialized from the database
|
|
590
|
-
//
|
|
446
|
+
// by the name key
|
|
591
447
|
public toString() {
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
448
|
+
if (this.name) {
|
|
449
|
+
return `named:${this.network}:${this.name}`;
|
|
450
|
+
} else if (this.walletType == WalletTypeEnum.Seed) {
|
|
451
|
+
return `${this.walletType}:${this.network}:${this.mnemonic}:${this.derivationPath}`;
|
|
452
|
+
} else if (this.walletType === WalletTypeEnum.Wif) {
|
|
596
453
|
return `${this.walletType}:${this.network}:${this.privateKeyWif}`;
|
|
454
|
+
} else if (this.walletType == WalletTypeEnum.Watch) {
|
|
455
|
+
return super.toString();
|
|
597
456
|
}
|
|
598
457
|
|
|
599
458
|
throw Error("toString unsupported wallet type");
|
|
600
459
|
}
|
|
601
460
|
|
|
602
|
-
|
|
461
|
+
/**
|
|
462
|
+
* toDbString - store the serialized version of the wallet in the database, not just the name
|
|
463
|
+
*
|
|
464
|
+
* @throws {Error} if called on BaseWallet
|
|
465
|
+
*/
|
|
603
466
|
public toDbString() {
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
if (this.walletType === WalletTypeEnum.Wif) {
|
|
467
|
+
if (this.walletType == WalletTypeEnum.Seed) {
|
|
468
|
+
return `${this.walletType}:${this.network}:${this.mnemonic}:${this.derivationPath}`;
|
|
469
|
+
} else if (this.walletType === WalletTypeEnum.Wif) {
|
|
608
470
|
return `${this.walletType}:${this.network}:${this.privateKeyWif}`;
|
|
471
|
+
} else if (this.walletType == WalletTypeEnum.Watch) {
|
|
472
|
+
return super.toDbString();
|
|
609
473
|
}
|
|
610
474
|
|
|
611
475
|
throw Error("toDbString unsupported wallet type");
|
|
@@ -613,293 +477,6 @@ export class Wallet extends BaseWallet {
|
|
|
613
477
|
//#endregion Serialization
|
|
614
478
|
|
|
615
479
|
//#region Funds
|
|
616
|
-
//
|
|
617
|
-
public async getAddressUtxos(address?: string): Promise<UtxoI[]> {
|
|
618
|
-
if (!address) {
|
|
619
|
-
address = this.cashaddr!;
|
|
620
|
-
}
|
|
621
|
-
|
|
622
|
-
if (this._slpSemiAware) {
|
|
623
|
-
const bchUtxos: UtxoI[] = await this.provider!.getUtxos(address);
|
|
624
|
-
return bchUtxos.filter(
|
|
625
|
-
(bchutxo) => bchutxo.satoshis > DUST_UTXO_THRESHOLD
|
|
626
|
-
);
|
|
627
|
-
} else {
|
|
628
|
-
return await this.provider!.getUtxos(address);
|
|
629
|
-
}
|
|
630
|
-
}
|
|
631
|
-
|
|
632
|
-
/**
|
|
633
|
-
* utxos Get unspent outputs for the wallet
|
|
634
|
-
*
|
|
635
|
-
*/
|
|
636
|
-
public async getUtxos() {
|
|
637
|
-
if (!this.cashaddr) {
|
|
638
|
-
throw Error("Attempted to get utxos without an address");
|
|
639
|
-
}
|
|
640
|
-
return await this.getAddressUtxos(this.cashaddr);
|
|
641
|
-
}
|
|
642
|
-
|
|
643
|
-
// gets wallet balance in sats, bch and currency
|
|
644
|
-
public async getBalance(
|
|
645
|
-
rawUnit?: string,
|
|
646
|
-
priceCache = true
|
|
647
|
-
): Promise<BalanceResponse | number> {
|
|
648
|
-
if (rawUnit) {
|
|
649
|
-
const unit = sanitizeUnit(rawUnit);
|
|
650
|
-
return await balanceFromSatoshi(
|
|
651
|
-
await this.getBalanceFromProvider(),
|
|
652
|
-
unit,
|
|
653
|
-
priceCache
|
|
654
|
-
);
|
|
655
|
-
} else {
|
|
656
|
-
return await balanceResponseFromSatoshi(
|
|
657
|
-
await this.getBalanceFromProvider(),
|
|
658
|
-
priceCache
|
|
659
|
-
);
|
|
660
|
-
}
|
|
661
|
-
}
|
|
662
|
-
|
|
663
|
-
// Gets balance by summing value in all utxos in stats
|
|
664
|
-
public async getBalanceFromUtxos(): Promise<number> {
|
|
665
|
-
const utxos = (await this.getAddressUtxos(this.cashaddr!)).filter(
|
|
666
|
-
(val) => val.token === undefined
|
|
667
|
-
);
|
|
668
|
-
return sumUtxoValue(utxos);
|
|
669
|
-
}
|
|
670
|
-
|
|
671
|
-
// Gets balance from fulcrum
|
|
672
|
-
public async getBalanceFromProvider(): Promise<number> {
|
|
673
|
-
// Fulcrum reports balance of all utxos, including tokens, which is undesirable
|
|
674
|
-
// // TODO not sure why getting the balance from a provider doesn't work
|
|
675
|
-
// if (this._slpAware || this._slpSemiAware) {
|
|
676
|
-
// return await this.getBalanceFromUtxos();
|
|
677
|
-
// } else {
|
|
678
|
-
// return await this.provider!.getBalance(this.cashaddr!);
|
|
679
|
-
// }
|
|
680
|
-
|
|
681
|
-
// FIXME
|
|
682
|
-
return this.getBalanceFromUtxos();
|
|
683
|
-
}
|
|
684
|
-
|
|
685
|
-
// watching for any transaction hash of this wallet
|
|
686
|
-
public watchAddress(callback: (txHash: string) => void): CancelWatchFn {
|
|
687
|
-
return (this.provider! as ElectrumNetworkProvider).watchAddress(
|
|
688
|
-
this.getDepositAddress(),
|
|
689
|
-
callback
|
|
690
|
-
);
|
|
691
|
-
}
|
|
692
|
-
|
|
693
|
-
// watching for any transaction of this wallet
|
|
694
|
-
public watchAddressTransactions(
|
|
695
|
-
callback: (tx: ElectrumRawTransaction) => void
|
|
696
|
-
): CancelWatchFn {
|
|
697
|
-
return (this.provider! as ElectrumNetworkProvider).watchAddressTransactions(
|
|
698
|
-
this.getDepositAddress(),
|
|
699
|
-
callback
|
|
700
|
-
);
|
|
701
|
-
}
|
|
702
|
-
|
|
703
|
-
// watching for cashtoken transaction of this wallet
|
|
704
|
-
public watchAddressTokenTransactions(
|
|
705
|
-
callback: (tx: ElectrumRawTransaction) => void
|
|
706
|
-
): CancelWatchFn {
|
|
707
|
-
return (
|
|
708
|
-
this.provider! as ElectrumNetworkProvider
|
|
709
|
-
).watchAddressTokenTransactions(this.getDepositAddress(), callback);
|
|
710
|
-
}
|
|
711
|
-
|
|
712
|
-
// sets up a callback to be called upon wallet's balance change
|
|
713
|
-
// can be cancelled by calling the function returned from this one
|
|
714
|
-
public watchBalance(
|
|
715
|
-
callback: (balance: BalanceResponse) => void
|
|
716
|
-
): CancelWatchFn {
|
|
717
|
-
return (this.provider! as ElectrumNetworkProvider).watchAddressStatus(
|
|
718
|
-
this.getDepositAddress(),
|
|
719
|
-
async (_status: string) => {
|
|
720
|
-
const balance = (await this.getBalance()) as BalanceResponse;
|
|
721
|
-
callback(balance);
|
|
722
|
-
}
|
|
723
|
-
);
|
|
724
|
-
}
|
|
725
|
-
|
|
726
|
-
// sets up a callback to be called upon wallet's BCH or USD balance change
|
|
727
|
-
// if BCH balance does not change, the callback will be triggered every
|
|
728
|
-
// @param `usdPriceRefreshInterval` milliseconds by polling for new BCH USD price
|
|
729
|
-
// Since we want to be most sensitive to usd value change, we do not use the cached exchange rates
|
|
730
|
-
// can be cancelled by calling the function returned from this one
|
|
731
|
-
public watchBalanceUsd(
|
|
732
|
-
callback: (balance: BalanceResponse) => void,
|
|
733
|
-
usdPriceRefreshInterval = 30000
|
|
734
|
-
): CancelWatchFn {
|
|
735
|
-
let usdPrice = -1;
|
|
736
|
-
|
|
737
|
-
const _callback = async () => {
|
|
738
|
-
const balance = (await this.getBalance(
|
|
739
|
-
undefined,
|
|
740
|
-
false
|
|
741
|
-
)) as BalanceResponse;
|
|
742
|
-
if (usdPrice !== balance.usd!) {
|
|
743
|
-
usdPrice = balance.usd!;
|
|
744
|
-
callback(balance);
|
|
745
|
-
}
|
|
746
|
-
};
|
|
747
|
-
|
|
748
|
-
const watchCancel = (
|
|
749
|
-
this.provider! as ElectrumNetworkProvider
|
|
750
|
-
).watchAddressStatus(this.getDepositAddress(), _callback);
|
|
751
|
-
const interval = setInterval(_callback, usdPriceRefreshInterval);
|
|
752
|
-
|
|
753
|
-
return async () => {
|
|
754
|
-
await watchCancel();
|
|
755
|
-
clearInterval(interval);
|
|
756
|
-
};
|
|
757
|
-
}
|
|
758
|
-
|
|
759
|
-
// waits for address balance to be greater than or equal to the target value
|
|
760
|
-
// this call halts the execution
|
|
761
|
-
public async waitForBalance(
|
|
762
|
-
value: number,
|
|
763
|
-
rawUnit: UnitEnum = UnitEnum.BCH
|
|
764
|
-
): Promise<BalanceResponse> {
|
|
765
|
-
return new Promise(async (resolve) => {
|
|
766
|
-
const watchCancel = this.watchBalance(
|
|
767
|
-
async (balance: BalanceResponse) => {
|
|
768
|
-
const satoshiBalance = await amountInSatoshi(value, rawUnit);
|
|
769
|
-
if (balance.sat! >= satoshiBalance) {
|
|
770
|
-
await watchCancel();
|
|
771
|
-
resolve(balance);
|
|
772
|
-
}
|
|
773
|
-
}
|
|
774
|
-
);
|
|
775
|
-
});
|
|
776
|
-
}
|
|
777
|
-
|
|
778
|
-
// sets up a callback to be called upon wallet's token balance change
|
|
779
|
-
// can be cancelled by calling the function returned from this one
|
|
780
|
-
public watchTokenBalance(
|
|
781
|
-
tokenId: string,
|
|
782
|
-
callback: (balance: bigint) => void
|
|
783
|
-
): CancelWatchFn {
|
|
784
|
-
let previous: bigint | undefined = undefined;
|
|
785
|
-
return (this.provider! as ElectrumNetworkProvider).watchAddressStatus(
|
|
786
|
-
this.getDepositAddress(),
|
|
787
|
-
async (_status: string) => {
|
|
788
|
-
const balance = await this.getTokenBalance(tokenId);
|
|
789
|
-
if (previous != balance) {
|
|
790
|
-
callback(balance);
|
|
791
|
-
}
|
|
792
|
-
previous = balance;
|
|
793
|
-
}
|
|
794
|
-
);
|
|
795
|
-
}
|
|
796
|
-
|
|
797
|
-
// waits for address token balance to be greater than or equal to the target amount
|
|
798
|
-
// this call halts the execution
|
|
799
|
-
public async waitForTokenBalance(
|
|
800
|
-
tokenId: string,
|
|
801
|
-
amount: bigint
|
|
802
|
-
): Promise<bigint> {
|
|
803
|
-
return new Promise(async (resolve) => {
|
|
804
|
-
const watchCancel = this.watchTokenBalance(
|
|
805
|
-
tokenId,
|
|
806
|
-
async (balance: bigint) => {
|
|
807
|
-
if (balance >= amount) {
|
|
808
|
-
await watchCancel();
|
|
809
|
-
resolve(balance);
|
|
810
|
-
}
|
|
811
|
-
}
|
|
812
|
-
);
|
|
813
|
-
});
|
|
814
|
-
}
|
|
815
|
-
|
|
816
|
-
public async getTokenInfo(
|
|
817
|
-
tokenId: string
|
|
818
|
-
): Promise<IdentitySnapshot | undefined> {
|
|
819
|
-
return BCMR.getTokenInfo(tokenId);
|
|
820
|
-
}
|
|
821
|
-
|
|
822
|
-
private async _getMaxAmountToSend(
|
|
823
|
-
params: {
|
|
824
|
-
outputCount?: number;
|
|
825
|
-
options?: SendRequestOptionsI;
|
|
826
|
-
} = {
|
|
827
|
-
outputCount: 1,
|
|
828
|
-
options: {},
|
|
829
|
-
}
|
|
830
|
-
): Promise<{ value: number; utxos: UtxoI[] }> {
|
|
831
|
-
if (!this.privateKey && params.options?.buildUnsigned !== true) {
|
|
832
|
-
throw Error("Couldn't get network or private key for wallet.");
|
|
833
|
-
}
|
|
834
|
-
if (!this.cashaddr) {
|
|
835
|
-
throw Error("attempted to send without a cashaddr");
|
|
836
|
-
}
|
|
837
|
-
|
|
838
|
-
if (params.options && params.options.slpSemiAware) {
|
|
839
|
-
this._slpSemiAware = true;
|
|
840
|
-
}
|
|
841
|
-
|
|
842
|
-
let feePaidBy;
|
|
843
|
-
if (params.options && params.options.feePaidBy) {
|
|
844
|
-
feePaidBy = params.options.feePaidBy;
|
|
845
|
-
} else {
|
|
846
|
-
feePaidBy = FeePaidByEnum.change;
|
|
847
|
-
}
|
|
848
|
-
|
|
849
|
-
// get inputs
|
|
850
|
-
let utxos: UtxoI[];
|
|
851
|
-
if (params.options && params.options.utxoIds) {
|
|
852
|
-
utxos = await checkUtxos(
|
|
853
|
-
params.options.utxoIds.map((utxoId: UtxoI | string) =>
|
|
854
|
-
typeof utxoId === "string" ? fromUtxoId(utxoId) : utxoId
|
|
855
|
-
),
|
|
856
|
-
this
|
|
857
|
-
);
|
|
858
|
-
} else {
|
|
859
|
-
utxos = (await this.getAddressUtxos(this.cashaddr)).filter(
|
|
860
|
-
(utxo) => !utxo.token
|
|
861
|
-
);
|
|
862
|
-
}
|
|
863
|
-
|
|
864
|
-
// Get current height to assure recently mined coins are not spent.
|
|
865
|
-
const bestHeight = await this.provider!.getBlockHeight();
|
|
866
|
-
|
|
867
|
-
// simulate outputs using the sender's address
|
|
868
|
-
const sendRequest = new SendRequest({
|
|
869
|
-
cashaddr: this.cashaddr,
|
|
870
|
-
value: 100,
|
|
871
|
-
unit: "sat",
|
|
872
|
-
});
|
|
873
|
-
const sendRequests = Array(params.outputCount)
|
|
874
|
-
.fill(0)
|
|
875
|
-
.map(() => sendRequest);
|
|
876
|
-
|
|
877
|
-
const fundingUtxos = await getSuitableUtxos(
|
|
878
|
-
utxos,
|
|
879
|
-
undefined,
|
|
880
|
-
bestHeight,
|
|
881
|
-
feePaidBy,
|
|
882
|
-
sendRequests
|
|
883
|
-
);
|
|
884
|
-
const relayFeePerByteInSatoshi = await getRelayFeeCache(this.provider!);
|
|
885
|
-
const fee = await getFeeAmountSimple({
|
|
886
|
-
utxos: fundingUtxos,
|
|
887
|
-
sendRequests: sendRequests,
|
|
888
|
-
privateKey: this.privateKey ?? hexToBin(placeholderPrivateKey),
|
|
889
|
-
sourceAddress: this.cashaddr!,
|
|
890
|
-
relayFeePerByteInSatoshi: relayFeePerByteInSatoshi,
|
|
891
|
-
feePaidBy: feePaidBy,
|
|
892
|
-
});
|
|
893
|
-
const spendableAmount = sumUtxoValue(fundingUtxos);
|
|
894
|
-
|
|
895
|
-
let result = spendableAmount - fee;
|
|
896
|
-
if (result < 0) {
|
|
897
|
-
result = 0;
|
|
898
|
-
}
|
|
899
|
-
|
|
900
|
-
return { value: result, utxos: fundingUtxos };
|
|
901
|
-
}
|
|
902
|
-
|
|
903
480
|
public async getMaxAmountToSend(
|
|
904
481
|
params: {
|
|
905
482
|
outputCount?: number;
|
|
@@ -909,58 +486,15 @@ export class Wallet extends BaseWallet {
|
|
|
909
486
|
options: {},
|
|
910
487
|
}
|
|
911
488
|
): Promise<BalanceResponse> {
|
|
912
|
-
const { value: result } = await this._getMaxAmountToSend(
|
|
489
|
+
const { value: result } = await this._getMaxAmountToSend({
|
|
490
|
+
options: params.options,
|
|
491
|
+
outputCount: params.outputCount,
|
|
492
|
+
privateKey: this.privateKey,
|
|
493
|
+
});
|
|
913
494
|
|
|
914
495
|
return await balanceResponseFromSatoshi(result);
|
|
915
496
|
}
|
|
916
497
|
|
|
917
|
-
/**
|
|
918
|
-
* send Send some amount to an address
|
|
919
|
-
* this function processes the send requests, encodes the transaction, sends it to the network
|
|
920
|
-
* @returns (depending on the options parameter) the transaction id, new address balance and a link to the transaction on the blockchain explorer
|
|
921
|
-
*
|
|
922
|
-
* This is a first class function with REST analog, maintainers should strive to keep backward-compatibility
|
|
923
|
-
*
|
|
924
|
-
*/
|
|
925
|
-
public async send(
|
|
926
|
-
requests:
|
|
927
|
-
| SendRequest
|
|
928
|
-
| TokenSendRequest
|
|
929
|
-
| OpReturnData
|
|
930
|
-
| Array<SendRequest | TokenSendRequest | OpReturnData>
|
|
931
|
-
| SendRequestArray[],
|
|
932
|
-
options?: SendRequestOptionsI
|
|
933
|
-
): Promise<SendResponse> {
|
|
934
|
-
const { encodedTransaction, tokenIds, sourceOutputs } =
|
|
935
|
-
await this.encodeTransaction(requests, undefined, options);
|
|
936
|
-
|
|
937
|
-
const resp = new SendResponse({});
|
|
938
|
-
resp.tokenIds = tokenIds;
|
|
939
|
-
|
|
940
|
-
if (options?.buildUnsigned !== true) {
|
|
941
|
-
const txId = await this.submitTransaction(
|
|
942
|
-
encodedTransaction,
|
|
943
|
-
options?.awaitTransactionPropagation === undefined ||
|
|
944
|
-
options?.awaitTransactionPropagation === true
|
|
945
|
-
);
|
|
946
|
-
|
|
947
|
-
resp.txId = txId;
|
|
948
|
-
resp.explorerUrl = this.explorerUrl(resp.txId);
|
|
949
|
-
|
|
950
|
-
if (
|
|
951
|
-
options?.queryBalance === undefined ||
|
|
952
|
-
options?.queryBalance === true
|
|
953
|
-
) {
|
|
954
|
-
resp.balance = (await this.getBalance()) as BalanceResponse;
|
|
955
|
-
}
|
|
956
|
-
} else {
|
|
957
|
-
resp.unsignedTransaction = binToHex(encodedTransaction);
|
|
958
|
-
resp.sourceOutputs = sourceOutputs;
|
|
959
|
-
}
|
|
960
|
-
|
|
961
|
-
return resp;
|
|
962
|
-
}
|
|
963
|
-
|
|
964
498
|
/**
|
|
965
499
|
* sendMax Send all available funds to a destination cash address
|
|
966
500
|
*
|
|
@@ -973,68 +507,7 @@ export class Wallet extends BaseWallet {
|
|
|
973
507
|
cashaddr: string,
|
|
974
508
|
options?: SendRequestOptionsI
|
|
975
509
|
): Promise<SendResponse> {
|
|
976
|
-
return
|
|
977
|
-
}
|
|
978
|
-
|
|
979
|
-
/**
|
|
980
|
-
* sendMaxRaw (internal) Send all available funds to a destination cash address
|
|
981
|
-
*
|
|
982
|
-
* @param {string} cashaddr destination cash address
|
|
983
|
-
* @param {SendRequestOptionsI} options Options of the send requests
|
|
984
|
-
*
|
|
985
|
-
* @returns the transaction id sent to the network
|
|
986
|
-
*/
|
|
987
|
-
private async sendMaxRaw(
|
|
988
|
-
cashaddr: string,
|
|
989
|
-
options?: SendRequestOptionsI
|
|
990
|
-
): Promise<SendResponse> {
|
|
991
|
-
const { value: maxSpendableAmount, utxos } = await this._getMaxAmountToSend(
|
|
992
|
-
{
|
|
993
|
-
outputCount: 1,
|
|
994
|
-
options: options,
|
|
995
|
-
}
|
|
996
|
-
);
|
|
997
|
-
|
|
998
|
-
if (!options) {
|
|
999
|
-
options = {};
|
|
1000
|
-
}
|
|
1001
|
-
|
|
1002
|
-
options.utxoIds = utxos;
|
|
1003
|
-
|
|
1004
|
-
const sendRequest = new SendRequest({
|
|
1005
|
-
cashaddr: cashaddr,
|
|
1006
|
-
value: maxSpendableAmount,
|
|
1007
|
-
unit: "sat",
|
|
1008
|
-
});
|
|
1009
|
-
|
|
1010
|
-
const { encodedTransaction, tokenIds, sourceOutputs } =
|
|
1011
|
-
await this.encodeTransaction([sendRequest], true, options);
|
|
1012
|
-
|
|
1013
|
-
const resp = new SendResponse({});
|
|
1014
|
-
resp.tokenIds = tokenIds;
|
|
1015
|
-
|
|
1016
|
-
if (options?.buildUnsigned !== true) {
|
|
1017
|
-
const txId = await this.submitTransaction(
|
|
1018
|
-
encodedTransaction,
|
|
1019
|
-
options?.awaitTransactionPropagation === undefined ||
|
|
1020
|
-
options?.awaitTransactionPropagation === true
|
|
1021
|
-
);
|
|
1022
|
-
|
|
1023
|
-
resp.txId = txId;
|
|
1024
|
-
resp.explorerUrl = this.explorerUrl(resp.txId);
|
|
1025
|
-
|
|
1026
|
-
if (
|
|
1027
|
-
options?.queryBalance === undefined ||
|
|
1028
|
-
options?.queryBalance === true
|
|
1029
|
-
) {
|
|
1030
|
-
resp.balance = (await this.getBalance()) as BalanceResponse;
|
|
1031
|
-
}
|
|
1032
|
-
} else {
|
|
1033
|
-
resp.unsignedTransaction = binToHex(encodedTransaction);
|
|
1034
|
-
resp.sourceOutputs = sourceOutputs;
|
|
1035
|
-
}
|
|
1036
|
-
|
|
1037
|
-
return resp;
|
|
510
|
+
return this.sendMaxRaw(cashaddr, options, this.privateKey);
|
|
1038
511
|
}
|
|
1039
512
|
|
|
1040
513
|
/**
|
|
@@ -1051,208 +524,15 @@ export class Wallet extends BaseWallet {
|
|
|
1051
524
|
| Array<SendRequest | TokenSendRequest | OpReturnData>
|
|
1052
525
|
| SendRequestArray[],
|
|
1053
526
|
discardChange: boolean = false,
|
|
1054
|
-
options?: SendRequestOptionsI
|
|
527
|
+
options?: SendRequestOptionsI,
|
|
528
|
+
privateKey?: Uint8Array
|
|
1055
529
|
) {
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
);
|
|
1062
|
-
}
|
|
1063
|
-
if (!this.cashaddr) {
|
|
1064
|
-
throw Error("attempted to send without a cashaddr");
|
|
1065
|
-
}
|
|
1066
|
-
|
|
1067
|
-
if (options && options.slpSemiAware) {
|
|
1068
|
-
this._slpSemiAware = true;
|
|
1069
|
-
}
|
|
1070
|
-
|
|
1071
|
-
let feePaidBy;
|
|
1072
|
-
if (options && options.feePaidBy) {
|
|
1073
|
-
feePaidBy = options.feePaidBy;
|
|
1074
|
-
} else {
|
|
1075
|
-
feePaidBy = FeePaidByEnum.change;
|
|
1076
|
-
}
|
|
1077
|
-
|
|
1078
|
-
let changeAddress;
|
|
1079
|
-
if (options && options.changeAddress) {
|
|
1080
|
-
changeAddress = options.changeAddress;
|
|
1081
|
-
} else {
|
|
1082
|
-
changeAddress = this.cashaddr!;
|
|
1083
|
-
}
|
|
1084
|
-
|
|
1085
|
-
let checkTokenQuantities: boolean = true;
|
|
1086
|
-
if (options && options.checkTokenQuantities === false) {
|
|
1087
|
-
checkTokenQuantities = false;
|
|
1088
|
-
}
|
|
1089
|
-
|
|
1090
|
-
// get inputs from options or query all inputs
|
|
1091
|
-
let utxos: UtxoI[];
|
|
1092
|
-
if (options && options.utxoIds) {
|
|
1093
|
-
utxos = await checkUtxos(
|
|
1094
|
-
options.utxoIds.map((utxoId: UtxoI | string) =>
|
|
1095
|
-
typeof utxoId === "string" ? fromUtxoId(utxoId) : utxoId
|
|
1096
|
-
),
|
|
1097
|
-
this
|
|
1098
|
-
);
|
|
1099
|
-
} else {
|
|
1100
|
-
utxos = await this.getAddressUtxos(this.cashaddr);
|
|
1101
|
-
}
|
|
1102
|
-
|
|
1103
|
-
// filter out token utxos if there are no token requests
|
|
1104
|
-
if (
|
|
1105
|
-
checkTokenQuantities &&
|
|
1106
|
-
!sendRequests.some((val) => val instanceof TokenSendRequest)
|
|
1107
|
-
) {
|
|
1108
|
-
utxos = utxos.filter((val) => !val.token);
|
|
1109
|
-
}
|
|
1110
|
-
|
|
1111
|
-
const addTokenChangeOutputs = (
|
|
1112
|
-
inputs: UtxoI[],
|
|
1113
|
-
outputs: SendRequestType[]
|
|
1114
|
-
) => {
|
|
1115
|
-
// Allow for implicit token burn if the total amount sent is less than user had
|
|
1116
|
-
// allow for token genesis, creating more tokens than we had before (0)
|
|
1117
|
-
if (!checkTokenQuantities) {
|
|
1118
|
-
return;
|
|
1119
|
-
}
|
|
1120
|
-
const allTokenInputs = inputs.filter((val) => val.token);
|
|
1121
|
-
const allTokenOutputs = outputs.filter(
|
|
1122
|
-
(val) => val instanceof TokenSendRequest
|
|
1123
|
-
) as TokenSendRequest[];
|
|
1124
|
-
const tokenIds = allTokenOutputs
|
|
1125
|
-
.map((val) => val.tokenId)
|
|
1126
|
-
.filter((val, idx, arr) => arr.indexOf(val) === idx);
|
|
1127
|
-
for (let tokenId of tokenIds) {
|
|
1128
|
-
const tokenInputs = allTokenInputs.filter(
|
|
1129
|
-
(val) => val.token?.tokenId === tokenId
|
|
1130
|
-
);
|
|
1131
|
-
const inputAmountSum = tokenInputs.reduce(
|
|
1132
|
-
(prev, cur) => prev + cur.token!.amount,
|
|
1133
|
-
0n
|
|
1134
|
-
);
|
|
1135
|
-
const tokenOutputs = allTokenOutputs.filter(
|
|
1136
|
-
(val) => val.tokenId === tokenId
|
|
1137
|
-
);
|
|
1138
|
-
const outputAmountSum = tokenOutputs.reduce(
|
|
1139
|
-
(prev, cur) => prev + cur.amount,
|
|
1140
|
-
0n
|
|
1141
|
-
);
|
|
1142
|
-
|
|
1143
|
-
const diff = inputAmountSum - outputAmountSum;
|
|
1144
|
-
if (diff < 0) {
|
|
1145
|
-
throw new Error("Not enough token amount to send");
|
|
1146
|
-
}
|
|
1147
|
-
if (diff >= 0) {
|
|
1148
|
-
let available = 0n;
|
|
1149
|
-
let change = 0n;
|
|
1150
|
-
const ensureUtxos: UtxoI[] = [];
|
|
1151
|
-
for (const token of tokenInputs.filter((val) => val.token?.amount)) {
|
|
1152
|
-
ensureUtxos.push(token);
|
|
1153
|
-
available += token.token?.amount!;
|
|
1154
|
-
if (available >= outputAmountSum) {
|
|
1155
|
-
change = available - outputAmountSum;
|
|
1156
|
-
//break;
|
|
1157
|
-
}
|
|
1158
|
-
}
|
|
1159
|
-
if (ensureUtxos.length) {
|
|
1160
|
-
if (!options) {
|
|
1161
|
-
options = {};
|
|
1162
|
-
}
|
|
1163
|
-
options!.ensureUtxos = [
|
|
1164
|
-
...(options.ensureUtxos ?? []),
|
|
1165
|
-
...ensureUtxos,
|
|
1166
|
-
].filter(
|
|
1167
|
-
(val, index, array) =>
|
|
1168
|
-
array.findIndex(
|
|
1169
|
-
(other) => other.txid === val.txid && other.vout === val.vout
|
|
1170
|
-
) === index
|
|
1171
|
-
);
|
|
1172
|
-
}
|
|
1173
|
-
if (change > 0) {
|
|
1174
|
-
outputs.push(
|
|
1175
|
-
new TokenSendRequest({
|
|
1176
|
-
cashaddr: toTokenaddr(changeAddress) || this.tokenaddr!,
|
|
1177
|
-
amount: change,
|
|
1178
|
-
tokenId: tokenId,
|
|
1179
|
-
commitment: tokenOutputs[0].commitment,
|
|
1180
|
-
capability: tokenOutputs[0].capability,
|
|
1181
|
-
value: tokenOutputs[0].value,
|
|
1182
|
-
})
|
|
1183
|
-
);
|
|
1184
|
-
}
|
|
1185
|
-
}
|
|
1186
|
-
}
|
|
1187
|
-
};
|
|
1188
|
-
addTokenChangeOutputs(utxos, sendRequests);
|
|
1189
|
-
|
|
1190
|
-
const bestHeight = await this.provider!.getBlockHeight()!;
|
|
1191
|
-
const spendAmount = await sumSendRequestAmounts(sendRequests);
|
|
1192
|
-
|
|
1193
|
-
if (utxos.length === 0) {
|
|
1194
|
-
throw Error("There were no Unspent Outputs");
|
|
1195
|
-
}
|
|
1196
|
-
if (typeof spendAmount !== "bigint") {
|
|
1197
|
-
throw Error("Couldn't get spend amount when building transaction");
|
|
1198
|
-
}
|
|
1199
|
-
|
|
1200
|
-
const relayFeePerByteInSatoshi = await getRelayFeeCache(this.provider!);
|
|
1201
|
-
const feeEstimate = await getFeeAmountSimple({
|
|
1202
|
-
utxos: utxos,
|
|
1203
|
-
sendRequests: sendRequests,
|
|
1204
|
-
privateKey: this.privateKey ?? hexToBin(placeholderPrivateKey),
|
|
1205
|
-
sourceAddress: this.cashaddr!,
|
|
1206
|
-
relayFeePerByteInSatoshi: relayFeePerByteInSatoshi,
|
|
1207
|
-
feePaidBy: feePaidBy,
|
|
1208
|
-
});
|
|
1209
|
-
|
|
1210
|
-
const fundingUtxos = await getSuitableUtxos(
|
|
1211
|
-
utxos,
|
|
1212
|
-
BigInt(spendAmount) + BigInt(Math.ceil(feeEstimate)),
|
|
1213
|
-
bestHeight,
|
|
1214
|
-
feePaidBy,
|
|
1215
|
-
sendRequests,
|
|
1216
|
-
options?.ensureUtxos || [],
|
|
1217
|
-
options?.tokenOperation
|
|
530
|
+
return super.encodeTransaction(
|
|
531
|
+
requests,
|
|
532
|
+
discardChange,
|
|
533
|
+
options,
|
|
534
|
+
this.privateKey
|
|
1218
535
|
);
|
|
1219
|
-
if (fundingUtxos.length === 0) {
|
|
1220
|
-
throw Error(
|
|
1221
|
-
"The available inputs couldn't satisfy the request with fees"
|
|
1222
|
-
);
|
|
1223
|
-
}
|
|
1224
|
-
const fee = await getFeeAmount({
|
|
1225
|
-
utxos: fundingUtxos,
|
|
1226
|
-
sendRequests: sendRequests,
|
|
1227
|
-
privateKey: this.privateKey ?? hexToBin(placeholderPrivateKey),
|
|
1228
|
-
sourceAddress: this.cashaddr!,
|
|
1229
|
-
relayFeePerByteInSatoshi: relayFeePerByteInSatoshi,
|
|
1230
|
-
feePaidBy: feePaidBy,
|
|
1231
|
-
});
|
|
1232
|
-
const { encodedTransaction, sourceOutputs } = await buildEncodedTransaction(
|
|
1233
|
-
{
|
|
1234
|
-
inputs: fundingUtxos,
|
|
1235
|
-
outputs: sendRequests,
|
|
1236
|
-
signingKey: this.privateKey ?? hexToBin(placeholderPrivateKey),
|
|
1237
|
-
sourceAddress: this.cashaddr!,
|
|
1238
|
-
fee,
|
|
1239
|
-
discardChange,
|
|
1240
|
-
feePaidBy,
|
|
1241
|
-
changeAddress,
|
|
1242
|
-
buildUnsigned: options?.buildUnsigned === true,
|
|
1243
|
-
}
|
|
1244
|
-
);
|
|
1245
|
-
|
|
1246
|
-
const tokenIds = [
|
|
1247
|
-
...fundingUtxos
|
|
1248
|
-
.filter((val) => val.token?.tokenId)
|
|
1249
|
-
.map((val) => val.token!.tokenId),
|
|
1250
|
-
...sendRequests
|
|
1251
|
-
.filter((val) => val instanceof TokenSendRequest)
|
|
1252
|
-
.map((val) => (val as TokenSendRequest).tokenId),
|
|
1253
|
-
].filter((value, index, array) => array.indexOf(value) === index);
|
|
1254
|
-
|
|
1255
|
-
return { encodedTransaction, tokenIds, sourceOutputs };
|
|
1256
536
|
}
|
|
1257
537
|
|
|
1258
538
|
public async signUnsignedTransaction(
|
|
@@ -1263,202 +543,19 @@ export class Wallet extends BaseWallet {
|
|
|
1263
543
|
throw Error("Can not sign a transaction with watch-only wallet.");
|
|
1264
544
|
}
|
|
1265
545
|
|
|
1266
|
-
return signUnsignedTransaction(
|
|
1267
|
-
transaction,
|
|
1268
|
-
sourceOutputs,
|
|
1269
|
-
this.privateKey!
|
|
1270
|
-
);
|
|
1271
|
-
}
|
|
1272
|
-
|
|
1273
|
-
// Submit a raw transaction
|
|
1274
|
-
public async submitTransaction(
|
|
1275
|
-
transaction: Uint8Array,
|
|
1276
|
-
awaitPropagation: boolean = true
|
|
1277
|
-
): Promise<string> {
|
|
1278
|
-
if (!this.provider) {
|
|
1279
|
-
throw Error("Wallet network provider was not initialized");
|
|
1280
|
-
}
|
|
1281
|
-
let rawTransaction = binToHex(transaction);
|
|
1282
|
-
return await this.provider.sendRawTransaction(
|
|
1283
|
-
rawTransaction,
|
|
1284
|
-
awaitPropagation
|
|
1285
|
-
);
|
|
1286
|
-
}
|
|
1287
|
-
|
|
1288
|
-
// gets transaction history of this wallet
|
|
1289
|
-
public async getRawHistory(
|
|
1290
|
-
fromHeight: number = 0,
|
|
1291
|
-
toHeight: number = -1
|
|
1292
|
-
): Promise<TxI[]> {
|
|
1293
|
-
return await this.provider!.getHistory(
|
|
1294
|
-
this.cashaddr!,
|
|
1295
|
-
fromHeight,
|
|
1296
|
-
toHeight
|
|
1297
|
-
);
|
|
1298
|
-
}
|
|
1299
|
-
|
|
1300
|
-
/**
|
|
1301
|
-
* getHistory gets transaction history of this wallet with most data decoded and ready to present to user
|
|
1302
|
-
* @note balance calculations are valid only if querying to the blockchain tip (`toHeight` === -1, `count` === -1)
|
|
1303
|
-
* @note this method is heavy on network calls, if invoked in browser use of cache is advised, @see `Config.UseLocalStorageCache`
|
|
1304
|
-
* @note this method tries to recreate the history tab view of Electron Cash wallet, however, it may not be 100% accurate if the tnransaction value changes are the same in the same block (ordering)
|
|
1305
|
-
*
|
|
1306
|
-
* @param unit optional, BCH or currency unit to present balance and balance changes. If unit is currency like USD or EUR, balances will be subject to possible rounding errors. Default 0
|
|
1307
|
-
* @param fromHeight optional, if set, history will be limited. Default 0
|
|
1308
|
-
* @param toHeight optional, if set, history will be limited. Default -1, meaning that all history items will be returned, including mempool
|
|
1309
|
-
* @param start optional, if set, the result set will be paginated with offset `start`
|
|
1310
|
-
* @param count optional, if set, the result set will be paginated with `count`. Default -1, meaning that all history items will be returned
|
|
1311
|
-
*
|
|
1312
|
-
* @returns an array of transaction history items, with input values and addresses encoded in cashaddress format. @see `TransactionHistoryItem` type
|
|
1313
|
-
*/
|
|
1314
|
-
public async getHistory({
|
|
1315
|
-
unit = "sat",
|
|
1316
|
-
fromHeight = 0,
|
|
1317
|
-
toHeight = -1,
|
|
1318
|
-
start = 0,
|
|
1319
|
-
count = -1,
|
|
1320
|
-
}: {
|
|
1321
|
-
unit?: UnitEnum;
|
|
1322
|
-
fromHeight?: number;
|
|
1323
|
-
toHeight?: number;
|
|
1324
|
-
start?: number;
|
|
1325
|
-
count?: number;
|
|
1326
|
-
}): Promise<TransactionHistoryItem[]> {
|
|
1327
|
-
return getAddressHistory({
|
|
1328
|
-
address: this.cashaddr!,
|
|
1329
|
-
provider: this.provider!,
|
|
1330
|
-
unit,
|
|
1331
|
-
fromHeight,
|
|
1332
|
-
toHeight,
|
|
1333
|
-
start,
|
|
1334
|
-
count,
|
|
1335
|
-
});
|
|
1336
|
-
}
|
|
1337
|
-
|
|
1338
|
-
// gets last transaction of this wallet
|
|
1339
|
-
public async getLastTransaction(
|
|
1340
|
-
confirmedOnly: boolean = false
|
|
1341
|
-
): Promise<ElectrumRawTransaction | null> {
|
|
1342
|
-
let history: TxI[] = await this.getRawHistory();
|
|
1343
|
-
if (confirmedOnly) {
|
|
1344
|
-
history = history.filter((val) => val.height > 0);
|
|
1345
|
-
}
|
|
1346
|
-
|
|
1347
|
-
if (!history.length) {
|
|
1348
|
-
return null;
|
|
1349
|
-
}
|
|
1350
|
-
|
|
1351
|
-
const [lastTx] = history.slice(-1);
|
|
1352
|
-
return this.provider!.getRawTransactionObject(lastTx.tx_hash);
|
|
1353
|
-
}
|
|
1354
|
-
|
|
1355
|
-
// waits for next transaction, program execution is halted
|
|
1356
|
-
public async waitForTransaction(
|
|
1357
|
-
options: WaitForTransactionOptions = {
|
|
1358
|
-
getTransactionInfo: true,
|
|
1359
|
-
getBalance: false,
|
|
1360
|
-
txHash: undefined,
|
|
1361
|
-
}
|
|
1362
|
-
): Promise<WaitForTransactionResponse> {
|
|
1363
|
-
if (options.getTransactionInfo === undefined) {
|
|
1364
|
-
options.getTransactionInfo = true;
|
|
1365
|
-
}
|
|
1366
|
-
|
|
1367
|
-
return new Promise(async (resolve) => {
|
|
1368
|
-
let txHashSeen = false;
|
|
1369
|
-
|
|
1370
|
-
const makeResponse = async (txHash?: string) => {
|
|
1371
|
-
const response = <WaitForTransactionResponse>{};
|
|
1372
|
-
const promises: any[] = [undefined, undefined];
|
|
1373
|
-
|
|
1374
|
-
if (options.getBalance === true) {
|
|
1375
|
-
promises[0] = this.getBalance();
|
|
1376
|
-
}
|
|
1377
|
-
|
|
1378
|
-
if (options.getTransactionInfo === true) {
|
|
1379
|
-
if (!txHash) {
|
|
1380
|
-
promises[1] = this.getLastTransaction();
|
|
1381
|
-
} else {
|
|
1382
|
-
promises[1] = this.provider!.getRawTransactionObject(txHash);
|
|
1383
|
-
}
|
|
1384
|
-
}
|
|
1385
|
-
|
|
1386
|
-
const result = await Promise.all(promises);
|
|
1387
|
-
response.balance = result[0];
|
|
1388
|
-
response.transactionInfo = result[1];
|
|
1389
|
-
|
|
1390
|
-
return response;
|
|
1391
|
-
};
|
|
1392
|
-
|
|
1393
|
-
// waiting for a specific transaction to propagate
|
|
1394
|
-
if (options.txHash) {
|
|
1395
|
-
const waitForTransactionCallback = async (data) => {
|
|
1396
|
-
if (data && data[0] === options.txHash!) {
|
|
1397
|
-
txHashSeen = true;
|
|
1398
|
-
this.provider!.unsubscribeFromTransaction(
|
|
1399
|
-
options.txHash!,
|
|
1400
|
-
waitForTransactionCallback
|
|
1401
|
-
);
|
|
1402
|
-
|
|
1403
|
-
resolve(makeResponse(options.txHash!));
|
|
1404
|
-
}
|
|
1405
|
-
};
|
|
1406
|
-
|
|
1407
|
-
this.provider!.subscribeToTransaction(
|
|
1408
|
-
options.txHash,
|
|
1409
|
-
waitForTransactionCallback
|
|
1410
|
-
);
|
|
1411
|
-
return;
|
|
1412
|
-
}
|
|
1413
|
-
|
|
1414
|
-
// waiting for any address transaction
|
|
1415
|
-
const watchCancel = (
|
|
1416
|
-
this.provider! as ElectrumNetworkProvider
|
|
1417
|
-
).watchAddressStatus(this.getDepositAddress(), async (_status) => {
|
|
1418
|
-
watchCancel();
|
|
1419
|
-
resolve(makeResponse());
|
|
1420
|
-
});
|
|
1421
|
-
});
|
|
1422
|
-
}
|
|
1423
|
-
|
|
1424
|
-
/**
|
|
1425
|
-
* watchBlocks Watch network blocks
|
|
1426
|
-
*
|
|
1427
|
-
* @param callback callback with a block header object
|
|
1428
|
-
* @param skipCurrentHeight if set, the notification about current block will not arrive
|
|
1429
|
-
*
|
|
1430
|
-
* @returns a function which will cancel watching upon evaluation
|
|
1431
|
-
*/
|
|
1432
|
-
public watchBlocks(
|
|
1433
|
-
callback: (header: HexHeaderI) => void,
|
|
1434
|
-
skipCurrentHeight: boolean = true
|
|
1435
|
-
): CancelWatchFn {
|
|
1436
|
-
return (this.provider! as ElectrumNetworkProvider).watchBlocks(
|
|
1437
|
-
callback,
|
|
1438
|
-
skipCurrentHeight
|
|
1439
|
-
);
|
|
1440
|
-
}
|
|
1441
|
-
|
|
1442
|
-
/**
|
|
1443
|
-
* waitForBlock Wait for a network block
|
|
1444
|
-
*
|
|
1445
|
-
* @param height if specified waits for this exact blockchain height, otherwise resolves with the next block
|
|
1446
|
-
*
|
|
1447
|
-
*/
|
|
1448
|
-
public async waitForBlock(height?: number): Promise<HexHeaderI> {
|
|
1449
|
-
return (this.provider! as ElectrumNetworkProvider).waitForBlock(height);
|
|
546
|
+
return signUnsignedTransaction(transaction, sourceOutputs, this.privateKey);
|
|
1450
547
|
}
|
|
1451
548
|
//#endregion Funds
|
|
1452
549
|
|
|
1453
550
|
//#region Private implementation details
|
|
1454
551
|
private async deriveInfo() {
|
|
1455
|
-
const publicKey = secp256k1.derivePublicKeyUncompressed(this.privateKey
|
|
552
|
+
const publicKey = secp256k1.derivePublicKeyUncompressed(this.privateKey);
|
|
1456
553
|
if (typeof publicKey === "string") {
|
|
1457
554
|
throw new Error(publicKey);
|
|
1458
555
|
}
|
|
1459
556
|
this.publicKey = publicKey;
|
|
1460
557
|
const publicKeyCompressed = secp256k1.derivePublicKeyCompressed(
|
|
1461
|
-
this.privateKey
|
|
558
|
+
this.privateKey
|
|
1462
559
|
);
|
|
1463
560
|
if (typeof publicKeyCompressed === "string") {
|
|
1464
561
|
throw new Error(publicKeyCompressed);
|
|
@@ -1466,13 +563,12 @@ export class Wallet extends BaseWallet {
|
|
|
1466
563
|
this.publicKeyCompressed = publicKeyCompressed;
|
|
1467
564
|
const networkType =
|
|
1468
565
|
this.network === NetworkType.Regtest ? NetworkType.Testnet : this.network;
|
|
1469
|
-
this.privateKeyWif = encodePrivateKeyWif(this.privateKey
|
|
566
|
+
this.privateKeyWif = encodePrivateKeyWif(this.privateKey, networkType);
|
|
1470
567
|
checkWifNetwork(this.privateKeyWif, this.network);
|
|
1471
568
|
|
|
1472
|
-
this.cashaddr = deriveCashaddr(this.privateKey
|
|
1473
|
-
this.tokenaddr = deriveTokenaddr(this.privateKey
|
|
1474
|
-
this.
|
|
1475
|
-
this.publicKeyHash = derivePublicKeyHash(this.cashaddr!);
|
|
569
|
+
this.cashaddr = deriveCashaddr(this.privateKey, this.networkPrefix);
|
|
570
|
+
this.tokenaddr = deriveTokenaddr(this.privateKey, this.networkPrefix);
|
|
571
|
+
this.publicKeyHash = derivePublicKeyHash(this.cashaddr);
|
|
1476
572
|
return this;
|
|
1477
573
|
}
|
|
1478
574
|
//#endregion Private implementation details
|
|
@@ -1480,350 +576,8 @@ export class Wallet extends BaseWallet {
|
|
|
1480
576
|
//#region Signing
|
|
1481
577
|
// Convenience wrapper to sign interface
|
|
1482
578
|
public async sign(message: string) {
|
|
1483
|
-
return await Wallet.signedMessage.sign(message, this.privateKey
|
|
1484
|
-
}
|
|
1485
|
-
|
|
1486
|
-
// Convenience wrapper to verify interface
|
|
1487
|
-
public async verify(message: string, sig: string, publicKey?: Uint8Array) {
|
|
1488
|
-
return await Wallet.signedMessage.verify(
|
|
1489
|
-
message,
|
|
1490
|
-
sig,
|
|
1491
|
-
this.cashaddr!,
|
|
1492
|
-
publicKey
|
|
1493
|
-
);
|
|
1494
|
-
}
|
|
1495
|
-
//#endregion Signing
|
|
1496
|
-
|
|
1497
|
-
//#region Cashtokens
|
|
1498
|
-
/**
|
|
1499
|
-
* Create new cashtoken, both funglible and/or non-fungible (NFT)
|
|
1500
|
-
* Refer to spec https://github.com/bitjson/cashtokens
|
|
1501
|
-
* @param {number} genesisRequest.amount amount of *fungible* tokens to create
|
|
1502
|
-
* @param {NFTCapability?} genesisRequest.capability capability of new NFT
|
|
1503
|
-
* @param {string?} genesisRequest.commitment NFT commitment message
|
|
1504
|
-
* @param {string?} genesisRequest.cashaddr cash address to send the created token UTXO to; if undefined will default to your address
|
|
1505
|
-
* @param {number?} genesisRequest.value satoshi value to send alongside with tokens; if undefined will default to 1000 satoshi
|
|
1506
|
-
* @param {SendRequestType | SendRequestType[]} sendRequests single or an array of extra send requests (OP_RETURN, value transfer, etc.) to include in genesis transaction
|
|
1507
|
-
* @param {SendRequestOptionsI} options Options of the send requests
|
|
1508
|
-
*/
|
|
1509
|
-
public async tokenGenesis(
|
|
1510
|
-
genesisRequest: TokenGenesisRequest,
|
|
1511
|
-
sendRequests: SendRequestType | SendRequestType[] = [],
|
|
1512
|
-
options?: SendRequestOptionsI
|
|
1513
|
-
): Promise<SendResponse> {
|
|
1514
|
-
if (!Array.isArray(sendRequests)) {
|
|
1515
|
-
sendRequests = [sendRequests];
|
|
1516
|
-
}
|
|
1517
|
-
|
|
1518
|
-
let utxos: UtxoI[];
|
|
1519
|
-
if (options && options.utxoIds) {
|
|
1520
|
-
utxos = await checkUtxos(
|
|
1521
|
-
options.utxoIds.map((utxoId: UtxoI | string) =>
|
|
1522
|
-
typeof utxoId === "string" ? fromUtxoId(utxoId) : utxoId
|
|
1523
|
-
),
|
|
1524
|
-
this
|
|
1525
|
-
);
|
|
1526
|
-
} else {
|
|
1527
|
-
utxos = await this.getAddressUtxos(this.cashaddr);
|
|
1528
|
-
}
|
|
1529
|
-
|
|
1530
|
-
const genesisInputs = utxos.filter((val) => val.vout === 0 && !val.token);
|
|
1531
|
-
if (genesisInputs.length === 0) {
|
|
1532
|
-
throw new Error(
|
|
1533
|
-
"No suitable inputs with vout=0 available for new token genesis"
|
|
1534
|
-
);
|
|
1535
|
-
}
|
|
1536
|
-
|
|
1537
|
-
const genesisSendRequest = new TokenSendRequest({
|
|
1538
|
-
cashaddr: genesisRequest.cashaddr || this.tokenaddr!,
|
|
1539
|
-
amount: genesisRequest.amount,
|
|
1540
|
-
value: genesisRequest.value || 1000,
|
|
1541
|
-
capability: genesisRequest.capability,
|
|
1542
|
-
commitment: genesisRequest.commitment,
|
|
1543
|
-
tokenId: genesisInputs[0].txid,
|
|
1544
|
-
});
|
|
1545
|
-
|
|
1546
|
-
return this.send([genesisSendRequest, ...(sendRequests as any)], {
|
|
1547
|
-
...options,
|
|
1548
|
-
utxoIds: utxos,
|
|
1549
|
-
ensureUtxos: [genesisInputs[0]],
|
|
1550
|
-
checkTokenQuantities: false,
|
|
1551
|
-
queryBalance: false,
|
|
1552
|
-
tokenOperation: "genesis",
|
|
1553
|
-
});
|
|
1554
|
-
}
|
|
1555
|
-
|
|
1556
|
-
/**
|
|
1557
|
-
* Mint new NFT cashtokens using an existing minting token
|
|
1558
|
-
* Refer to spec https://github.com/bitjson/cashtokens
|
|
1559
|
-
* @param {string} tokenId tokenId of an NFT to mint
|
|
1560
|
-
* @param {TokenMintRequest | TokenMintRequest[]} mintRequests mint requests with new token properties and recipients
|
|
1561
|
-
* @param {NFTCapability?} mintRequest.capability capability of new NFT
|
|
1562
|
-
* @param {string?} mintRequest.commitment NFT commitment message
|
|
1563
|
-
* @param {string?} mintRequest.cashaddr cash address to send the created token UTXO to; if undefined will default to your address
|
|
1564
|
-
* @param {number?} mintRequest.value satoshi value to send alongside with tokens; if undefined will default to 1000 satoshi
|
|
1565
|
-
* @param {boolean?} deductTokenAmount if minting token contains fungible amount, deduct from it by amount of minted tokens
|
|
1566
|
-
* @param {SendRequestOptionsI} options Options of the send requests
|
|
1567
|
-
*/
|
|
1568
|
-
public async tokenMint(
|
|
1569
|
-
tokenId: string,
|
|
1570
|
-
mintRequests: TokenMintRequest | Array<TokenMintRequest>,
|
|
1571
|
-
deductTokenAmount: boolean = false,
|
|
1572
|
-
options?: SendRequestOptionsI
|
|
1573
|
-
): Promise<SendResponse> {
|
|
1574
|
-
if (tokenId?.length !== 64) {
|
|
1575
|
-
throw Error(`Invalid tokenId supplied: ${tokenId}`);
|
|
1576
|
-
}
|
|
1577
|
-
|
|
1578
|
-
if (!Array.isArray(mintRequests)) {
|
|
1579
|
-
mintRequests = [mintRequests];
|
|
1580
|
-
}
|
|
1581
|
-
|
|
1582
|
-
const utxos = await this.getAddressUtxos(this.cashaddr!);
|
|
1583
|
-
const nftUtxos = utxos.filter(
|
|
1584
|
-
(val) =>
|
|
1585
|
-
val.token?.tokenId === tokenId &&
|
|
1586
|
-
val.token?.capability === NFTCapability.minting
|
|
1587
|
-
);
|
|
1588
|
-
if (!nftUtxos.length) {
|
|
1589
|
-
throw new Error(
|
|
1590
|
-
"You do not have any token UTXOs with minting capability for specified tokenId"
|
|
1591
|
-
);
|
|
1592
|
-
}
|
|
1593
|
-
const newAmount =
|
|
1594
|
-
deductTokenAmount && nftUtxos[0].token!.amount > 0
|
|
1595
|
-
? nftUtxos[0].token!.amount - BigInt(mintRequests.length)
|
|
1596
|
-
: nftUtxos[0].token!.amount;
|
|
1597
|
-
const safeNewAmount = newAmount < 0n ? 0n : newAmount;
|
|
1598
|
-
const mintingInput = new TokenSendRequest({
|
|
1599
|
-
cashaddr: this.tokenaddr!,
|
|
1600
|
-
tokenId: tokenId,
|
|
1601
|
-
capability: nftUtxos[0].token!.capability,
|
|
1602
|
-
commitment: nftUtxos[0].token!.commitment,
|
|
1603
|
-
amount: safeNewAmount,
|
|
1604
|
-
value: nftUtxos[0].satoshis,
|
|
1605
|
-
});
|
|
1606
|
-
return this.send(
|
|
1607
|
-
[
|
|
1608
|
-
mintingInput,
|
|
1609
|
-
...mintRequests.map(
|
|
1610
|
-
(val) =>
|
|
1611
|
-
new TokenSendRequest({
|
|
1612
|
-
cashaddr: val.cashaddr || this.tokenaddr!,
|
|
1613
|
-
amount: 0,
|
|
1614
|
-
tokenId: tokenId,
|
|
1615
|
-
value: val.value,
|
|
1616
|
-
capability: val.capability,
|
|
1617
|
-
commitment: val.commitment,
|
|
1618
|
-
})
|
|
1619
|
-
),
|
|
1620
|
-
],
|
|
1621
|
-
{
|
|
1622
|
-
...options,
|
|
1623
|
-
ensureUtxos: [nftUtxos[0]],
|
|
1624
|
-
checkTokenQuantities: false,
|
|
1625
|
-
queryBalance: false,
|
|
1626
|
-
tokenOperation: "mint",
|
|
1627
|
-
}
|
|
1628
|
-
);
|
|
1629
|
-
}
|
|
1630
|
-
|
|
1631
|
-
/**
|
|
1632
|
-
* Perform an explicit token burning by spending a token utxo to an OP_RETURN
|
|
1633
|
-
*
|
|
1634
|
-
* Behaves differently for fungible and non-fungible tokens:
|
|
1635
|
-
* * NFTs are always "destroyed"
|
|
1636
|
-
* * FTs' amount is reduced by the amount specified, if 0 FT amount is left and no NFT present, the token is "destroyed"
|
|
1637
|
-
*
|
|
1638
|
-
* Refer to spec https://github.com/bitjson/cashtokens
|
|
1639
|
-
* @param {string} burnRequest.tokenId tokenId of a token to burn
|
|
1640
|
-
* @param {NFTCapability} burnRequest.capability capability of the NFT token to select, optional
|
|
1641
|
-
* @param {string?} burnRequest.commitment commitment of the NFT token to select, optional
|
|
1642
|
-
* @param {number?} burnRequest.amount amount of fungible tokens to burn, optional
|
|
1643
|
-
* @param {string?} burnRequest.cashaddr address to return token and satoshi change to
|
|
1644
|
-
* @param {string?} message optional message to include in OP_RETURN
|
|
1645
|
-
* @param {SendRequestOptionsI} options Options of the send requests
|
|
1646
|
-
*/
|
|
1647
|
-
public async tokenBurn(
|
|
1648
|
-
burnRequest: TokenBurnRequest,
|
|
1649
|
-
message?: string,
|
|
1650
|
-
options?: SendRequestOptionsI
|
|
1651
|
-
): Promise<SendResponse> {
|
|
1652
|
-
if (burnRequest.tokenId?.length !== 64) {
|
|
1653
|
-
throw Error(`Invalid tokenId supplied: ${burnRequest.tokenId}`);
|
|
1654
|
-
}
|
|
1655
|
-
|
|
1656
|
-
const utxos = await this.getAddressUtxos(this.cashaddr!);
|
|
1657
|
-
const tokenUtxos = utxos.filter(
|
|
1658
|
-
(val) =>
|
|
1659
|
-
val.token?.tokenId === burnRequest.tokenId &&
|
|
1660
|
-
val.token?.capability === burnRequest.capability &&
|
|
1661
|
-
val.token?.commitment === burnRequest.commitment
|
|
1662
|
-
);
|
|
1663
|
-
|
|
1664
|
-
if (!tokenUtxos.length) {
|
|
1665
|
-
throw new Error("You do not have suitable token UTXOs to perform burn");
|
|
1666
|
-
}
|
|
1667
|
-
|
|
1668
|
-
const totalFungibleAmount = tokenUtxos.reduce(
|
|
1669
|
-
(prev, cur) => prev + (cur.token?.amount || 0n),
|
|
1670
|
-
0n
|
|
1671
|
-
);
|
|
1672
|
-
let fungibleBurnAmount =
|
|
1673
|
-
burnRequest.amount && burnRequest.amount > 0 ? burnRequest.amount! : 0n;
|
|
1674
|
-
fungibleBurnAmount = BigInt(fungibleBurnAmount);
|
|
1675
|
-
const hasNFT = burnRequest.capability || burnRequest.commitment;
|
|
1676
|
-
|
|
1677
|
-
let utxoIds: UtxoI[] = [];
|
|
1678
|
-
let changeSendRequests: TokenSendRequest[];
|
|
1679
|
-
if (hasNFT) {
|
|
1680
|
-
// does not have FT tokens, let us destroy the token completely
|
|
1681
|
-
if (totalFungibleAmount === 0n) {
|
|
1682
|
-
changeSendRequests = [];
|
|
1683
|
-
utxoIds.push(tokenUtxos[0]);
|
|
1684
|
-
} else {
|
|
1685
|
-
// add utxos to spend from
|
|
1686
|
-
let available = 0n;
|
|
1687
|
-
for (const token of tokenUtxos.filter((val) => val.token?.amount)) {
|
|
1688
|
-
utxoIds.push(token);
|
|
1689
|
-
available += token.token?.amount!;
|
|
1690
|
-
if (available >= fungibleBurnAmount) {
|
|
1691
|
-
break;
|
|
1692
|
-
}
|
|
1693
|
-
}
|
|
1694
|
-
|
|
1695
|
-
// if there are FT, reduce their amount
|
|
1696
|
-
const newAmount = totalFungibleAmount - fungibleBurnAmount;
|
|
1697
|
-
const safeNewAmount = newAmount < 0n ? 0n : newAmount;
|
|
1698
|
-
changeSendRequests = [
|
|
1699
|
-
new TokenSendRequest({
|
|
1700
|
-
cashaddr: burnRequest.cashaddr || this.tokenaddr!,
|
|
1701
|
-
tokenId: burnRequest.tokenId,
|
|
1702
|
-
capability: burnRequest.capability,
|
|
1703
|
-
commitment: burnRequest.commitment,
|
|
1704
|
-
amount: safeNewAmount,
|
|
1705
|
-
value: tokenUtxos[0].satoshis,
|
|
1706
|
-
}),
|
|
1707
|
-
];
|
|
1708
|
-
}
|
|
1709
|
-
} else {
|
|
1710
|
-
// if we are burning last fungible tokens, let us destroy the token completely
|
|
1711
|
-
if (totalFungibleAmount === fungibleBurnAmount) {
|
|
1712
|
-
changeSendRequests = [];
|
|
1713
|
-
utxoIds.push(...tokenUtxos);
|
|
1714
|
-
} else {
|
|
1715
|
-
// add utxos to spend from
|
|
1716
|
-
let available = 0n;
|
|
1717
|
-
for (const token of tokenUtxos.filter((val) => val.token?.amount)) {
|
|
1718
|
-
utxoIds.push(token);
|
|
1719
|
-
available += token.token?.amount!;
|
|
1720
|
-
if (available >= fungibleBurnAmount) {
|
|
1721
|
-
break;
|
|
1722
|
-
}
|
|
1723
|
-
}
|
|
1724
|
-
|
|
1725
|
-
// reduce the FT amount
|
|
1726
|
-
const newAmount = available - fungibleBurnAmount;
|
|
1727
|
-
const safeNewAmount = newAmount < 0n ? 0n : newAmount;
|
|
1728
|
-
changeSendRequests = [
|
|
1729
|
-
new TokenSendRequest({
|
|
1730
|
-
cashaddr: burnRequest.cashaddr || this.tokenaddr!,
|
|
1731
|
-
tokenId: burnRequest.tokenId,
|
|
1732
|
-
amount: safeNewAmount,
|
|
1733
|
-
value: tokenUtxos.reduce((a, c) => a + c.satoshis, 0),
|
|
1734
|
-
}),
|
|
1735
|
-
];
|
|
1736
|
-
}
|
|
1737
|
-
}
|
|
1738
|
-
|
|
1739
|
-
const opReturn = OpReturnData.fromString(message || "");
|
|
1740
|
-
return this.send([opReturn, ...changeSendRequests], {
|
|
1741
|
-
...options,
|
|
1742
|
-
checkTokenQuantities: false,
|
|
1743
|
-
queryBalance: false,
|
|
1744
|
-
ensureUtxos: utxoIds.length > 0 ? utxoIds : undefined,
|
|
1745
|
-
tokenOperation: "burn",
|
|
1746
|
-
});
|
|
1747
|
-
}
|
|
1748
|
-
|
|
1749
|
-
/**
|
|
1750
|
-
* getTokenUtxos Get unspent token outputs for the wallet
|
|
1751
|
-
* will return utxos only for the specified token if `tokenId` provided
|
|
1752
|
-
* @param {string?} tokenId tokenId (category) to filter utxos by, if not set will return utxos from all tokens
|
|
1753
|
-
* @returns {UtxoI[]} token utxos
|
|
1754
|
-
*/
|
|
1755
|
-
public async getTokenUtxos(tokenId?: string): Promise<UtxoI[]> {
|
|
1756
|
-
const utxos = await this.getAddressUtxos(this.address!);
|
|
1757
|
-
return utxos.filter((val) =>
|
|
1758
|
-
tokenId ? val.token?.tokenId === tokenId : val.token
|
|
1759
|
-
);
|
|
1760
|
-
}
|
|
1761
|
-
|
|
1762
|
-
/**
|
|
1763
|
-
* getTokenBalance Gets fungible token balance
|
|
1764
|
-
* for NFT token balance see @ref getNftTokenBalance
|
|
1765
|
-
* @param {string} tokenId tokenId to get balance for
|
|
1766
|
-
* @returns {bigint} fungible token balance
|
|
1767
|
-
*/
|
|
1768
|
-
public async getTokenBalance(tokenId: string): Promise<bigint> {
|
|
1769
|
-
const utxos = (await this.getTokenUtxos(tokenId)).filter(
|
|
1770
|
-
(val) => val.token?.amount
|
|
1771
|
-
);
|
|
1772
|
-
return sumTokenAmounts(utxos, tokenId);
|
|
1773
|
-
}
|
|
1774
|
-
|
|
1775
|
-
/**
|
|
1776
|
-
* getNftTokenBalance Gets non-fungible token (NFT) balance for a particular tokenId
|
|
1777
|
-
* disregards fungible token balances
|
|
1778
|
-
* for fungible token balance see @ref getTokenBalance
|
|
1779
|
-
* @param {string} tokenId tokenId to get balance for
|
|
1780
|
-
* @returns {number} non-fungible token balance
|
|
1781
|
-
*/
|
|
1782
|
-
public async getNftTokenBalance(tokenId: string): Promise<number> {
|
|
1783
|
-
const utxos = (await this.getTokenUtxos(tokenId)).filter(
|
|
1784
|
-
(val) => val.token?.commitment !== undefined
|
|
1785
|
-
);
|
|
1786
|
-
return utxos.length;
|
|
1787
|
-
}
|
|
1788
|
-
|
|
1789
|
-
/**
|
|
1790
|
-
* getAllTokenBalances Gets all fungible token balances in this wallet
|
|
1791
|
-
* @returns {Object} a map [tokenId => balance] for all tokens in this wallet
|
|
1792
|
-
*/
|
|
1793
|
-
public async getAllTokenBalances(): Promise<{ [tokenId: string]: bigint }> {
|
|
1794
|
-
const result = {};
|
|
1795
|
-
const utxos = (await this.getTokenUtxos()).filter(
|
|
1796
|
-
(val) => val.token?.amount
|
|
1797
|
-
);
|
|
1798
|
-
for (const utxo of utxos) {
|
|
1799
|
-
if (!result[utxo.token!.tokenId]) {
|
|
1800
|
-
result[utxo.token!.tokenId] = 0n;
|
|
1801
|
-
}
|
|
1802
|
-
result[utxo.token!.tokenId] += utxo.token!.amount;
|
|
1803
|
-
}
|
|
1804
|
-
return result;
|
|
1805
|
-
}
|
|
1806
|
-
|
|
1807
|
-
/**
|
|
1808
|
-
* getAllNftTokenBalances Gets all non-fungible token (NFT) balances in this wallet
|
|
1809
|
-
* @returns {Object} a map [tokenId => balance] for all NFTs in this wallet
|
|
1810
|
-
*/
|
|
1811
|
-
public async getAllNftTokenBalances(): Promise<{
|
|
1812
|
-
[tokenId: string]: number;
|
|
1813
|
-
}> {
|
|
1814
|
-
const result = {};
|
|
1815
|
-
const utxos = (await this.getTokenUtxos()).filter(
|
|
1816
|
-
(val) => val.token?.commitment !== undefined
|
|
1817
|
-
);
|
|
1818
|
-
for (const utxo of utxos) {
|
|
1819
|
-
if (!result[utxo.token!.tokenId]) {
|
|
1820
|
-
result[utxo.token!.tokenId] = 0;
|
|
1821
|
-
}
|
|
1822
|
-
result[utxo.token!.tokenId] += 1;
|
|
1823
|
-
}
|
|
1824
|
-
return result;
|
|
579
|
+
return await Wallet.signedMessage.sign(message, this.privateKey);
|
|
1825
580
|
}
|
|
1826
|
-
//#endregion Cashtokens
|
|
1827
581
|
}
|
|
1828
582
|
|
|
1829
583
|
/**
|
|
@@ -1835,11 +589,6 @@ export class TestNetWallet extends Wallet {
|
|
|
1835
589
|
constructor(name = "") {
|
|
1836
590
|
super(name, NetworkType.Testnet);
|
|
1837
591
|
}
|
|
1838
|
-
|
|
1839
|
-
// interface to static util functions. see Util.ts
|
|
1840
|
-
public static get util() {
|
|
1841
|
-
return TestNetUtil;
|
|
1842
|
-
}
|
|
1843
592
|
}
|
|
1844
593
|
|
|
1845
594
|
/**
|
|
@@ -1850,11 +599,6 @@ export class RegTestWallet extends Wallet {
|
|
|
1850
599
|
constructor(name = "") {
|
|
1851
600
|
super(name, NetworkType.Regtest);
|
|
1852
601
|
}
|
|
1853
|
-
|
|
1854
|
-
// interface to static util functions. see Util.ts
|
|
1855
|
-
public static get util() {
|
|
1856
|
-
return RegTestUtil;
|
|
1857
|
-
}
|
|
1858
602
|
}
|
|
1859
603
|
|
|
1860
604
|
/**
|
|
@@ -1866,11 +610,6 @@ export class WifWallet extends Wallet {
|
|
|
1866
610
|
constructor(name = "") {
|
|
1867
611
|
super(name, NetworkType.Mainnet, WalletTypeEnum.Wif);
|
|
1868
612
|
}
|
|
1869
|
-
|
|
1870
|
-
// interface to static util functions. see Util.ts
|
|
1871
|
-
public static get util() {
|
|
1872
|
-
return WifUtil;
|
|
1873
|
-
}
|
|
1874
613
|
}
|
|
1875
614
|
|
|
1876
615
|
/**
|
|
@@ -1882,11 +621,6 @@ export class TestNetWifWallet extends Wallet {
|
|
|
1882
621
|
constructor(name = "") {
|
|
1883
622
|
super(name, NetworkType.Testnet, WalletTypeEnum.Wif);
|
|
1884
623
|
}
|
|
1885
|
-
|
|
1886
|
-
// interface to static util functions. see Util.ts
|
|
1887
|
-
public static get util() {
|
|
1888
|
-
return TestNetWifUtil;
|
|
1889
|
-
}
|
|
1890
624
|
}
|
|
1891
625
|
|
|
1892
626
|
/**
|
|
@@ -1898,57 +632,4 @@ export class RegTestWifWallet extends Wallet {
|
|
|
1898
632
|
constructor(name = "") {
|
|
1899
633
|
super(name, NetworkType.Regtest, WalletTypeEnum.Wif);
|
|
1900
634
|
}
|
|
1901
|
-
|
|
1902
|
-
// interface to static util functions. see Util.ts
|
|
1903
|
-
public static get util() {
|
|
1904
|
-
return RegTestWifUtil;
|
|
1905
|
-
}
|
|
1906
|
-
}
|
|
1907
|
-
|
|
1908
|
-
/**
|
|
1909
|
-
* Class to manage a bitcoin cash watch wallet.
|
|
1910
|
-
*/
|
|
1911
|
-
export class WatchWallet extends Wallet {
|
|
1912
|
-
static networkPrefix = CashAddressNetworkPrefix.mainnet;
|
|
1913
|
-
static walletType = WalletTypeEnum.Watch;
|
|
1914
|
-
constructor(name = "") {
|
|
1915
|
-
super(name, NetworkType.Mainnet, WalletTypeEnum.Watch);
|
|
1916
|
-
}
|
|
1917
|
-
|
|
1918
|
-
// interface to static util functions. see Util.ts
|
|
1919
|
-
public static get util() {
|
|
1920
|
-
return WatchUtil;
|
|
1921
|
-
}
|
|
1922
|
-
}
|
|
1923
|
-
|
|
1924
|
-
/**
|
|
1925
|
-
* Class to manage a testnet watch wallet.
|
|
1926
|
-
*/
|
|
1927
|
-
export class TestNetWatchWallet extends Wallet {
|
|
1928
|
-
static networkPrefix = CashAddressNetworkPrefix.testnet;
|
|
1929
|
-
static walletType = WalletTypeEnum.Watch;
|
|
1930
|
-
constructor(name = "") {
|
|
1931
|
-
super(name, NetworkType.Testnet, WalletTypeEnum.Watch);
|
|
1932
|
-
}
|
|
1933
|
-
|
|
1934
|
-
// interface to static util functions. see Util.ts
|
|
1935
|
-
public static get util() {
|
|
1936
|
-
return TestNetWatchUtil;
|
|
1937
|
-
}
|
|
1938
|
-
}
|
|
1939
|
-
|
|
1940
|
-
/**
|
|
1941
|
-
* Class to manage a regtest watch wallet.
|
|
1942
|
-
*/
|
|
1943
|
-
export class RegTestWatchWallet extends Wallet {
|
|
1944
|
-
static networkPrefix = CashAddressNetworkPrefix.regtest;
|
|
1945
|
-
static walletType = WalletTypeEnum.Watch;
|
|
1946
|
-
constructor(name = "") {
|
|
1947
|
-
super(name, NetworkType.Regtest, WalletTypeEnum.Watch);
|
|
1948
|
-
}
|
|
1949
|
-
|
|
1950
|
-
// interface to static util functions. see Util.ts
|
|
1951
|
-
public static get util() {
|
|
1952
|
-
return RegTestWatchUtil;
|
|
1953
|
-
}
|
|
1954
635
|
}
|