@atomicfinance/bitcoin-wallet-provider 2.5.1 → 3.0.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/.turbo/turbo-build.log +1 -2
- package/.turbo/turbo-lint.log +1 -0
- package/.turbo/turbo-lint:fix.log +15 -0
- package/.turbo/turbo-test.log +1 -3
- package/CHANGELOG.md +13 -0
- package/dist/BitcoinWalletProvider.d.ts +113 -104
- package/dist/BitcoinWalletProvider.js +444 -353
- package/dist/BitcoinWalletProvider.js.map +1 -1
- package/lib/BitcoinWalletProvider.ts +668 -530
- package/package.json +7 -8
|
@@ -1,23 +1,26 @@
|
|
|
1
|
-
import Provider from '@atomicfinance/provider';
|
|
2
|
-
import {
|
|
3
|
-
CreateMultisigResponse,
|
|
4
|
-
finalizePSBTResponse,
|
|
5
|
-
FinanceWalletProvider,
|
|
6
|
-
Input,
|
|
7
|
-
Output,
|
|
8
|
-
} from '@atomicfinance/types';
|
|
9
|
-
import { BitcoinNetwork } from '@liquality/bitcoin-networks';
|
|
10
1
|
import {
|
|
2
|
+
CoinSelectTarget,
|
|
11
3
|
decodeRawTransaction,
|
|
12
4
|
normalizeTransactionObject,
|
|
13
5
|
selectCoins,
|
|
14
|
-
} from '@
|
|
15
|
-
import {
|
|
16
|
-
import
|
|
6
|
+
} from '@atomicfinance/bitcoin-utils';
|
|
7
|
+
import { InsufficientBalanceError } from '@atomicfinance/errors';
|
|
8
|
+
import Provider from '@atomicfinance/provider';
|
|
9
|
+
import {
|
|
10
|
+
Address,
|
|
11
|
+
BigNumber,
|
|
12
|
+
bitcoin as bT,
|
|
13
|
+
ChainProvider,
|
|
14
|
+
SendOptions,
|
|
15
|
+
Transaction,
|
|
16
|
+
WalletProvider,
|
|
17
|
+
} from '@atomicfinance/types';
|
|
18
|
+
import { addressToString } from '@atomicfinance/utils';
|
|
19
|
+
import { BitcoinNetwork } from 'bitcoin-networks';
|
|
17
20
|
import * as bitcoin from 'bitcoinjs-lib';
|
|
18
|
-
import
|
|
21
|
+
import { BIP32Interface } from 'bitcoinjs-lib';
|
|
22
|
+
import memoize from 'memoizee';
|
|
19
23
|
|
|
20
|
-
const FEE_PER_BYTE_FALLBACK = 5;
|
|
21
24
|
const ADDRESS_GAP = 20;
|
|
22
25
|
const NONCHANGE_ADDRESS = 0;
|
|
23
26
|
const CHANGE_ADDRESS = 1;
|
|
@@ -27,596 +30,731 @@ type UnusedAddressesBlacklist = {
|
|
|
27
30
|
[address: string]: true;
|
|
28
31
|
};
|
|
29
32
|
|
|
30
|
-
export
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
_maxAddressesToDerive: number;
|
|
33
|
+
export enum AddressSearchType {
|
|
34
|
+
EXTERNAL,
|
|
35
|
+
CHANGE,
|
|
36
|
+
EXTERNAL_OR_CHANGE,
|
|
37
|
+
}
|
|
36
38
|
|
|
37
|
-
|
|
38
|
-
super();
|
|
39
|
+
type DerivationCache = { [index: string]: Address };
|
|
39
40
|
|
|
40
|
-
|
|
41
|
-
this._unusedAddressesBlacklist = {};
|
|
42
|
-
this._maxAddressesToDerive = 5000;
|
|
43
|
-
}
|
|
41
|
+
type Constructor<T = unknown> = new (...args: any[]) => T;
|
|
44
42
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
) {
|
|
51
|
-
return this._buildSweepTransaction(
|
|
52
|
-
externalChangeAddress,
|
|
53
|
-
feePerByte,
|
|
54
|
-
_outputs,
|
|
55
|
-
fixedInputs,
|
|
56
|
-
);
|
|
57
|
-
}
|
|
43
|
+
interface BitcoinWalletProviderOptions {
|
|
44
|
+
network: BitcoinNetwork;
|
|
45
|
+
baseDerivationPath: string;
|
|
46
|
+
addressType?: bT.AddressType;
|
|
47
|
+
}
|
|
58
48
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
49
|
+
export default <T extends Constructor<Provider>>(superclass: T) => {
|
|
50
|
+
abstract class BitcoinWalletProvider
|
|
51
|
+
extends superclass
|
|
52
|
+
implements Partial<ChainProvider>, Partial<WalletProvider> {
|
|
53
|
+
_network: BitcoinNetwork;
|
|
54
|
+
_unusedAddressesBlacklist: UnusedAddressesBlacklist;
|
|
55
|
+
_maxAddressesToDerive: number;
|
|
56
|
+
_baseDerivationPath: string;
|
|
57
|
+
_addressType: bT.AddressType;
|
|
58
|
+
_derivationCache: DerivationCache;
|
|
59
|
+
|
|
60
|
+
constructor(...args: any[]) {
|
|
61
|
+
const options = args[0] as BitcoinWalletProviderOptions;
|
|
62
|
+
const {
|
|
63
|
+
network,
|
|
64
|
+
baseDerivationPath,
|
|
65
|
+
addressType = bT.AddressType.BECH32,
|
|
66
|
+
} = options;
|
|
67
|
+
const addressTypes = Object.values(bT.AddressType);
|
|
68
|
+
if (!addressTypes.includes(addressType)) {
|
|
69
|
+
throw new Error(`addressType must be one of ${addressTypes.join(',')}`);
|
|
70
|
+
}
|
|
62
71
|
|
|
63
|
-
|
|
64
|
-
unusedAddressesBlacklist: UnusedAddressesBlacklist,
|
|
65
|
-
) {
|
|
66
|
-
this._unusedAddressesBlacklist = unusedAddressesBlacklist;
|
|
67
|
-
}
|
|
72
|
+
super(options);
|
|
68
73
|
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
74
|
+
this._baseDerivationPath = baseDerivationPath;
|
|
75
|
+
this._network = network;
|
|
76
|
+
this._addressType = addressType;
|
|
77
|
+
this._derivationCache = {};
|
|
78
|
+
this._unusedAddressesBlacklist = {};
|
|
79
|
+
this._maxAddressesToDerive = 5000;
|
|
80
|
+
}
|
|
72
81
|
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
82
|
+
abstract baseDerivationNode(): Promise<BIP32Interface>;
|
|
83
|
+
abstract _buildTransaction(
|
|
84
|
+
targets: bT.OutputTarget[],
|
|
85
|
+
feePerByte?: number,
|
|
86
|
+
fixedInputs?: bT.Input[],
|
|
87
|
+
): Promise<{ hex: string; fee: number }>;
|
|
88
|
+
abstract _buildSweepTransaction(
|
|
89
|
+
externalChangeAddress: string,
|
|
90
|
+
feePerByte?: number,
|
|
91
|
+
): Promise<{ hex: string; fee: number }>;
|
|
92
|
+
abstract signPSBT(
|
|
93
|
+
data: string,
|
|
94
|
+
inputs: bT.PsbtInputTarget[],
|
|
95
|
+
): Promise<string>;
|
|
96
|
+
abstract signBatchP2SHTransaction(
|
|
97
|
+
inputs: [
|
|
98
|
+
{ inputTxHex: string; index: number; vout: any; outputScript: Buffer },
|
|
99
|
+
],
|
|
100
|
+
addresses: string,
|
|
101
|
+
tx: any,
|
|
102
|
+
lockTime?: number,
|
|
103
|
+
segwit?: boolean,
|
|
104
|
+
): Promise<Buffer[]>;
|
|
105
|
+
|
|
106
|
+
getDerivationCache() {
|
|
107
|
+
return this._derivationCache;
|
|
108
|
+
}
|
|
76
109
|
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
`not enough keys supplied (got ${pubkeys.length} keys, but need at least ${m} to redeem)`,
|
|
110
|
+
async setDerivationCache(derivationCache: DerivationCache) {
|
|
111
|
+
const address = await this.getDerivationPathAddress(
|
|
112
|
+
Object.keys(derivationCache)[0],
|
|
81
113
|
);
|
|
114
|
+
if (derivationCache[address.derivationPath].address !== address.address) {
|
|
115
|
+
throw new Error(
|
|
116
|
+
`derivationCache at ${address.derivationPath} does not match`,
|
|
117
|
+
);
|
|
118
|
+
}
|
|
119
|
+
this._derivationCache = derivationCache;
|
|
82
120
|
}
|
|
83
|
-
// Create m-of-n multisig
|
|
84
|
-
const p2ms = bitcoin.payments.p2ms({
|
|
85
|
-
m: m,
|
|
86
|
-
pubkeys: pubkeys.map((key: string) => Buffer.from(key, 'hex')),
|
|
87
|
-
network: this._network,
|
|
88
|
-
});
|
|
89
|
-
|
|
90
|
-
// Create p2wsh for multisig
|
|
91
|
-
const p2wsh = bitcoin.payments.p2wsh({
|
|
92
|
-
redeem: p2ms,
|
|
93
|
-
network: this._network,
|
|
94
|
-
});
|
|
95
|
-
|
|
96
|
-
return p2wsh;
|
|
97
|
-
}
|
|
98
121
|
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
* https://developer.bitcoin.org/reference/rpc/createmultisig.html
|
|
102
|
-
* @param m the number of required signatures
|
|
103
|
-
* @param pubkeys n possible pubkeys in total
|
|
104
|
-
* @returns a json object containing the `address` and `redeemScript`
|
|
105
|
-
*/
|
|
106
|
-
createMultisig(m: number, pubkeys: string[]): CreateMultisigResponse {
|
|
107
|
-
const p2wsh = this._createMultisigPayment(m, pubkeys);
|
|
108
|
-
return {
|
|
109
|
-
address: p2wsh.address,
|
|
110
|
-
redeemScript: p2wsh.redeem?.output?.toString('hex'),
|
|
111
|
-
};
|
|
112
|
-
}
|
|
122
|
+
sendOptionsToOutputs(transactions: SendOptions[]): bT.OutputTarget[] {
|
|
123
|
+
const targets: bT.OutputTarget[] = [];
|
|
113
124
|
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
* @param ouputs the Outputs to the PSBT
|
|
122
|
-
* @returns a base64 encoded psbt string
|
|
123
|
-
*/
|
|
124
|
-
buildMultisigPSBT(
|
|
125
|
-
m: number,
|
|
126
|
-
pubkeys: string[],
|
|
127
|
-
inputs: Input[],
|
|
128
|
-
outputs: Output[],
|
|
129
|
-
): string {
|
|
130
|
-
assert(inputs.length > 0, 'no inputs found');
|
|
131
|
-
assert(outputs.length > 0, 'no outputs found');
|
|
132
|
-
|
|
133
|
-
const p2wsh = this._createMultisigPayment(m, pubkeys);
|
|
134
|
-
|
|
135
|
-
// Verify pubkeyhash for all inputs matches the p2wsh hash
|
|
136
|
-
assert(
|
|
137
|
-
inputs.every(
|
|
138
|
-
(input: Input) => p2wsh.output.toString('hex') === input.scriptPubKey,
|
|
139
|
-
),
|
|
140
|
-
'address pubkeyhash does not match input scriptPubKey',
|
|
141
|
-
);
|
|
142
|
-
|
|
143
|
-
// creator
|
|
144
|
-
const psbt = new bitcoin.Psbt({ network: this._network });
|
|
145
|
-
|
|
146
|
-
// updater
|
|
147
|
-
inputs.forEach((input: Input) => {
|
|
148
|
-
psbt.addInput({
|
|
149
|
-
hash: input.txid,
|
|
150
|
-
index: input.vout,
|
|
151
|
-
witnessUtxo: { script: p2wsh.output, value: input.value },
|
|
152
|
-
witnessScript: p2wsh.redeem.output,
|
|
153
|
-
});
|
|
154
|
-
});
|
|
125
|
+
transactions.forEach((tx) => {
|
|
126
|
+
if (tx.to && tx.value && tx.value.gt(0)) {
|
|
127
|
+
targets.push({
|
|
128
|
+
address: addressToString(tx.to),
|
|
129
|
+
value: tx.value.toNumber(),
|
|
130
|
+
});
|
|
131
|
+
}
|
|
155
132
|
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
133
|
+
if (tx.data) {
|
|
134
|
+
const scriptBuffer = bitcoin.script.compile([
|
|
135
|
+
bitcoin.script.OPS.OP_RETURN,
|
|
136
|
+
Buffer.from(tx.data, 'hex'),
|
|
137
|
+
]);
|
|
138
|
+
targets.push({
|
|
139
|
+
value: 0,
|
|
140
|
+
script: scriptBuffer,
|
|
141
|
+
});
|
|
142
|
+
}
|
|
160
143
|
});
|
|
161
|
-
});
|
|
162
144
|
|
|
163
|
-
|
|
164
|
-
|
|
145
|
+
return targets;
|
|
146
|
+
}
|
|
165
147
|
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
* @param psbt a base64 encoded psbt string (P2WSH only)
|
|
170
|
-
* @returns a base64 encoded signed psbt string
|
|
171
|
-
*/
|
|
172
|
-
async walletProcessPSBT(psbtString: string): Promise<string> {
|
|
173
|
-
const psbt = bitcoin.Psbt.fromBase64(psbtString);
|
|
174
|
-
|
|
175
|
-
await Promise.all(
|
|
176
|
-
psbt.data.inputs.map(async (input, i: number) => {
|
|
177
|
-
assert(
|
|
178
|
-
psbt.getInputType(i).slice(0, 5) === 'p2wsh',
|
|
179
|
-
'only accepts P2WSH inputs',
|
|
180
|
-
);
|
|
148
|
+
async buildTransaction(output: bT.OutputTarget, feePerByte: number) {
|
|
149
|
+
return this._buildTransaction([output], feePerByte);
|
|
150
|
+
}
|
|
181
151
|
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
);
|
|
152
|
+
async buildBatchTransaction(outputs: bT.OutputTarget[]) {
|
|
153
|
+
return this._buildTransaction(outputs);
|
|
154
|
+
}
|
|
186
155
|
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
if (!address) return;
|
|
202
|
-
|
|
203
|
-
// derive keypair
|
|
204
|
-
const keyPair = await this.getMethod('keyPair')(
|
|
205
|
-
address.derivationPath,
|
|
206
|
-
);
|
|
156
|
+
async _sendTransaction(
|
|
157
|
+
transactions: bT.OutputTarget[],
|
|
158
|
+
feePerByte?: number,
|
|
159
|
+
) {
|
|
160
|
+
const { hex, fee } = await this._buildTransaction(
|
|
161
|
+
transactions,
|
|
162
|
+
feePerByte,
|
|
163
|
+
);
|
|
164
|
+
await this.getMethod('sendRawTransaction')(hex);
|
|
165
|
+
return normalizeTransactionObject(
|
|
166
|
+
decodeRawTransaction(hex, this._network),
|
|
167
|
+
fee,
|
|
168
|
+
);
|
|
169
|
+
}
|
|
207
170
|
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
171
|
+
async sendTransaction(options: SendOptions) {
|
|
172
|
+
return this._sendTransaction(
|
|
173
|
+
this.sendOptionsToOutputs([options]),
|
|
174
|
+
options.fee as number,
|
|
175
|
+
);
|
|
176
|
+
}
|
|
214
177
|
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
178
|
+
async sendBatchTransaction(transactions: SendOptions[]) {
|
|
179
|
+
return this._sendTransaction(this.sendOptionsToOutputs(transactions));
|
|
180
|
+
}
|
|
218
181
|
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
* @returns a json object containing `psbt` in base64, `hex` for transaction and `complete` for if
|
|
225
|
-
* the transaction has a complete set of signatures
|
|
226
|
-
*/
|
|
227
|
-
finalizePSBT(psbtString: string): finalizePSBTResponse {
|
|
228
|
-
const psbt = bitcoin.Psbt.fromBase64(psbtString);
|
|
229
|
-
|
|
230
|
-
try {
|
|
231
|
-
psbt.validateSignaturesOfAllInputs(); // ensure all signatures are valid!
|
|
232
|
-
psbt.finalizeAllInputs();
|
|
233
|
-
} catch (error) {
|
|
234
|
-
return {
|
|
235
|
-
psbt: psbt.toBase64(),
|
|
236
|
-
complete: false,
|
|
237
|
-
};
|
|
182
|
+
async buildSweepTransaction(
|
|
183
|
+
externalChangeAddress: string,
|
|
184
|
+
feePerByte: number,
|
|
185
|
+
) {
|
|
186
|
+
return this._buildSweepTransaction(externalChangeAddress, feePerByte);
|
|
238
187
|
}
|
|
239
|
-
return {
|
|
240
|
-
psbt: psbt.toBase64(),
|
|
241
|
-
hex: psbt.extractTransaction().toHex(),
|
|
242
|
-
complete: true,
|
|
243
|
-
};
|
|
244
|
-
}
|
|
245
188
|
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
189
|
+
async sendSweepTransaction(
|
|
190
|
+
externalChangeAddress: Address | string,
|
|
191
|
+
feePerByte: number,
|
|
192
|
+
) {
|
|
193
|
+
const { hex, fee } = await this._buildSweepTransaction(
|
|
194
|
+
addressToString(externalChangeAddress),
|
|
195
|
+
feePerByte,
|
|
196
|
+
);
|
|
197
|
+
await this.getMethod('sendRawTransaction')(hex);
|
|
198
|
+
return normalizeTransactionObject(
|
|
199
|
+
decodeRawTransaction(hex, this._network),
|
|
200
|
+
fee,
|
|
201
|
+
);
|
|
202
|
+
}
|
|
249
203
|
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
).then(({ unusedAddress }) => unusedAddress[key]);
|
|
254
|
-
this._unusedAddressesBlacklist[address.address] = true;
|
|
204
|
+
getUnusedAddressesBlacklist(): UnusedAddressesBlacklist {
|
|
205
|
+
return this._unusedAddressesBlacklist;
|
|
206
|
+
}
|
|
255
207
|
|
|
256
|
-
|
|
257
|
-
|
|
208
|
+
setUnusedAddressesBlacklist(
|
|
209
|
+
unusedAddressesBlacklist: UnusedAddressesBlacklist,
|
|
210
|
+
) {
|
|
211
|
+
this._unusedAddressesBlacklist = unusedAddressesBlacklist;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
setMaxAddressesToDerive(maxAddressesToDerive: number) {
|
|
215
|
+
this._maxAddressesToDerive = maxAddressesToDerive;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
getMaxAddressesToDerive() {
|
|
219
|
+
return this._maxAddressesToDerive;
|
|
220
|
+
}
|
|
258
221
|
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
const unusedAddressMap = { change: null, nonChange: null };
|
|
263
|
-
|
|
264
|
-
let addrList;
|
|
265
|
-
let addressIndex = 0;
|
|
266
|
-
let changeAddresses: Address[] = [];
|
|
267
|
-
let nonChangeAddresses: Address[] = [];
|
|
268
|
-
|
|
269
|
-
/* eslint-disable no-unmodified-loop-condition */
|
|
270
|
-
while (
|
|
271
|
-
(addressType === NONCHANGE_OR_CHANGE_ADDRESS &&
|
|
272
|
-
(addressCountMap.change < ADDRESS_GAP ||
|
|
273
|
-
addressCountMap.nonChange < ADDRESS_GAP)) ||
|
|
274
|
-
(addressType === NONCHANGE_ADDRESS &&
|
|
275
|
-
addressCountMap.nonChange < ADDRESS_GAP) ||
|
|
276
|
-
(addressType === CHANGE_ADDRESS && addressCountMap.change < ADDRESS_GAP)
|
|
222
|
+
async updateTransactionFee(
|
|
223
|
+
tx: Transaction<bitcoin.Transaction> | string,
|
|
224
|
+
newFeePerByte: number,
|
|
277
225
|
) {
|
|
278
|
-
|
|
279
|
-
|
|
226
|
+
const txHash = typeof tx === 'string' ? tx : tx.hash;
|
|
227
|
+
const transaction: bT.Transaction = (
|
|
228
|
+
await this.getMethod('getTransactionByHash')(txHash)
|
|
229
|
+
)._raw;
|
|
230
|
+
const fixedInputs = [transaction.vin[0]]; // TODO: should this pick more than 1 input? RBF doesn't mandate it
|
|
231
|
+
|
|
232
|
+
const lookupAddresses = transaction.vout.map(
|
|
233
|
+
(vout) => vout.scriptPubKey.addresses[0],
|
|
234
|
+
);
|
|
235
|
+
const changeAddress = await this.findAddress(lookupAddresses, true);
|
|
236
|
+
const changeOutput = transaction.vout.find(
|
|
237
|
+
(vout) => vout.scriptPubKey.addresses[0] === changeAddress.address,
|
|
238
|
+
);
|
|
280
239
|
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
changeAddresses = await this.client.wallet.getAddresses(
|
|
288
|
-
addressIndex,
|
|
289
|
-
numAddressPerCall,
|
|
290
|
-
true,
|
|
240
|
+
let outputs = transaction.vout;
|
|
241
|
+
if (changeOutput) {
|
|
242
|
+
outputs = outputs.filter(
|
|
243
|
+
(vout) =>
|
|
244
|
+
vout.scriptPubKey.addresses[0] !==
|
|
245
|
+
changeOutput.scriptPubKey.addresses[0],
|
|
291
246
|
);
|
|
292
|
-
addrList = addrList.concat(changeAddresses);
|
|
293
|
-
} else {
|
|
294
|
-
changeAddresses = [];
|
|
295
247
|
}
|
|
296
248
|
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
)
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
249
|
+
// TODO more checks?
|
|
250
|
+
const transactions = outputs.map((output) => ({
|
|
251
|
+
address: output.scriptPubKey.addresses[0],
|
|
252
|
+
value: new BigNumber(output.value).times(1e8).toNumber(),
|
|
253
|
+
}));
|
|
254
|
+
const { hex, fee } = await this._buildTransaction(
|
|
255
|
+
transactions,
|
|
256
|
+
newFeePerByte,
|
|
257
|
+
fixedInputs,
|
|
258
|
+
);
|
|
259
|
+
await this.getMethod('sendRawTransaction')(hex);
|
|
260
|
+
return normalizeTransactionObject(
|
|
261
|
+
decodeRawTransaction(hex, this._network),
|
|
262
|
+
fee,
|
|
263
|
+
);
|
|
264
|
+
}
|
|
310
265
|
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
266
|
+
async getUnusedAddress(change = false, numAddressPerCall = 100) {
|
|
267
|
+
const addressType = change ? CHANGE_ADDRESS : NONCHANGE_ADDRESS;
|
|
268
|
+
const key = change ? 'change' : 'nonChange';
|
|
314
269
|
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
270
|
+
const address = await this._getUsedUnusedAddresses(
|
|
271
|
+
numAddressPerCall,
|
|
272
|
+
addressType,
|
|
273
|
+
).then(({ unusedAddress }) => unusedAddress[key]);
|
|
274
|
+
this._unusedAddressesBlacklist[address.address] = true;
|
|
275
|
+
|
|
276
|
+
return address;
|
|
277
|
+
}
|
|
323
278
|
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
279
|
+
async _getUsedUnusedAddresses(numAddressPerCall = 100, addressType) {
|
|
280
|
+
const usedAddresses = [];
|
|
281
|
+
const addressCountMap = { change: 0, nonChange: 0 };
|
|
282
|
+
const unusedAddressMap = { change: null, nonChange: null };
|
|
283
|
+
|
|
284
|
+
let addrList;
|
|
285
|
+
let addressIndex = 0;
|
|
286
|
+
let changeAddresses: Address[] = [];
|
|
287
|
+
let nonChangeAddresses: Address[] = [];
|
|
288
|
+
|
|
289
|
+
/* eslint-disable no-unmodified-loop-condition */
|
|
290
|
+
while (
|
|
291
|
+
(addressType === NONCHANGE_OR_CHANGE_ADDRESS &&
|
|
292
|
+
(addressCountMap.change < ADDRESS_GAP ||
|
|
293
|
+
addressCountMap.nonChange < ADDRESS_GAP)) ||
|
|
294
|
+
(addressType === NONCHANGE_ADDRESS &&
|
|
295
|
+
addressCountMap.nonChange < ADDRESS_GAP) ||
|
|
296
|
+
(addressType === CHANGE_ADDRESS && addressCountMap.change < ADDRESS_GAP)
|
|
297
|
+
) {
|
|
298
|
+
/* eslint-enable no-unmodified-loop-condition */
|
|
299
|
+
addrList = [];
|
|
300
|
+
|
|
301
|
+
if (
|
|
302
|
+
(addressType === NONCHANGE_OR_CHANGE_ADDRESS ||
|
|
303
|
+
addressType === CHANGE_ADDRESS) &&
|
|
304
|
+
addressCountMap.change < ADDRESS_GAP
|
|
305
|
+
) {
|
|
306
|
+
// Scanning for change addr
|
|
307
|
+
changeAddresses = await this.client.wallet.getAddresses(
|
|
308
|
+
addressIndex,
|
|
309
|
+
numAddressPerCall,
|
|
310
|
+
true,
|
|
311
|
+
);
|
|
312
|
+
addrList = addrList.concat(changeAddresses);
|
|
328
313
|
} else {
|
|
329
|
-
|
|
314
|
+
changeAddresses = [];
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
if (
|
|
318
|
+
(addressType === NONCHANGE_OR_CHANGE_ADDRESS ||
|
|
319
|
+
addressType === NONCHANGE_ADDRESS) &&
|
|
320
|
+
addressCountMap.nonChange < ADDRESS_GAP
|
|
321
|
+
) {
|
|
322
|
+
// Scanning for non change addr
|
|
323
|
+
nonChangeAddresses = await this.getAddresses(
|
|
324
|
+
addressIndex,
|
|
325
|
+
numAddressPerCall,
|
|
326
|
+
false,
|
|
327
|
+
);
|
|
328
|
+
addrList = addrList.concat(nonChangeAddresses);
|
|
329
|
+
}
|
|
330
330
|
|
|
331
|
-
|
|
332
|
-
|
|
331
|
+
const transactionCounts = await this.getMethod(
|
|
332
|
+
'getAddressTransactionCounts',
|
|
333
|
+
)(addrList);
|
|
334
|
+
|
|
335
|
+
for (const address of addrList) {
|
|
336
|
+
const isUsed =
|
|
337
|
+
transactionCounts[address.address] > 0 ||
|
|
338
|
+
this._unusedAddressesBlacklist[address.address];
|
|
339
|
+
const isChangeAddress = changeAddresses.find(
|
|
340
|
+
(a) => address.address === a.address,
|
|
341
|
+
);
|
|
342
|
+
const key = isChangeAddress ? 'change' : 'nonChange';
|
|
343
|
+
|
|
344
|
+
if (isUsed) {
|
|
345
|
+
usedAddresses.push(address);
|
|
346
|
+
addressCountMap[key] = 0;
|
|
347
|
+
unusedAddressMap[key] = null;
|
|
348
|
+
} else {
|
|
349
|
+
addressCountMap[key]++;
|
|
350
|
+
|
|
351
|
+
if (!unusedAddressMap[key]) {
|
|
352
|
+
unusedAddressMap[key] = address;
|
|
353
|
+
}
|
|
333
354
|
}
|
|
334
355
|
}
|
|
356
|
+
|
|
357
|
+
addressIndex += numAddressPerCall;
|
|
335
358
|
}
|
|
336
359
|
|
|
337
|
-
|
|
360
|
+
let firstUnusedAddress;
|
|
361
|
+
const indexNonChange = unusedAddressMap.nonChange
|
|
362
|
+
? unusedAddressMap.nonChange.index
|
|
363
|
+
: Infinity;
|
|
364
|
+
const indexChange = unusedAddressMap.change
|
|
365
|
+
? unusedAddressMap.change.index
|
|
366
|
+
: Infinity;
|
|
367
|
+
|
|
368
|
+
if (indexNonChange <= indexChange)
|
|
369
|
+
firstUnusedAddress = unusedAddressMap.nonChange;
|
|
370
|
+
else firstUnusedAddress = unusedAddressMap.change;
|
|
371
|
+
|
|
372
|
+
return {
|
|
373
|
+
usedAddresses,
|
|
374
|
+
unusedAddress: unusedAddressMap,
|
|
375
|
+
firstUnusedAddress,
|
|
376
|
+
};
|
|
338
377
|
}
|
|
339
378
|
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
: Infinity;
|
|
344
|
-
const indexChange = unusedAddressMap.change
|
|
345
|
-
? unusedAddressMap.change.index
|
|
346
|
-
: Infinity;
|
|
347
|
-
|
|
348
|
-
if (indexNonChange <= indexChange)
|
|
349
|
-
firstUnusedAddress = unusedAddressMap.nonChange;
|
|
350
|
-
else firstUnusedAddress = unusedAddressMap.change;
|
|
351
|
-
|
|
352
|
-
return {
|
|
353
|
-
usedAddresses,
|
|
354
|
-
unusedAddress: unusedAddressMap,
|
|
355
|
-
firstUnusedAddress,
|
|
356
|
-
};
|
|
357
|
-
}
|
|
379
|
+
async getWalletAddress(address: string) {
|
|
380
|
+
const foundAddress = await this.findAddress([address]);
|
|
381
|
+
if (foundAddress) return foundAddress;
|
|
358
382
|
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
feePerByte: number,
|
|
362
|
-
_outputs: Output[],
|
|
363
|
-
fixedInputs: Input[],
|
|
364
|
-
): Promise<Transaction<bT.Transaction>> {
|
|
365
|
-
const { hex, fee } = await this._buildSweepTransaction(
|
|
366
|
-
externalChangeAddress,
|
|
367
|
-
feePerByte,
|
|
368
|
-
_outputs,
|
|
369
|
-
fixedInputs,
|
|
370
|
-
);
|
|
371
|
-
await this.getMethod('sendRawTransaction')(hex);
|
|
372
|
-
return normalizeTransactionObject(
|
|
373
|
-
decodeRawTransaction(hex, this._network),
|
|
374
|
-
fee,
|
|
375
|
-
);
|
|
376
|
-
}
|
|
383
|
+
throw new Error('Wallet does not contain address');
|
|
384
|
+
}
|
|
377
385
|
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
386
|
+
getAddressFromPublicKey(publicKey: Buffer) {
|
|
387
|
+
return this.getPaymentVariantFromPublicKey(publicKey).address;
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
getPaymentVariantFromPublicKey(publicKey: Buffer) {
|
|
391
|
+
if (this._addressType === bT.AddressType.LEGACY) {
|
|
392
|
+
return bitcoin.payments.p2pkh({
|
|
393
|
+
pubkey: publicKey,
|
|
394
|
+
network: this._network,
|
|
395
|
+
});
|
|
396
|
+
} else if (this._addressType === bT.AddressType.P2SH_SEGWIT) {
|
|
397
|
+
return bitcoin.payments.p2sh({
|
|
398
|
+
redeem: bitcoin.payments.p2wpkh({
|
|
399
|
+
pubkey: publicKey,
|
|
400
|
+
network: this._network,
|
|
401
|
+
}),
|
|
402
|
+
network: this._network,
|
|
403
|
+
});
|
|
404
|
+
} else if (this._addressType === bT.AddressType.BECH32) {
|
|
405
|
+
return bitcoin.payments.p2wpkh({
|
|
406
|
+
pubkey: publicKey,
|
|
407
|
+
network: this._network,
|
|
408
|
+
});
|
|
400
409
|
}
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
if (
|
|
405
|
-
|
|
406
|
-
`Inputs for amount doesn't exist and no fixedInputs provided`,
|
|
407
|
-
);
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
async getDerivationPathAddress(path: string) {
|
|
413
|
+
if (path in this._derivationCache) {
|
|
414
|
+
return this._derivationCache[path];
|
|
408
415
|
}
|
|
409
416
|
|
|
410
|
-
const
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
}
|
|
420
|
-
_outputs.forEach((output) => {
|
|
421
|
-
const spliceIndex = outputs.findIndex(
|
|
422
|
-
(sweepOutput: Output) => output.value === sweepOutput.value,
|
|
423
|
-
);
|
|
424
|
-
outputs.splice(spliceIndex, 1);
|
|
425
|
-
});
|
|
426
|
-
_outputs.push({
|
|
427
|
-
to: externalChangeAddress,
|
|
428
|
-
value: outputs[0].value,
|
|
429
|
-
});
|
|
430
|
-
return this._buildTransactionWithoutUtxoCheck(
|
|
431
|
-
_outputs,
|
|
432
|
-
_feePerByte,
|
|
433
|
-
inputs,
|
|
434
|
-
);
|
|
435
|
-
}
|
|
417
|
+
const baseDerivationNode = await this.baseDerivationNode();
|
|
418
|
+
const subPath = path.replace(this._baseDerivationPath + '/', '');
|
|
419
|
+
const publicKey = baseDerivationNode.derivePath(subPath).publicKey;
|
|
420
|
+
const address = this.getAddressFromPublicKey(publicKey);
|
|
421
|
+
const addressObject = new Address({
|
|
422
|
+
address,
|
|
423
|
+
publicKey: publicKey.toString('hex'),
|
|
424
|
+
derivationPath: path,
|
|
425
|
+
});
|
|
436
426
|
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
_feePerByte: number,
|
|
440
|
-
fixedInputs: Input[],
|
|
441
|
-
): {
|
|
442
|
-
inputs: bT.UTXO[];
|
|
443
|
-
outputs: { value: number; id?: string }[];
|
|
444
|
-
fee: number;
|
|
445
|
-
change: { value: number; id?: string };
|
|
446
|
-
} {
|
|
447
|
-
const utxoBalance = fixedInputs.reduce((a, b) => a + (b['value'] || 0), 0);
|
|
448
|
-
const outputBalance = _outputs.reduce((a, b) => a + (b['value'] || 0), 0);
|
|
449
|
-
const amountToSend =
|
|
450
|
-
utxoBalance -
|
|
451
|
-
_feePerByte * ((_outputs.length + 1) * 39 + fixedInputs.length * 153); // todo better calculation
|
|
452
|
-
|
|
453
|
-
const targets = _outputs.map((target, i) => ({
|
|
454
|
-
id: 'main',
|
|
455
|
-
value: target.value,
|
|
456
|
-
}));
|
|
457
|
-
if (amountToSend - outputBalance > 0) {
|
|
458
|
-
targets.push({ id: 'main', value: amountToSend - outputBalance });
|
|
427
|
+
this._derivationCache[path] = addressObject;
|
|
428
|
+
return addressObject;
|
|
459
429
|
}
|
|
460
430
|
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
431
|
+
/**
|
|
432
|
+
* getAddresses is an optimized version of upstream CAL's getAddresses.
|
|
433
|
+
* It removes the call to `asyncSetImmediate()`, speeding up the function by a factor of 6x.
|
|
434
|
+
*
|
|
435
|
+
* @param startingIndex
|
|
436
|
+
* @param numAddresses
|
|
437
|
+
* @param change
|
|
438
|
+
* @returns {Promise<Address[]>}
|
|
439
|
+
*/
|
|
440
|
+
async getAddresses(
|
|
441
|
+
startingIndex = 0,
|
|
442
|
+
numAddresses = 1,
|
|
443
|
+
change = false,
|
|
444
|
+
): Promise<Address[]> {
|
|
445
|
+
if (numAddresses < 1) {
|
|
446
|
+
throw new Error('You must return at least one address');
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
const addresses = [];
|
|
450
|
+
const lastIndex = startingIndex + numAddresses;
|
|
451
|
+
const changeVal = change ? '1' : '0';
|
|
468
452
|
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
const inputs = fixedInputs;
|
|
482
|
-
|
|
483
|
-
const txb = new bitcoin.TransactionBuilder(network);
|
|
484
|
-
|
|
485
|
-
for (const output of outputs) {
|
|
486
|
-
const to = output.to; // Allow for OP_RETURN
|
|
487
|
-
txb.addOutput(to, output.value);
|
|
453
|
+
for (
|
|
454
|
+
let currentIndex = startingIndex;
|
|
455
|
+
currentIndex < lastIndex;
|
|
456
|
+
currentIndex++
|
|
457
|
+
) {
|
|
458
|
+
const subPath = changeVal + '/' + currentIndex;
|
|
459
|
+
const path = this._baseDerivationPath + '/' + subPath;
|
|
460
|
+
const addressObject = await this.getDerivationPathAddress(path);
|
|
461
|
+
addresses.push(addressObject);
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
return addresses;
|
|
488
465
|
}
|
|
489
466
|
|
|
490
|
-
|
|
467
|
+
/**
|
|
468
|
+
* findAddress is an optimized version of upstream CAL's findAddress.
|
|
469
|
+
*
|
|
470
|
+
* It searches through both change and non-change addresses (if change arg is not provided) each iteration.
|
|
471
|
+
*
|
|
472
|
+
* This is in contrast to the original findAddress function which searches
|
|
473
|
+
* through all non-change addresses before moving on to change addresses.
|
|
474
|
+
*
|
|
475
|
+
* @param addresses
|
|
476
|
+
* @returns {Promise<Address>}
|
|
477
|
+
*/
|
|
478
|
+
async findAddress(
|
|
479
|
+
addresses: string[],
|
|
480
|
+
change: boolean | null = null,
|
|
481
|
+
): Promise<Address> {
|
|
482
|
+
const addressesPerCall = 20;
|
|
483
|
+
let index = 0;
|
|
484
|
+
|
|
485
|
+
while (index < this._maxAddressesToDerive) {
|
|
486
|
+
const walletAddresses = [];
|
|
487
|
+
|
|
488
|
+
if (change === null || change === false) {
|
|
489
|
+
walletAddresses.push(
|
|
490
|
+
...(await this.getAddresses(index, addressesPerCall, false)),
|
|
491
|
+
);
|
|
492
|
+
}
|
|
491
493
|
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
494
|
+
if (change === null || change === true) {
|
|
495
|
+
walletAddresses.push(
|
|
496
|
+
...(await this.getAddresses(index, addressesPerCall, true)),
|
|
497
|
+
);
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
const walletAddress = walletAddresses.find((walletAddr) =>
|
|
501
|
+
addresses.find((addr) => walletAddr.address === addr),
|
|
502
|
+
);
|
|
503
|
+
|
|
504
|
+
if (walletAddress) {
|
|
505
|
+
// Increment max addresses to derive by 100 if found within 100 addresses of maxAddressesToDerive
|
|
506
|
+
this._maxAddressesToDerive = Math.max(
|
|
507
|
+
this._maxAddressesToDerive,
|
|
508
|
+
index + 100,
|
|
509
|
+
);
|
|
510
|
+
return walletAddress;
|
|
511
|
+
}
|
|
512
|
+
index += addressesPerCall;
|
|
513
|
+
}
|
|
514
|
+
}
|
|
500
515
|
|
|
501
|
-
|
|
516
|
+
async getUsedAddresses(numAddressPerCall = 100) {
|
|
517
|
+
return this._getUsedUnusedAddresses(
|
|
518
|
+
numAddressPerCall,
|
|
519
|
+
AddressSearchType.EXTERNAL_OR_CHANGE,
|
|
520
|
+
).then(({ usedAddresses }) => usedAddresses);
|
|
502
521
|
}
|
|
503
522
|
|
|
504
|
-
|
|
505
|
-
const
|
|
506
|
-
|
|
523
|
+
async withCachedUtxos(func: () => any) {
|
|
524
|
+
const originalGetMethod = this.getMethod;
|
|
525
|
+
const memoizedGetFeePerByte = memoize(this.getMethod('getFeePerByte'), {
|
|
526
|
+
primitive: true,
|
|
527
|
+
});
|
|
528
|
+
const memoizedGetUnspentTransactions = memoize(
|
|
529
|
+
this.getMethod('getUnspentTransactions'),
|
|
530
|
+
{ primitive: true },
|
|
507
531
|
);
|
|
508
|
-
const
|
|
509
|
-
|
|
510
|
-
|
|
532
|
+
const memoizedGetAddressTransactionCounts = memoize(
|
|
533
|
+
this.getMethod('getAddressTransactionCounts'),
|
|
534
|
+
{
|
|
535
|
+
primitive: true,
|
|
536
|
+
},
|
|
511
537
|
);
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
538
|
+
this.getMethod = (method: string, requestor: any = this) => {
|
|
539
|
+
if (method === 'getFeePerByte') return memoizedGetFeePerByte;
|
|
540
|
+
if (method === 'getUnspentTransactions')
|
|
541
|
+
return memoizedGetUnspentTransactions;
|
|
542
|
+
else if (method === 'getAddressTransactionCounts')
|
|
543
|
+
return memoizedGetAddressTransactionCounts;
|
|
544
|
+
else return originalGetMethod.bind(this)(method, requestor);
|
|
519
545
|
};
|
|
520
546
|
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
547
|
+
const result = await func.bind(this)();
|
|
548
|
+
|
|
549
|
+
this.getMethod = originalGetMethod;
|
|
524
550
|
|
|
525
|
-
|
|
551
|
+
return result;
|
|
526
552
|
}
|
|
527
553
|
|
|
528
|
-
|
|
529
|
-
|
|
554
|
+
async getTotalFee(opts: SendOptions, max: boolean) {
|
|
555
|
+
const targets = this.sendOptionsToOutputs([opts]);
|
|
556
|
+
if (!max) {
|
|
557
|
+
const { fee } = await this.getInputsForAmount(
|
|
558
|
+
targets,
|
|
559
|
+
opts.fee as number,
|
|
560
|
+
);
|
|
561
|
+
return fee;
|
|
562
|
+
} else {
|
|
563
|
+
const { fee } = await this.getInputsForAmount(
|
|
564
|
+
targets.filter((t) => !t.value),
|
|
565
|
+
opts.fee as number,
|
|
566
|
+
[],
|
|
567
|
+
100,
|
|
568
|
+
true,
|
|
569
|
+
);
|
|
570
|
+
return fee;
|
|
571
|
+
}
|
|
572
|
+
}
|
|
530
573
|
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
startingIndex = 0,
|
|
542
|
-
numAddresses = 1,
|
|
543
|
-
change = false,
|
|
544
|
-
): Promise<Address[]> {
|
|
545
|
-
if (numAddresses < 1) {
|
|
546
|
-
throw new Error('You must return at least one address');
|
|
574
|
+
async getTotalFees(transactions: SendOptions[], max: boolean) {
|
|
575
|
+
const fees = await this.withCachedUtxos(async () => {
|
|
576
|
+
const fees: { [index: number]: BigNumber } = {};
|
|
577
|
+
for (const tx of transactions) {
|
|
578
|
+
const fee = await this.getTotalFee(tx, max);
|
|
579
|
+
fees[tx.fee as number] = new BigNumber(fee);
|
|
580
|
+
}
|
|
581
|
+
return fees;
|
|
582
|
+
});
|
|
583
|
+
return fees;
|
|
547
584
|
}
|
|
548
585
|
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
586
|
+
async getInputsForAmount(
|
|
587
|
+
_targets: bT.OutputTarget[],
|
|
588
|
+
feePerByte?: number,
|
|
589
|
+
fixedInputs: bT.Input[] = [],
|
|
590
|
+
numAddressPerCall = 100,
|
|
591
|
+
sweep = false,
|
|
592
|
+
) {
|
|
593
|
+
let addressIndex = 0;
|
|
594
|
+
let changeAddresses: Address[] = [];
|
|
595
|
+
let externalAddresses: Address[] = [];
|
|
596
|
+
const addressCountMap = {
|
|
597
|
+
change: 0,
|
|
598
|
+
nonChange: 0,
|
|
599
|
+
};
|
|
552
600
|
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
556
|
-
const baseDerivationPath = (originalProvider as any)
|
|
557
|
-
._baseDerivationPath as string;
|
|
601
|
+
const feePerBytePromise = this.getMethod('getFeePerByte')();
|
|
602
|
+
let utxos: bT.UTXO[] = [];
|
|
558
603
|
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
604
|
+
while (
|
|
605
|
+
addressCountMap.change < ADDRESS_GAP ||
|
|
606
|
+
addressCountMap.nonChange < ADDRESS_GAP
|
|
607
|
+
) {
|
|
608
|
+
let addrList: Address[] = [];
|
|
609
|
+
|
|
610
|
+
if (addressCountMap.change < ADDRESS_GAP) {
|
|
611
|
+
// Scanning for change addr
|
|
612
|
+
changeAddresses = await this.getAddresses(
|
|
613
|
+
addressIndex,
|
|
614
|
+
numAddressPerCall,
|
|
615
|
+
true,
|
|
616
|
+
);
|
|
617
|
+
addrList = addrList.concat(changeAddresses);
|
|
618
|
+
} else {
|
|
619
|
+
changeAddresses = [];
|
|
620
|
+
}
|
|
562
621
|
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
}
|
|
622
|
+
if (addressCountMap.nonChange < ADDRESS_GAP) {
|
|
623
|
+
// Scanning for non change addr
|
|
624
|
+
externalAddresses = await this.getAddresses(
|
|
625
|
+
addressIndex,
|
|
626
|
+
numAddressPerCall,
|
|
627
|
+
false,
|
|
628
|
+
);
|
|
629
|
+
addrList = addrList.concat(externalAddresses);
|
|
630
|
+
}
|
|
573
631
|
|
|
574
|
-
|
|
575
|
-
|
|
632
|
+
const fixedUtxos: bT.UTXO[] = [];
|
|
633
|
+
if (fixedInputs.length > 0) {
|
|
634
|
+
for (const input of fixedInputs) {
|
|
635
|
+
const txHex = await this.getMethod('getRawTransactionByHash')(
|
|
636
|
+
input.txid,
|
|
637
|
+
);
|
|
638
|
+
const tx = decodeRawTransaction(txHex, this._network);
|
|
639
|
+
const value = new BigNumber(tx.vout[input.vout].value)
|
|
640
|
+
.times(1e8)
|
|
641
|
+
.toNumber();
|
|
642
|
+
const address = tx.vout[input.vout].scriptPubKey.addresses[0];
|
|
643
|
+
const walletAddress = await this.getWalletAddress(address);
|
|
644
|
+
const utxo = {
|
|
645
|
+
...input,
|
|
646
|
+
value,
|
|
647
|
+
address,
|
|
648
|
+
derivationPath: walletAddress.derivationPath,
|
|
649
|
+
};
|
|
650
|
+
fixedUtxos.push(utxo);
|
|
651
|
+
}
|
|
652
|
+
}
|
|
576
653
|
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
654
|
+
if (!sweep || fixedUtxos.length === 0) {
|
|
655
|
+
const _utxos: bT.UTXO[] = await this.getMethod(
|
|
656
|
+
'getUnspentTransactions',
|
|
657
|
+
)(addrList);
|
|
658
|
+
utxos.push(
|
|
659
|
+
..._utxos.map((utxo) => {
|
|
660
|
+
const addr = addrList.find((a) => a.address === utxo.address);
|
|
661
|
+
return {
|
|
662
|
+
...utxo,
|
|
663
|
+
derivationPath: addr.derivationPath,
|
|
664
|
+
};
|
|
665
|
+
}),
|
|
666
|
+
);
|
|
667
|
+
} else {
|
|
668
|
+
utxos = fixedUtxos;
|
|
669
|
+
}
|
|
670
|
+
|
|
671
|
+
const utxoBalance = utxos.reduce((a, b) => a + (b.value || 0), 0);
|
|
672
|
+
|
|
673
|
+
const transactionCounts: bT.AddressTxCounts = await this.getMethod(
|
|
674
|
+
'getAddressTransactionCounts',
|
|
675
|
+
)(addrList);
|
|
676
|
+
|
|
677
|
+
if (!feePerByte) feePerByte = await feePerBytePromise;
|
|
678
|
+
const minRelayFee = await this.getMethod('getMinRelayFee')();
|
|
679
|
+
if (feePerByte < minRelayFee) {
|
|
680
|
+
throw new Error(
|
|
681
|
+
`Fee supplied (${feePerByte} sat/b) too low. Minimum relay fee is ${minRelayFee} sat/b`,
|
|
682
|
+
);
|
|
683
|
+
}
|
|
684
|
+
|
|
685
|
+
let targets: CoinSelectTarget[];
|
|
686
|
+
if (sweep) {
|
|
687
|
+
const outputBalance = _targets.reduce(
|
|
688
|
+
(a, b) => a + (b['value'] || 0),
|
|
689
|
+
0,
|
|
690
|
+
);
|
|
691
|
+
|
|
692
|
+
const sweepOutputSize = 39;
|
|
693
|
+
const paymentOutputSize =
|
|
694
|
+
_targets.filter((t) => t.value && t.address).length * 39;
|
|
695
|
+
const scriptOutputSize = _targets
|
|
696
|
+
.filter((t) => !t.value && t.script)
|
|
697
|
+
.reduce((size, t) => size + 39 + t.script.byteLength, 0);
|
|
698
|
+
|
|
699
|
+
const outputSize =
|
|
700
|
+
sweepOutputSize + paymentOutputSize + scriptOutputSize;
|
|
701
|
+
const inputSize = utxos.length * 153;
|
|
702
|
+
|
|
703
|
+
const sweepFee = feePerByte * (inputSize + outputSize);
|
|
704
|
+
const amountToSend = new BigNumber(utxoBalance).minus(sweepFee);
|
|
705
|
+
|
|
706
|
+
targets = _targets.map((target) => ({
|
|
707
|
+
id: 'main',
|
|
708
|
+
value: target.value,
|
|
709
|
+
script: target.script,
|
|
710
|
+
}));
|
|
711
|
+
targets.push({
|
|
712
|
+
id: 'main',
|
|
713
|
+
value: amountToSend.minus(outputBalance).toNumber(),
|
|
714
|
+
});
|
|
715
|
+
} else {
|
|
716
|
+
targets = _targets.map((target) => ({
|
|
717
|
+
id: 'main',
|
|
718
|
+
value: target.value,
|
|
719
|
+
script: target.script,
|
|
720
|
+
}));
|
|
721
|
+
}
|
|
610
722
|
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
723
|
+
const { inputs, outputs, change, fee } = selectCoins(
|
|
724
|
+
utxos,
|
|
725
|
+
targets,
|
|
726
|
+
Math.ceil(feePerByte),
|
|
727
|
+
fixedUtxos,
|
|
616
728
|
);
|
|
617
|
-
|
|
729
|
+
|
|
730
|
+
if (inputs && outputs) {
|
|
731
|
+
return {
|
|
732
|
+
inputs,
|
|
733
|
+
change,
|
|
734
|
+
outputs,
|
|
735
|
+
fee,
|
|
736
|
+
};
|
|
737
|
+
}
|
|
738
|
+
|
|
739
|
+
for (const address of addrList) {
|
|
740
|
+
const isUsed = transactionCounts[address.address];
|
|
741
|
+
const isChangeAddress = changeAddresses.find(
|
|
742
|
+
(a) => address.address === a.address,
|
|
743
|
+
);
|
|
744
|
+
const key = isChangeAddress ? 'change' : 'nonChange';
|
|
745
|
+
|
|
746
|
+
if (isUsed) {
|
|
747
|
+
addressCountMap[key] = 0;
|
|
748
|
+
} else {
|
|
749
|
+
addressCountMap[key]++;
|
|
750
|
+
}
|
|
751
|
+
}
|
|
752
|
+
|
|
753
|
+
addressIndex += numAddressPerCall;
|
|
618
754
|
}
|
|
619
|
-
|
|
755
|
+
|
|
756
|
+
throw new InsufficientBalanceError('Not enough balance');
|
|
620
757
|
}
|
|
621
758
|
}
|
|
622
|
-
|
|
759
|
+
return BitcoinWalletProvider;
|
|
760
|
+
};
|