@atomicfinance/bitcoin-wallet-provider 2.2.3
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/.nvmrc +1 -0
- package/LICENSE +674 -0
- package/dist/BitcoinWalletProvider.d.ts +87 -0
- package/dist/BitcoinWalletProvider.js +359 -0
- package/dist/BitcoinWalletProvider.js.map +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +9 -0
- package/dist/index.js.map +1 -0
- package/lib/BitcoinWalletProvider.ts +542 -0
- package/lib/index.ts +1 -0
- package/package.json +35 -0
- package/tsconfig.json +7 -0
|
@@ -0,0 +1,542 @@
|
|
|
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
|
+
import {
|
|
11
|
+
decodeRawTransaction,
|
|
12
|
+
normalizeTransactionObject,
|
|
13
|
+
selectCoins,
|
|
14
|
+
} from '@liquality/bitcoin-utils';
|
|
15
|
+
import { Address, bitcoin as bT, Transaction } from '@liquality/types';
|
|
16
|
+
import assert from 'assert';
|
|
17
|
+
import * as bitcoin from 'bitcoinjs-lib';
|
|
18
|
+
import secp256k1 from 'secp256k1';
|
|
19
|
+
|
|
20
|
+
const FEE_PER_BYTE_FALLBACK = 5;
|
|
21
|
+
const ADDRESS_GAP = 20;
|
|
22
|
+
const NONCHANGE_ADDRESS = 0;
|
|
23
|
+
const CHANGE_ADDRESS = 1;
|
|
24
|
+
const NONCHANGE_OR_CHANGE_ADDRESS = 2;
|
|
25
|
+
|
|
26
|
+
type UnusedAddressesBlacklist = {
|
|
27
|
+
[address: string]: true;
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
export default class BitcoinWalletProvider
|
|
31
|
+
extends Provider
|
|
32
|
+
implements Partial<FinanceWalletProvider> {
|
|
33
|
+
_network: BitcoinNetwork;
|
|
34
|
+
_unusedAddressesBlacklist: UnusedAddressesBlacklist;
|
|
35
|
+
|
|
36
|
+
constructor(network: BitcoinNetwork) {
|
|
37
|
+
super();
|
|
38
|
+
|
|
39
|
+
this._network = network;
|
|
40
|
+
this._unusedAddressesBlacklist = {};
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
async buildSweepTransactionWithSetOutputs(
|
|
44
|
+
externalChangeAddress: string,
|
|
45
|
+
feePerByte: number,
|
|
46
|
+
_outputs: Output[],
|
|
47
|
+
fixedInputs: Input[],
|
|
48
|
+
) {
|
|
49
|
+
return this._buildSweepTransaction(
|
|
50
|
+
externalChangeAddress,
|
|
51
|
+
feePerByte,
|
|
52
|
+
_outputs,
|
|
53
|
+
fixedInputs,
|
|
54
|
+
);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
getUnusedAddressesBlacklist(): UnusedAddressesBlacklist {
|
|
58
|
+
return this._unusedAddressesBlacklist;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
setUnusedAddressesBlacklist(
|
|
62
|
+
unusedAddressesBlacklist: UnusedAddressesBlacklist,
|
|
63
|
+
) {
|
|
64
|
+
this._unusedAddressesBlacklist = unusedAddressesBlacklist;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
_createMultisigPayment(m: number, pubkeys: string[]): bitcoin.Payment {
|
|
68
|
+
if (m > pubkeys.length) {
|
|
69
|
+
throw new Error(
|
|
70
|
+
`not enough keys supplied (got ${pubkeys.length} keys, but need at least ${m} to redeem)`,
|
|
71
|
+
);
|
|
72
|
+
}
|
|
73
|
+
// Create m-of-n multisig
|
|
74
|
+
const p2ms = bitcoin.payments.p2ms({
|
|
75
|
+
m: m,
|
|
76
|
+
pubkeys: pubkeys.map((key: string) => Buffer.from(key, 'hex')),
|
|
77
|
+
network: this._network,
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
// Create p2wsh for multisig
|
|
81
|
+
const p2wsh = bitcoin.payments.p2wsh({
|
|
82
|
+
redeem: p2ms,
|
|
83
|
+
network: this._network,
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
return p2wsh;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Creates a native-segwit multi-signature address (P2MS in P2WSH) with n signatures of m required keys
|
|
91
|
+
* https://developer.bitcoin.org/reference/rpc/createmultisig.html
|
|
92
|
+
* @param m the number of required signatures
|
|
93
|
+
* @param pubkeys n possible pubkeys in total
|
|
94
|
+
* @returns a json object containing the `address` and `redeemScript`
|
|
95
|
+
*/
|
|
96
|
+
createMultisig(m: number, pubkeys: string[]): CreateMultisigResponse {
|
|
97
|
+
const p2wsh = this._createMultisigPayment(m, pubkeys);
|
|
98
|
+
return {
|
|
99
|
+
address: p2wsh.address,
|
|
100
|
+
redeemScript: p2wsh.redeem?.output?.toString('hex'),
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Creates a PSBT of a native-segwit multi-signature address (P2MS in P2WSH) with n signatures of m required keys
|
|
106
|
+
* https://developer.bitcoin.org/reference/rpc/createmultisig.html
|
|
107
|
+
* https://developer.bitcoin.org/reference/rpc/createpsbt.html
|
|
108
|
+
* @param m the number of required signatures
|
|
109
|
+
* @param pubkeys n possible pubkeys in total
|
|
110
|
+
* @param inputs the Inputs to the PSBT
|
|
111
|
+
* @param ouputs the Outputs to the PSBT
|
|
112
|
+
* @returns a base64 encoded psbt string
|
|
113
|
+
*/
|
|
114
|
+
buildMultisigPSBT(
|
|
115
|
+
m: number,
|
|
116
|
+
pubkeys: string[],
|
|
117
|
+
inputs: Input[],
|
|
118
|
+
outputs: Output[],
|
|
119
|
+
): string {
|
|
120
|
+
assert(inputs.length > 0, 'no inputs found');
|
|
121
|
+
assert(outputs.length > 0, 'no outputs found');
|
|
122
|
+
|
|
123
|
+
const p2wsh = this._createMultisigPayment(m, pubkeys);
|
|
124
|
+
|
|
125
|
+
// Verify pubkeyhash for all inputs matches the p2wsh hash
|
|
126
|
+
assert(
|
|
127
|
+
inputs.every(
|
|
128
|
+
(input: Input) => p2wsh.output.toString('hex') === input.scriptPubKey,
|
|
129
|
+
),
|
|
130
|
+
'address pubkeyhash does not match input scriptPubKey',
|
|
131
|
+
);
|
|
132
|
+
|
|
133
|
+
// creator
|
|
134
|
+
const psbt = new bitcoin.Psbt({ network: this._network });
|
|
135
|
+
|
|
136
|
+
// updater
|
|
137
|
+
inputs.forEach((input: Input) => {
|
|
138
|
+
psbt.addInput({
|
|
139
|
+
hash: input.txid,
|
|
140
|
+
index: input.vout,
|
|
141
|
+
witnessUtxo: { script: p2wsh.output, value: input.value },
|
|
142
|
+
witnessScript: p2wsh.redeem.output,
|
|
143
|
+
});
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
outputs.forEach((output: Output) => {
|
|
147
|
+
psbt.addOutput({
|
|
148
|
+
address: output.to,
|
|
149
|
+
value: output.value,
|
|
150
|
+
});
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
return psbt.toBase64();
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* Update a PSBT with input information from our wallet and then sign inputs that we can sign for
|
|
158
|
+
* https://developer.bitcoin.org/reference/rpc/walletprocesspsbt.html
|
|
159
|
+
* @param psbt a base64 encoded psbt string (P2WSH only)
|
|
160
|
+
* @returns a base64 encoded signed psbt string
|
|
161
|
+
*/
|
|
162
|
+
async walletProcessPSBT(psbtString: string): Promise<string> {
|
|
163
|
+
const psbt = bitcoin.Psbt.fromBase64(psbtString);
|
|
164
|
+
|
|
165
|
+
await Promise.all(
|
|
166
|
+
psbt.data.inputs.map(async (input, i: number) => {
|
|
167
|
+
assert(
|
|
168
|
+
psbt.getInputType(i).slice(0, 5) === 'p2wsh',
|
|
169
|
+
'only accepts P2WSH inputs',
|
|
170
|
+
);
|
|
171
|
+
|
|
172
|
+
const scriptStack = bitcoin.script.decompile(input.witnessScript);
|
|
173
|
+
const pubkeys = scriptStack.filter(
|
|
174
|
+
(data) => Buffer.isBuffer(data) && secp256k1.publicKeyVerify(data),
|
|
175
|
+
);
|
|
176
|
+
|
|
177
|
+
await Promise.all(
|
|
178
|
+
pubkeys.map(async (key) => {
|
|
179
|
+
// create address using pubkey
|
|
180
|
+
const { address: addressString } = bitcoin.payments.p2wpkh({
|
|
181
|
+
pubkey: key as Buffer,
|
|
182
|
+
network: this._network,
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
// Retrieve address object from wallet using address
|
|
186
|
+
const address: Address = await this.quickFindAddress([
|
|
187
|
+
addressString,
|
|
188
|
+
]);
|
|
189
|
+
|
|
190
|
+
// exit if address doesn't exist in wallet
|
|
191
|
+
if (!address) return;
|
|
192
|
+
|
|
193
|
+
// derive keypair
|
|
194
|
+
const keyPair = await this.getMethod('keyPair')(
|
|
195
|
+
address.derivationPath,
|
|
196
|
+
);
|
|
197
|
+
|
|
198
|
+
// sign PSBT using keypair
|
|
199
|
+
psbt.signInput(i, keyPair);
|
|
200
|
+
}),
|
|
201
|
+
);
|
|
202
|
+
}),
|
|
203
|
+
);
|
|
204
|
+
|
|
205
|
+
psbt.validateSignaturesOfAllInputs(); // ensure all signatures are valid!
|
|
206
|
+
return psbt.toBase64();
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
/**
|
|
210
|
+
* Finalize the inputs of a PSBT. If the transaction is fully signed, it will
|
|
211
|
+
* produce a network serialized transaction which can be broadcast with sendrawtransaction
|
|
212
|
+
* https://developer.bitcoin.org/reference/rpc/finalizepsbt.html
|
|
213
|
+
* @param psbt a base64 encoded psbt string
|
|
214
|
+
* @returns a json object containing `psbt` in base64, `hex` for transaction and `complete` for if
|
|
215
|
+
* the transaction has a complete set of signatures
|
|
216
|
+
*/
|
|
217
|
+
finalizePSBT(psbtString: string): finalizePSBTResponse {
|
|
218
|
+
const psbt = bitcoin.Psbt.fromBase64(psbtString);
|
|
219
|
+
|
|
220
|
+
try {
|
|
221
|
+
psbt.validateSignaturesOfAllInputs(); // ensure all signatures are valid!
|
|
222
|
+
psbt.finalizeAllInputs();
|
|
223
|
+
} catch (error) {
|
|
224
|
+
return {
|
|
225
|
+
psbt: psbt.toBase64(),
|
|
226
|
+
complete: false,
|
|
227
|
+
};
|
|
228
|
+
}
|
|
229
|
+
return {
|
|
230
|
+
psbt: psbt.toBase64(),
|
|
231
|
+
hex: psbt.extractTransaction().toHex(),
|
|
232
|
+
complete: true,
|
|
233
|
+
};
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
async getUnusedAddress(change = false, numAddressPerCall = 100) {
|
|
237
|
+
const addressType = change ? CHANGE_ADDRESS : NONCHANGE_ADDRESS;
|
|
238
|
+
const key = change ? 'change' : 'nonChange';
|
|
239
|
+
|
|
240
|
+
const address = await this._getUsedUnusedAddresses(
|
|
241
|
+
numAddressPerCall,
|
|
242
|
+
addressType,
|
|
243
|
+
).then(({ unusedAddress }) => unusedAddress[key]);
|
|
244
|
+
this._unusedAddressesBlacklist[address.address] = true;
|
|
245
|
+
|
|
246
|
+
return address;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
async _getUsedUnusedAddresses(numAddressPerCall = 100, addressType) {
|
|
250
|
+
const usedAddresses = [];
|
|
251
|
+
const addressCountMap = { change: 0, nonChange: 0 };
|
|
252
|
+
const unusedAddressMap = { change: null, nonChange: null };
|
|
253
|
+
|
|
254
|
+
let addrList;
|
|
255
|
+
let addressIndex = 0;
|
|
256
|
+
let changeAddresses: Address[] = [];
|
|
257
|
+
let nonChangeAddresses: Address[] = [];
|
|
258
|
+
|
|
259
|
+
/* eslint-disable no-unmodified-loop-condition */
|
|
260
|
+
while (
|
|
261
|
+
(addressType === NONCHANGE_OR_CHANGE_ADDRESS &&
|
|
262
|
+
(addressCountMap.change < ADDRESS_GAP ||
|
|
263
|
+
addressCountMap.nonChange < ADDRESS_GAP)) ||
|
|
264
|
+
(addressType === NONCHANGE_ADDRESS &&
|
|
265
|
+
addressCountMap.nonChange < ADDRESS_GAP) ||
|
|
266
|
+
(addressType === CHANGE_ADDRESS && addressCountMap.change < ADDRESS_GAP)
|
|
267
|
+
) {
|
|
268
|
+
/* eslint-enable no-unmodified-loop-condition */
|
|
269
|
+
addrList = [];
|
|
270
|
+
|
|
271
|
+
if (
|
|
272
|
+
(addressType === NONCHANGE_OR_CHANGE_ADDRESS ||
|
|
273
|
+
addressType === CHANGE_ADDRESS) &&
|
|
274
|
+
addressCountMap.change < ADDRESS_GAP
|
|
275
|
+
) {
|
|
276
|
+
// Scanning for change addr
|
|
277
|
+
changeAddresses = await this.client.wallet.getAddresses(
|
|
278
|
+
addressIndex,
|
|
279
|
+
numAddressPerCall,
|
|
280
|
+
true,
|
|
281
|
+
);
|
|
282
|
+
addrList = addrList.concat(changeAddresses);
|
|
283
|
+
} else {
|
|
284
|
+
changeAddresses = [];
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
if (
|
|
288
|
+
(addressType === NONCHANGE_OR_CHANGE_ADDRESS ||
|
|
289
|
+
addressType === NONCHANGE_ADDRESS) &&
|
|
290
|
+
addressCountMap.nonChange < ADDRESS_GAP
|
|
291
|
+
) {
|
|
292
|
+
// Scanning for non change addr
|
|
293
|
+
nonChangeAddresses = await this.client.wallet.getAddresses(
|
|
294
|
+
addressIndex,
|
|
295
|
+
numAddressPerCall,
|
|
296
|
+
false,
|
|
297
|
+
);
|
|
298
|
+
addrList = addrList.concat(nonChangeAddresses);
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
const transactionCounts = await this.getMethod(
|
|
302
|
+
'getAddressTransactionCounts',
|
|
303
|
+
)(addrList);
|
|
304
|
+
|
|
305
|
+
for (const address of addrList) {
|
|
306
|
+
const isUsed =
|
|
307
|
+
transactionCounts[address.address] > 0 ||
|
|
308
|
+
this._unusedAddressesBlacklist[address.address];
|
|
309
|
+
const isChangeAddress = changeAddresses.find(
|
|
310
|
+
(a) => address.address === a.address,
|
|
311
|
+
);
|
|
312
|
+
const key = isChangeAddress ? 'change' : 'nonChange';
|
|
313
|
+
|
|
314
|
+
if (isUsed) {
|
|
315
|
+
usedAddresses.push(address);
|
|
316
|
+
addressCountMap[key] = 0;
|
|
317
|
+
unusedAddressMap[key] = null;
|
|
318
|
+
} else {
|
|
319
|
+
addressCountMap[key]++;
|
|
320
|
+
|
|
321
|
+
if (!unusedAddressMap[key]) {
|
|
322
|
+
unusedAddressMap[key] = address;
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
addressIndex += numAddressPerCall;
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
let firstUnusedAddress;
|
|
331
|
+
const indexNonChange = unusedAddressMap.nonChange
|
|
332
|
+
? unusedAddressMap.nonChange.index
|
|
333
|
+
: Infinity;
|
|
334
|
+
const indexChange = unusedAddressMap.change
|
|
335
|
+
? unusedAddressMap.change.index
|
|
336
|
+
: Infinity;
|
|
337
|
+
|
|
338
|
+
if (indexNonChange <= indexChange)
|
|
339
|
+
firstUnusedAddress = unusedAddressMap.nonChange;
|
|
340
|
+
else firstUnusedAddress = unusedAddressMap.change;
|
|
341
|
+
|
|
342
|
+
return {
|
|
343
|
+
usedAddresses,
|
|
344
|
+
unusedAddress: unusedAddressMap,
|
|
345
|
+
firstUnusedAddress,
|
|
346
|
+
};
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
async sendSweepTransactionWithSetOutputs(
|
|
350
|
+
externalChangeAddress: string,
|
|
351
|
+
feePerByte: number,
|
|
352
|
+
_outputs: Output[],
|
|
353
|
+
fixedInputs: Input[],
|
|
354
|
+
): Promise<Transaction<bT.Transaction>> {
|
|
355
|
+
const { hex, fee } = await this._buildSweepTransaction(
|
|
356
|
+
externalChangeAddress,
|
|
357
|
+
feePerByte,
|
|
358
|
+
_outputs,
|
|
359
|
+
fixedInputs,
|
|
360
|
+
);
|
|
361
|
+
await this.getMethod('sendRawTransaction')(hex);
|
|
362
|
+
return normalizeTransactionObject(
|
|
363
|
+
decodeRawTransaction(hex, this._network),
|
|
364
|
+
fee,
|
|
365
|
+
);
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
async _buildSweepTransaction(
|
|
369
|
+
externalChangeAddress: string,
|
|
370
|
+
feePerByte: number,
|
|
371
|
+
_outputs: Output[] = [],
|
|
372
|
+
fixedInputs: Input[],
|
|
373
|
+
) {
|
|
374
|
+
const _feePerByte =
|
|
375
|
+
feePerByte ||
|
|
376
|
+
(await this.getMethod('getFeePerByte')()) ||
|
|
377
|
+
FEE_PER_BYTE_FALLBACK;
|
|
378
|
+
const inputs: Input[] = [];
|
|
379
|
+
const outputs: Output[] = [];
|
|
380
|
+
try {
|
|
381
|
+
const inputsForAmount = await this.getMethod('getInputsForAmount')(
|
|
382
|
+
_outputs,
|
|
383
|
+
_feePerByte,
|
|
384
|
+
fixedInputs,
|
|
385
|
+
100,
|
|
386
|
+
true,
|
|
387
|
+
);
|
|
388
|
+
if (inputsForAmount.change) {
|
|
389
|
+
throw Error('There should not be any change for sweeping transaction');
|
|
390
|
+
}
|
|
391
|
+
inputs.push(...(inputsForAmount.inputs || []));
|
|
392
|
+
outputs.push(...(inputsForAmount.outputs || []));
|
|
393
|
+
} catch (e) {
|
|
394
|
+
if (fixedInputs.length === 0) {
|
|
395
|
+
throw Error(
|
|
396
|
+
`Inputs for amount doesn't exist and no fixedInputs provided`,
|
|
397
|
+
);
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
const inputsForAmount = await this._getInputForAmountWithoutUtxoCheck(
|
|
401
|
+
_outputs,
|
|
402
|
+
_feePerByte,
|
|
403
|
+
fixedInputs,
|
|
404
|
+
);
|
|
405
|
+
inputs.push(
|
|
406
|
+
...(inputsForAmount.inputs.map((utxo) => Input.fromUTXO(utxo)) || []),
|
|
407
|
+
);
|
|
408
|
+
outputs.push(...(inputsForAmount.outputs || []));
|
|
409
|
+
}
|
|
410
|
+
_outputs.forEach((output) => {
|
|
411
|
+
const spliceIndex = outputs.findIndex(
|
|
412
|
+
(sweepOutput: Output) => output.value === sweepOutput.value,
|
|
413
|
+
);
|
|
414
|
+
outputs.splice(spliceIndex, 1);
|
|
415
|
+
});
|
|
416
|
+
_outputs.push({
|
|
417
|
+
to: externalChangeAddress,
|
|
418
|
+
value: outputs[0].value,
|
|
419
|
+
});
|
|
420
|
+
return this._buildTransactionWithoutUtxoCheck(
|
|
421
|
+
_outputs,
|
|
422
|
+
_feePerByte,
|
|
423
|
+
inputs,
|
|
424
|
+
);
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
_getInputForAmountWithoutUtxoCheck(
|
|
428
|
+
_outputs: Output[],
|
|
429
|
+
_feePerByte: number,
|
|
430
|
+
fixedInputs: Input[],
|
|
431
|
+
) {
|
|
432
|
+
const utxoBalance = fixedInputs.reduce((a, b) => a + (b['value'] || 0), 0);
|
|
433
|
+
const outputBalance = _outputs.reduce((a, b) => a + (b['value'] || 0), 0);
|
|
434
|
+
const amountToSend =
|
|
435
|
+
utxoBalance -
|
|
436
|
+
_feePerByte * ((_outputs.length + 1) * 39 + fixedInputs.length * 153); // todo better calculation
|
|
437
|
+
|
|
438
|
+
const targets = _outputs.map((target, i) => ({
|
|
439
|
+
id: 'main',
|
|
440
|
+
value: target.value,
|
|
441
|
+
}));
|
|
442
|
+
if (amountToSend - outputBalance > 0) {
|
|
443
|
+
targets.push({ id: 'main', value: amountToSend - outputBalance });
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
return selectCoins(
|
|
447
|
+
fixedInputs,
|
|
448
|
+
targets,
|
|
449
|
+
Math.ceil(_feePerByte),
|
|
450
|
+
fixedInputs,
|
|
451
|
+
);
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
async _buildTransactionWithoutUtxoCheck(
|
|
455
|
+
outputs: Output[],
|
|
456
|
+
feePerByte: number,
|
|
457
|
+
fixedInputs: Input[],
|
|
458
|
+
) {
|
|
459
|
+
const network = this._network;
|
|
460
|
+
|
|
461
|
+
const { fee } = this._getInputForAmountWithoutUtxoCheck(
|
|
462
|
+
outputs,
|
|
463
|
+
feePerByte,
|
|
464
|
+
fixedInputs,
|
|
465
|
+
);
|
|
466
|
+
const inputs = fixedInputs;
|
|
467
|
+
|
|
468
|
+
const txb = new bitcoin.TransactionBuilder(network);
|
|
469
|
+
|
|
470
|
+
for (const output of outputs) {
|
|
471
|
+
const to = output.to; // Allow for OP_RETURN
|
|
472
|
+
txb.addOutput(to, output.value);
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
const prevOutScriptType = 'p2wpkh';
|
|
476
|
+
|
|
477
|
+
for (let i = 0; i < inputs.length; i++) {
|
|
478
|
+
const wallet = await this.getMethod('getWalletAddress')(
|
|
479
|
+
inputs[i].address,
|
|
480
|
+
);
|
|
481
|
+
const keyPair = await this.getMethod('keyPair')(wallet.derivationPath);
|
|
482
|
+
const paymentVariant = this.getMethod('getPaymentVariantFromPublicKey')(
|
|
483
|
+
keyPair.publicKey,
|
|
484
|
+
);
|
|
485
|
+
|
|
486
|
+
txb.addInput(inputs[i].txid, inputs[i].vout, 0, paymentVariant.output);
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
for (let i = 0; i < inputs.length; i++) {
|
|
490
|
+
const wallet = await this.getMethod('getWalletAddress')(
|
|
491
|
+
inputs[i].address,
|
|
492
|
+
);
|
|
493
|
+
const keyPair = await this.getMethod('keyPair')(wallet.derivationPath);
|
|
494
|
+
const paymentVariant = this.getMethod('getPaymentVariantFromPublicKey')(
|
|
495
|
+
keyPair.publicKey,
|
|
496
|
+
);
|
|
497
|
+
const needsWitness = true;
|
|
498
|
+
|
|
499
|
+
const signParams = {
|
|
500
|
+
prevOutScriptType,
|
|
501
|
+
vin: i,
|
|
502
|
+
keyPair,
|
|
503
|
+
witnessValue: 0,
|
|
504
|
+
};
|
|
505
|
+
|
|
506
|
+
if (needsWitness) {
|
|
507
|
+
signParams.witnessValue = inputs[i].value;
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
txb.sign(signParams);
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
return { hex: txb.build().toHex(), fee };
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
async quickFindAddress(addresses: string[]): Promise<Address> {
|
|
517
|
+
const maxAddresses = 500;
|
|
518
|
+
const addressesPerCall = 5;
|
|
519
|
+
let index = 0;
|
|
520
|
+
while (index < maxAddresses) {
|
|
521
|
+
const walletNonChangeAddresses = await this.getMethod('getAddresses')(
|
|
522
|
+
index,
|
|
523
|
+
addressesPerCall,
|
|
524
|
+
true,
|
|
525
|
+
);
|
|
526
|
+
const walletChangeAddresses = await this.getMethod('getAddresses')(
|
|
527
|
+
index,
|
|
528
|
+
addressesPerCall,
|
|
529
|
+
false,
|
|
530
|
+
);
|
|
531
|
+
const walletAddresses = [
|
|
532
|
+
...walletNonChangeAddresses,
|
|
533
|
+
...walletChangeAddresses,
|
|
534
|
+
];
|
|
535
|
+
const walletAddress = walletAddresses.find((walletAddr) =>
|
|
536
|
+
addresses.find((addr) => walletAddr.address === addr),
|
|
537
|
+
);
|
|
538
|
+
if (walletAddress) return walletAddress;
|
|
539
|
+
index += addressesPerCall;
|
|
540
|
+
}
|
|
541
|
+
}
|
|
542
|
+
}
|
package/lib/index.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { default } from './BitcoinWalletProvider';
|
package/package.json
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@atomicfinance/bitcoin-wallet-provider",
|
|
3
|
+
"umdName": "BitcoinWalletProvider",
|
|
4
|
+
"version": "2.2.3",
|
|
5
|
+
"description": "CAL Finance Bitcoin Wallet Provider",
|
|
6
|
+
"author": "Atomic Finance <info@atomic.finance>",
|
|
7
|
+
"homepage": "",
|
|
8
|
+
"license": "ISC",
|
|
9
|
+
"main": "dist/index.js",
|
|
10
|
+
"scripts": {
|
|
11
|
+
"build": "../../node_modules/.bin/tsc --project tsconfig.json",
|
|
12
|
+
"prepublishOnly": "yarn run build",
|
|
13
|
+
"test": "yarn run build",
|
|
14
|
+
"lint": "../../node_modules/.bin/eslint --ignore-path ../../.eslintignore -c ../../.eslintrc.js .",
|
|
15
|
+
"lint:fix": "../../node_modules/.bin/eslint --fix --ignore-path ../../.eslintignore -c ../../.eslintrc.js ."
|
|
16
|
+
},
|
|
17
|
+
"dependencies": {
|
|
18
|
+
"@atomicfinance/provider": "^2.2.3",
|
|
19
|
+
"@atomicfinance/types": "^2.2.3",
|
|
20
|
+
"@liquality/bitcoin-utils": "1.1.5",
|
|
21
|
+
"@liquality/provider": "1.1.5",
|
|
22
|
+
"@liquality/types": "1.1.5",
|
|
23
|
+
"bitcoinjs-lib": "5.2.0",
|
|
24
|
+
"lodash": "^4.17.20",
|
|
25
|
+
"secp256k1": "^4.0.2"
|
|
26
|
+
},
|
|
27
|
+
"devDependencies": {
|
|
28
|
+
"@types/lodash": "^4.14.160",
|
|
29
|
+
"@types/node": "^10.17.55"
|
|
30
|
+
},
|
|
31
|
+
"publishConfig": {
|
|
32
|
+
"access": "public"
|
|
33
|
+
},
|
|
34
|
+
"gitHead": "205d2d24dde632ae2bd931dbf297f78f2919fcc4"
|
|
35
|
+
}
|