@atomicfinance/bitcoin-wallet-provider 2.4.2 → 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 -0
- package/.turbo/turbo-lint.log +1 -0
- package/.turbo/turbo-lint:fix.log +15 -0
- package/.turbo/turbo-test.log +1 -0
- package/CHANGELOG.md +23 -0
- package/dist/BitcoinWalletProvider.d.ts +113 -80
- package/dist/BitcoinWalletProvider.js +446 -305
- package/dist/BitcoinWalletProvider.js.map +1 -1
- package/dist/index.d.ts +0 -0
- package/dist/index.js +0 -0
- package/dist/index.js.map +0 -0
- package/lib/BitcoinWalletProvider.ts +679 -461
- package/package.json +9 -11
- package/LICENSE +0 -674
|
@@ -22,338 +22,479 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
22
22
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
23
23
|
};
|
|
24
24
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
25
|
-
|
|
25
|
+
exports.AddressSearchType = void 0;
|
|
26
|
+
const bitcoin_utils_1 = require("@atomicfinance/bitcoin-utils");
|
|
27
|
+
const errors_1 = require("@atomicfinance/errors");
|
|
26
28
|
const types_1 = require("@atomicfinance/types");
|
|
27
|
-
const
|
|
28
|
-
const assert_1 = __importDefault(require("assert"));
|
|
29
|
+
const utils_1 = require("@atomicfinance/utils");
|
|
29
30
|
const bitcoin = __importStar(require("bitcoinjs-lib"));
|
|
30
|
-
const
|
|
31
|
-
const FEE_PER_BYTE_FALLBACK = 5;
|
|
31
|
+
const memoizee_1 = __importDefault(require("memoizee"));
|
|
32
32
|
const ADDRESS_GAP = 20;
|
|
33
33
|
const NONCHANGE_ADDRESS = 0;
|
|
34
34
|
const CHANGE_ADDRESS = 1;
|
|
35
35
|
const NONCHANGE_OR_CHANGE_ADDRESS = 2;
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
36
|
+
var AddressSearchType;
|
|
37
|
+
(function (AddressSearchType) {
|
|
38
|
+
AddressSearchType[AddressSearchType["EXTERNAL"] = 0] = "EXTERNAL";
|
|
39
|
+
AddressSearchType[AddressSearchType["CHANGE"] = 1] = "CHANGE";
|
|
40
|
+
AddressSearchType[AddressSearchType["EXTERNAL_OR_CHANGE"] = 2] = "EXTERNAL_OR_CHANGE";
|
|
41
|
+
})(AddressSearchType = exports.AddressSearchType || (exports.AddressSearchType = {}));
|
|
42
|
+
exports.default = (superclass) => {
|
|
43
|
+
class BitcoinWalletProvider extends superclass {
|
|
44
|
+
constructor(...args) {
|
|
45
|
+
const options = args[0];
|
|
46
|
+
const { network, baseDerivationPath, addressType = types_1.bitcoin.AddressType.BECH32, } = options;
|
|
47
|
+
const addressTypes = Object.values(types_1.bitcoin.AddressType);
|
|
48
|
+
if (!addressTypes.includes(addressType)) {
|
|
49
|
+
throw new Error(`addressType must be one of ${addressTypes.join(',')}`);
|
|
50
|
+
}
|
|
51
|
+
super(options);
|
|
52
|
+
this._baseDerivationPath = baseDerivationPath;
|
|
53
|
+
this._network = network;
|
|
54
|
+
this._addressType = addressType;
|
|
55
|
+
this._derivationCache = {};
|
|
56
|
+
this._unusedAddressesBlacklist = {};
|
|
57
|
+
this._maxAddressesToDerive = 5000;
|
|
54
58
|
}
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
* https://developer.bitcoin.org/reference/rpc/createmultisig.html
|
|
85
|
-
* https://developer.bitcoin.org/reference/rpc/createpsbt.html
|
|
86
|
-
* @param m the number of required signatures
|
|
87
|
-
* @param pubkeys n possible pubkeys in total
|
|
88
|
-
* @param inputs the Inputs to the PSBT
|
|
89
|
-
* @param ouputs the Outputs to the PSBT
|
|
90
|
-
* @returns a base64 encoded psbt string
|
|
91
|
-
*/
|
|
92
|
-
buildMultisigPSBT(m, pubkeys, inputs, outputs) {
|
|
93
|
-
assert_1.default(inputs.length > 0, 'no inputs found');
|
|
94
|
-
assert_1.default(outputs.length > 0, 'no outputs found');
|
|
95
|
-
const p2wsh = this._createMultisigPayment(m, pubkeys);
|
|
96
|
-
// Verify pubkeyhash for all inputs matches the p2wsh hash
|
|
97
|
-
assert_1.default(inputs.every((input) => p2wsh.output.toString('hex') === input.scriptPubKey), 'address pubkeyhash does not match input scriptPubKey');
|
|
98
|
-
// creator
|
|
99
|
-
const psbt = new bitcoin.Psbt({ network: this._network });
|
|
100
|
-
// updater
|
|
101
|
-
inputs.forEach((input) => {
|
|
102
|
-
psbt.addInput({
|
|
103
|
-
hash: input.txid,
|
|
104
|
-
index: input.vout,
|
|
105
|
-
witnessUtxo: { script: p2wsh.output, value: input.value },
|
|
106
|
-
witnessScript: p2wsh.redeem.output,
|
|
107
|
-
});
|
|
108
|
-
});
|
|
109
|
-
outputs.forEach((output) => {
|
|
110
|
-
psbt.addOutput({
|
|
111
|
-
address: output.to,
|
|
112
|
-
value: output.value,
|
|
59
|
+
getDerivationCache() {
|
|
60
|
+
return this._derivationCache;
|
|
61
|
+
}
|
|
62
|
+
async setDerivationCache(derivationCache) {
|
|
63
|
+
const address = await this.getDerivationPathAddress(Object.keys(derivationCache)[0]);
|
|
64
|
+
if (derivationCache[address.derivationPath].address !== address.address) {
|
|
65
|
+
throw new Error(`derivationCache at ${address.derivationPath} does not match`);
|
|
66
|
+
}
|
|
67
|
+
this._derivationCache = derivationCache;
|
|
68
|
+
}
|
|
69
|
+
sendOptionsToOutputs(transactions) {
|
|
70
|
+
const targets = [];
|
|
71
|
+
transactions.forEach((tx) => {
|
|
72
|
+
if (tx.to && tx.value && tx.value.gt(0)) {
|
|
73
|
+
targets.push({
|
|
74
|
+
address: utils_1.addressToString(tx.to),
|
|
75
|
+
value: tx.value.toNumber(),
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
if (tx.data) {
|
|
79
|
+
const scriptBuffer = bitcoin.script.compile([
|
|
80
|
+
bitcoin.script.OPS.OP_RETURN,
|
|
81
|
+
Buffer.from(tx.data, 'hex'),
|
|
82
|
+
]);
|
|
83
|
+
targets.push({
|
|
84
|
+
value: 0,
|
|
85
|
+
script: scriptBuffer,
|
|
86
|
+
});
|
|
87
|
+
}
|
|
113
88
|
});
|
|
114
|
-
|
|
115
|
-
return psbt.toBase64();
|
|
116
|
-
}
|
|
117
|
-
/**
|
|
118
|
-
* Update a PSBT with input information from our wallet and then sign inputs that we can sign for
|
|
119
|
-
* https://developer.bitcoin.org/reference/rpc/walletprocesspsbt.html
|
|
120
|
-
* @param psbt a base64 encoded psbt string (P2WSH only)
|
|
121
|
-
* @returns a base64 encoded signed psbt string
|
|
122
|
-
*/
|
|
123
|
-
async walletProcessPSBT(psbtString) {
|
|
124
|
-
const psbt = bitcoin.Psbt.fromBase64(psbtString);
|
|
125
|
-
await Promise.all(psbt.data.inputs.map(async (input, i) => {
|
|
126
|
-
assert_1.default(psbt.getInputType(i).slice(0, 5) === 'p2wsh', 'only accepts P2WSH inputs');
|
|
127
|
-
const scriptStack = bitcoin.script.decompile(input.witnessScript);
|
|
128
|
-
const pubkeys = scriptStack.filter((data) => Buffer.isBuffer(data) && secp256k1_1.default.publicKeyVerify(data));
|
|
129
|
-
await Promise.all(pubkeys.map(async (key) => {
|
|
130
|
-
// create address using pubkey
|
|
131
|
-
const { address: addressString } = bitcoin.payments.p2wpkh({
|
|
132
|
-
pubkey: key,
|
|
133
|
-
network: this._network,
|
|
134
|
-
});
|
|
135
|
-
// Retrieve address object from wallet using address
|
|
136
|
-
const address = await this.quickFindAddress([
|
|
137
|
-
addressString,
|
|
138
|
-
]);
|
|
139
|
-
// exit if address doesn't exist in wallet
|
|
140
|
-
if (!address)
|
|
141
|
-
return;
|
|
142
|
-
// derive keypair
|
|
143
|
-
const keyPair = await this.getMethod('keyPair')(address.derivationPath);
|
|
144
|
-
// sign PSBT using keypair
|
|
145
|
-
psbt.signInput(i, keyPair);
|
|
146
|
-
}));
|
|
147
|
-
}));
|
|
148
|
-
psbt.validateSignaturesOfAllInputs(); // ensure all signatures are valid!
|
|
149
|
-
return psbt.toBase64();
|
|
150
|
-
}
|
|
151
|
-
/**
|
|
152
|
-
* Finalize the inputs of a PSBT. If the transaction is fully signed, it will
|
|
153
|
-
* produce a network serialized transaction which can be broadcast with sendrawtransaction
|
|
154
|
-
* https://developer.bitcoin.org/reference/rpc/finalizepsbt.html
|
|
155
|
-
* @param psbt a base64 encoded psbt string
|
|
156
|
-
* @returns a json object containing `psbt` in base64, `hex` for transaction and `complete` for if
|
|
157
|
-
* the transaction has a complete set of signatures
|
|
158
|
-
*/
|
|
159
|
-
finalizePSBT(psbtString) {
|
|
160
|
-
const psbt = bitcoin.Psbt.fromBase64(psbtString);
|
|
161
|
-
try {
|
|
162
|
-
psbt.validateSignaturesOfAllInputs(); // ensure all signatures are valid!
|
|
163
|
-
psbt.finalizeAllInputs();
|
|
89
|
+
return targets;
|
|
164
90
|
}
|
|
165
|
-
|
|
166
|
-
return
|
|
167
|
-
psbt: psbt.toBase64(),
|
|
168
|
-
complete: false,
|
|
169
|
-
};
|
|
91
|
+
async buildTransaction(output, feePerByte) {
|
|
92
|
+
return this._buildTransaction([output], feePerByte);
|
|
170
93
|
}
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
addrList = addrList.concat(nonChangeAddresses);
|
|
94
|
+
async buildBatchTransaction(outputs) {
|
|
95
|
+
return this._buildTransaction(outputs);
|
|
96
|
+
}
|
|
97
|
+
async _sendTransaction(transactions, feePerByte) {
|
|
98
|
+
const { hex, fee } = await this._buildTransaction(transactions, feePerByte);
|
|
99
|
+
await this.getMethod('sendRawTransaction')(hex);
|
|
100
|
+
return bitcoin_utils_1.normalizeTransactionObject(bitcoin_utils_1.decodeRawTransaction(hex, this._network), fee);
|
|
101
|
+
}
|
|
102
|
+
async sendTransaction(options) {
|
|
103
|
+
return this._sendTransaction(this.sendOptionsToOutputs([options]), options.fee);
|
|
104
|
+
}
|
|
105
|
+
async sendBatchTransaction(transactions) {
|
|
106
|
+
return this._sendTransaction(this.sendOptionsToOutputs(transactions));
|
|
107
|
+
}
|
|
108
|
+
async buildSweepTransaction(externalChangeAddress, feePerByte) {
|
|
109
|
+
return this._buildSweepTransaction(externalChangeAddress, feePerByte);
|
|
110
|
+
}
|
|
111
|
+
async sendSweepTransaction(externalChangeAddress, feePerByte) {
|
|
112
|
+
const { hex, fee } = await this._buildSweepTransaction(utils_1.addressToString(externalChangeAddress), feePerByte);
|
|
113
|
+
await this.getMethod('sendRawTransaction')(hex);
|
|
114
|
+
return bitcoin_utils_1.normalizeTransactionObject(bitcoin_utils_1.decodeRawTransaction(hex, this._network), fee);
|
|
115
|
+
}
|
|
116
|
+
getUnusedAddressesBlacklist() {
|
|
117
|
+
return this._unusedAddressesBlacklist;
|
|
118
|
+
}
|
|
119
|
+
setUnusedAddressesBlacklist(unusedAddressesBlacklist) {
|
|
120
|
+
this._unusedAddressesBlacklist = unusedAddressesBlacklist;
|
|
121
|
+
}
|
|
122
|
+
setMaxAddressesToDerive(maxAddressesToDerive) {
|
|
123
|
+
this._maxAddressesToDerive = maxAddressesToDerive;
|
|
124
|
+
}
|
|
125
|
+
getMaxAddressesToDerive() {
|
|
126
|
+
return this._maxAddressesToDerive;
|
|
127
|
+
}
|
|
128
|
+
async updateTransactionFee(tx, newFeePerByte) {
|
|
129
|
+
const txHash = typeof tx === 'string' ? tx : tx.hash;
|
|
130
|
+
const transaction = (await this.getMethod('getTransactionByHash')(txHash))._raw;
|
|
131
|
+
const fixedInputs = [transaction.vin[0]]; // TODO: should this pick more than 1 input? RBF doesn't mandate it
|
|
132
|
+
const lookupAddresses = transaction.vout.map((vout) => vout.scriptPubKey.addresses[0]);
|
|
133
|
+
const changeAddress = await this.findAddress(lookupAddresses, true);
|
|
134
|
+
const changeOutput = transaction.vout.find((vout) => vout.scriptPubKey.addresses[0] === changeAddress.address);
|
|
135
|
+
let outputs = transaction.vout;
|
|
136
|
+
if (changeOutput) {
|
|
137
|
+
outputs = outputs.filter((vout) => vout.scriptPubKey.addresses[0] !==
|
|
138
|
+
changeOutput.scriptPubKey.addresses[0]);
|
|
217
139
|
}
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
140
|
+
// TODO more checks?
|
|
141
|
+
const transactions = outputs.map((output) => ({
|
|
142
|
+
address: output.scriptPubKey.addresses[0],
|
|
143
|
+
value: new types_1.BigNumber(output.value).times(1e8).toNumber(),
|
|
144
|
+
}));
|
|
145
|
+
const { hex, fee } = await this._buildTransaction(transactions, newFeePerByte, fixedInputs);
|
|
146
|
+
await this.getMethod('sendRawTransaction')(hex);
|
|
147
|
+
return bitcoin_utils_1.normalizeTransactionObject(bitcoin_utils_1.decodeRawTransaction(hex, this._network), fee);
|
|
148
|
+
}
|
|
149
|
+
async getUnusedAddress(change = false, numAddressPerCall = 100) {
|
|
150
|
+
const addressType = change ? CHANGE_ADDRESS : NONCHANGE_ADDRESS;
|
|
151
|
+
const key = change ? 'change' : 'nonChange';
|
|
152
|
+
const address = await this._getUsedUnusedAddresses(numAddressPerCall, addressType).then(({ unusedAddress }) => unusedAddress[key]);
|
|
153
|
+
this._unusedAddressesBlacklist[address.address] = true;
|
|
154
|
+
return address;
|
|
155
|
+
}
|
|
156
|
+
async _getUsedUnusedAddresses(numAddressPerCall = 100, addressType) {
|
|
157
|
+
const usedAddresses = [];
|
|
158
|
+
const addressCountMap = { change: 0, nonChange: 0 };
|
|
159
|
+
const unusedAddressMap = { change: null, nonChange: null };
|
|
160
|
+
let addrList;
|
|
161
|
+
let addressIndex = 0;
|
|
162
|
+
let changeAddresses = [];
|
|
163
|
+
let nonChangeAddresses = [];
|
|
164
|
+
/* eslint-disable no-unmodified-loop-condition */
|
|
165
|
+
while ((addressType === NONCHANGE_OR_CHANGE_ADDRESS &&
|
|
166
|
+
(addressCountMap.change < ADDRESS_GAP ||
|
|
167
|
+
addressCountMap.nonChange < ADDRESS_GAP)) ||
|
|
168
|
+
(addressType === NONCHANGE_ADDRESS &&
|
|
169
|
+
addressCountMap.nonChange < ADDRESS_GAP) ||
|
|
170
|
+
(addressType === CHANGE_ADDRESS && addressCountMap.change < ADDRESS_GAP)) {
|
|
171
|
+
/* eslint-enable no-unmodified-loop-condition */
|
|
172
|
+
addrList = [];
|
|
173
|
+
if ((addressType === NONCHANGE_OR_CHANGE_ADDRESS ||
|
|
174
|
+
addressType === CHANGE_ADDRESS) &&
|
|
175
|
+
addressCountMap.change < ADDRESS_GAP) {
|
|
176
|
+
// Scanning for change addr
|
|
177
|
+
changeAddresses = await this.client.wallet.getAddresses(addressIndex, numAddressPerCall, true);
|
|
178
|
+
addrList = addrList.concat(changeAddresses);
|
|
228
179
|
}
|
|
229
180
|
else {
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
181
|
+
changeAddresses = [];
|
|
182
|
+
}
|
|
183
|
+
if ((addressType === NONCHANGE_OR_CHANGE_ADDRESS ||
|
|
184
|
+
addressType === NONCHANGE_ADDRESS) &&
|
|
185
|
+
addressCountMap.nonChange < ADDRESS_GAP) {
|
|
186
|
+
// Scanning for non change addr
|
|
187
|
+
nonChangeAddresses = await this.getAddresses(addressIndex, numAddressPerCall, false);
|
|
188
|
+
addrList = addrList.concat(nonChangeAddresses);
|
|
189
|
+
}
|
|
190
|
+
const transactionCounts = await this.getMethod('getAddressTransactionCounts')(addrList);
|
|
191
|
+
for (const address of addrList) {
|
|
192
|
+
const isUsed = transactionCounts[address.address] > 0 ||
|
|
193
|
+
this._unusedAddressesBlacklist[address.address];
|
|
194
|
+
const isChangeAddress = changeAddresses.find((a) => address.address === a.address);
|
|
195
|
+
const key = isChangeAddress ? 'change' : 'nonChange';
|
|
196
|
+
if (isUsed) {
|
|
197
|
+
usedAddresses.push(address);
|
|
198
|
+
addressCountMap[key] = 0;
|
|
199
|
+
unusedAddressMap[key] = null;
|
|
200
|
+
}
|
|
201
|
+
else {
|
|
202
|
+
addressCountMap[key]++;
|
|
203
|
+
if (!unusedAddressMap[key]) {
|
|
204
|
+
unusedAddressMap[key] = address;
|
|
205
|
+
}
|
|
233
206
|
}
|
|
234
207
|
}
|
|
208
|
+
addressIndex += numAddressPerCall;
|
|
235
209
|
}
|
|
236
|
-
|
|
210
|
+
let firstUnusedAddress;
|
|
211
|
+
const indexNonChange = unusedAddressMap.nonChange
|
|
212
|
+
? unusedAddressMap.nonChange.index
|
|
213
|
+
: Infinity;
|
|
214
|
+
const indexChange = unusedAddressMap.change
|
|
215
|
+
? unusedAddressMap.change.index
|
|
216
|
+
: Infinity;
|
|
217
|
+
if (indexNonChange <= indexChange)
|
|
218
|
+
firstUnusedAddress = unusedAddressMap.nonChange;
|
|
219
|
+
else
|
|
220
|
+
firstUnusedAddress = unusedAddressMap.change;
|
|
221
|
+
return {
|
|
222
|
+
usedAddresses,
|
|
223
|
+
unusedAddress: unusedAddressMap,
|
|
224
|
+
firstUnusedAddress,
|
|
225
|
+
};
|
|
237
226
|
}
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
if (inputsForAmount.change) {
|
|
269
|
-
throw Error('There should not be any change for sweeping transaction');
|
|
227
|
+
async getWalletAddress(address) {
|
|
228
|
+
const foundAddress = await this.findAddress([address]);
|
|
229
|
+
if (foundAddress)
|
|
230
|
+
return foundAddress;
|
|
231
|
+
throw new Error('Wallet does not contain address');
|
|
232
|
+
}
|
|
233
|
+
getAddressFromPublicKey(publicKey) {
|
|
234
|
+
return this.getPaymentVariantFromPublicKey(publicKey).address;
|
|
235
|
+
}
|
|
236
|
+
getPaymentVariantFromPublicKey(publicKey) {
|
|
237
|
+
if (this._addressType === types_1.bitcoin.AddressType.LEGACY) {
|
|
238
|
+
return bitcoin.payments.p2pkh({
|
|
239
|
+
pubkey: publicKey,
|
|
240
|
+
network: this._network,
|
|
241
|
+
});
|
|
242
|
+
}
|
|
243
|
+
else if (this._addressType === types_1.bitcoin.AddressType.P2SH_SEGWIT) {
|
|
244
|
+
return bitcoin.payments.p2sh({
|
|
245
|
+
redeem: bitcoin.payments.p2wpkh({
|
|
246
|
+
pubkey: publicKey,
|
|
247
|
+
network: this._network,
|
|
248
|
+
}),
|
|
249
|
+
network: this._network,
|
|
250
|
+
});
|
|
251
|
+
}
|
|
252
|
+
else if (this._addressType === types_1.bitcoin.AddressType.BECH32) {
|
|
253
|
+
return bitcoin.payments.p2wpkh({
|
|
254
|
+
pubkey: publicKey,
|
|
255
|
+
network: this._network,
|
|
256
|
+
});
|
|
270
257
|
}
|
|
271
|
-
inputs.push(...(inputsForAmount.inputs || []));
|
|
272
|
-
outputs.push(...(inputsForAmount.outputs || []));
|
|
273
258
|
}
|
|
274
|
-
|
|
275
|
-
if (
|
|
276
|
-
|
|
259
|
+
async getDerivationPathAddress(path) {
|
|
260
|
+
if (path in this._derivationCache) {
|
|
261
|
+
return this._derivationCache[path];
|
|
277
262
|
}
|
|
278
|
-
const
|
|
279
|
-
|
|
280
|
-
|
|
263
|
+
const baseDerivationNode = await this.baseDerivationNode();
|
|
264
|
+
const subPath = path.replace(this._baseDerivationPath + '/', '');
|
|
265
|
+
const publicKey = baseDerivationNode.derivePath(subPath).publicKey;
|
|
266
|
+
const address = this.getAddressFromPublicKey(publicKey);
|
|
267
|
+
const addressObject = new types_1.Address({
|
|
268
|
+
address,
|
|
269
|
+
publicKey: publicKey.toString('hex'),
|
|
270
|
+
derivationPath: path,
|
|
271
|
+
});
|
|
272
|
+
this._derivationCache[path] = addressObject;
|
|
273
|
+
return addressObject;
|
|
281
274
|
}
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
275
|
+
/**
|
|
276
|
+
* getAddresses is an optimized version of upstream CAL's getAddresses.
|
|
277
|
+
* It removes the call to `asyncSetImmediate()`, speeding up the function by a factor of 6x.
|
|
278
|
+
*
|
|
279
|
+
* @param startingIndex
|
|
280
|
+
* @param numAddresses
|
|
281
|
+
* @param change
|
|
282
|
+
* @returns {Promise<Address[]>}
|
|
283
|
+
*/
|
|
284
|
+
async getAddresses(startingIndex = 0, numAddresses = 1, change = false) {
|
|
285
|
+
if (numAddresses < 1) {
|
|
286
|
+
throw new Error('You must return at least one address');
|
|
287
|
+
}
|
|
288
|
+
const addresses = [];
|
|
289
|
+
const lastIndex = startingIndex + numAddresses;
|
|
290
|
+
const changeVal = change ? '1' : '0';
|
|
291
|
+
for (let currentIndex = startingIndex; currentIndex < lastIndex; currentIndex++) {
|
|
292
|
+
const subPath = changeVal + '/' + currentIndex;
|
|
293
|
+
const path = this._baseDerivationPath + '/' + subPath;
|
|
294
|
+
const addressObject = await this.getDerivationPathAddress(path);
|
|
295
|
+
addresses.push(addressObject);
|
|
296
|
+
}
|
|
297
|
+
return addresses;
|
|
303
298
|
}
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
299
|
+
/**
|
|
300
|
+
* findAddress is an optimized version of upstream CAL's findAddress.
|
|
301
|
+
*
|
|
302
|
+
* It searches through both change and non-change addresses (if change arg is not provided) each iteration.
|
|
303
|
+
*
|
|
304
|
+
* This is in contrast to the original findAddress function which searches
|
|
305
|
+
* through all non-change addresses before moving on to change addresses.
|
|
306
|
+
*
|
|
307
|
+
* @param addresses
|
|
308
|
+
* @returns {Promise<Address>}
|
|
309
|
+
*/
|
|
310
|
+
async findAddress(addresses, change = null) {
|
|
311
|
+
const addressesPerCall = 20;
|
|
312
|
+
let index = 0;
|
|
313
|
+
while (index < this._maxAddressesToDerive) {
|
|
314
|
+
const walletAddresses = [];
|
|
315
|
+
if (change === null || change === false) {
|
|
316
|
+
walletAddresses.push(...(await this.getAddresses(index, addressesPerCall, false)));
|
|
317
|
+
}
|
|
318
|
+
if (change === null || change === true) {
|
|
319
|
+
walletAddresses.push(...(await this.getAddresses(index, addressesPerCall, true)));
|
|
320
|
+
}
|
|
321
|
+
const walletAddress = walletAddresses.find((walletAddr) => addresses.find((addr) => walletAddr.address === addr));
|
|
322
|
+
if (walletAddress) {
|
|
323
|
+
// Increment max addresses to derive by 100 if found within 100 addresses of maxAddressesToDerive
|
|
324
|
+
this._maxAddressesToDerive = Math.max(this._maxAddressesToDerive, index + 100);
|
|
325
|
+
return walletAddress;
|
|
326
|
+
}
|
|
327
|
+
index += addressesPerCall;
|
|
328
|
+
}
|
|
314
329
|
}
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
const wallet = await this.getMethod('getWalletAddress')(inputs[i].address);
|
|
318
|
-
const keyPair = await this.getMethod('keyPair')(wallet.derivationPath);
|
|
319
|
-
const paymentVariant = this.getMethod('getPaymentVariantFromPublicKey')(keyPair.publicKey);
|
|
320
|
-
txb.addInput(inputs[i].txid, inputs[i].vout, 0, paymentVariant.output);
|
|
330
|
+
async getUsedAddresses(numAddressPerCall = 100) {
|
|
331
|
+
return this._getUsedUnusedAddresses(numAddressPerCall, AddressSearchType.EXTERNAL_OR_CHANGE).then(({ usedAddresses }) => usedAddresses);
|
|
321
332
|
}
|
|
322
|
-
|
|
323
|
-
const
|
|
324
|
-
const
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
const
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
333
|
+
async withCachedUtxos(func) {
|
|
334
|
+
const originalGetMethod = this.getMethod;
|
|
335
|
+
const memoizedGetFeePerByte = memoizee_1.default(this.getMethod('getFeePerByte'), {
|
|
336
|
+
primitive: true,
|
|
337
|
+
});
|
|
338
|
+
const memoizedGetUnspentTransactions = memoizee_1.default(this.getMethod('getUnspentTransactions'), { primitive: true });
|
|
339
|
+
const memoizedGetAddressTransactionCounts = memoizee_1.default(this.getMethod('getAddressTransactionCounts'), {
|
|
340
|
+
primitive: true,
|
|
341
|
+
});
|
|
342
|
+
this.getMethod = (method, requestor = this) => {
|
|
343
|
+
if (method === 'getFeePerByte')
|
|
344
|
+
return memoizedGetFeePerByte;
|
|
345
|
+
if (method === 'getUnspentTransactions')
|
|
346
|
+
return memoizedGetUnspentTransactions;
|
|
347
|
+
else if (method === 'getAddressTransactionCounts')
|
|
348
|
+
return memoizedGetAddressTransactionCounts;
|
|
349
|
+
else
|
|
350
|
+
return originalGetMethod.bind(this)(method, requestor);
|
|
332
351
|
};
|
|
333
|
-
|
|
334
|
-
|
|
352
|
+
const result = await func.bind(this)();
|
|
353
|
+
this.getMethod = originalGetMethod;
|
|
354
|
+
return result;
|
|
355
|
+
}
|
|
356
|
+
async getTotalFee(opts, max) {
|
|
357
|
+
const targets = this.sendOptionsToOutputs([opts]);
|
|
358
|
+
if (!max) {
|
|
359
|
+
const { fee } = await this.getInputsForAmount(targets, opts.fee);
|
|
360
|
+
return fee;
|
|
361
|
+
}
|
|
362
|
+
else {
|
|
363
|
+
const { fee } = await this.getInputsForAmount(targets.filter((t) => !t.value), opts.fee, [], 100, true);
|
|
364
|
+
return fee;
|
|
335
365
|
}
|
|
336
|
-
txb.sign(signParams);
|
|
337
366
|
}
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
367
|
+
async getTotalFees(transactions, max) {
|
|
368
|
+
const fees = await this.withCachedUtxos(async () => {
|
|
369
|
+
const fees = {};
|
|
370
|
+
for (const tx of transactions) {
|
|
371
|
+
const fee = await this.getTotalFee(tx, max);
|
|
372
|
+
fees[tx.fee] = new types_1.BigNumber(fee);
|
|
373
|
+
}
|
|
374
|
+
return fees;
|
|
375
|
+
});
|
|
376
|
+
return fees;
|
|
377
|
+
}
|
|
378
|
+
async getInputsForAmount(_targets, feePerByte, fixedInputs = [], numAddressPerCall = 100, sweep = false) {
|
|
379
|
+
let addressIndex = 0;
|
|
380
|
+
let changeAddresses = [];
|
|
381
|
+
let externalAddresses = [];
|
|
382
|
+
const addressCountMap = {
|
|
383
|
+
change: 0,
|
|
384
|
+
nonChange: 0,
|
|
385
|
+
};
|
|
386
|
+
const feePerBytePromise = this.getMethod('getFeePerByte')();
|
|
387
|
+
let utxos = [];
|
|
388
|
+
while (addressCountMap.change < ADDRESS_GAP ||
|
|
389
|
+
addressCountMap.nonChange < ADDRESS_GAP) {
|
|
390
|
+
let addrList = [];
|
|
391
|
+
if (addressCountMap.change < ADDRESS_GAP) {
|
|
392
|
+
// Scanning for change addr
|
|
393
|
+
changeAddresses = await this.getAddresses(addressIndex, numAddressPerCall, true);
|
|
394
|
+
addrList = addrList.concat(changeAddresses);
|
|
395
|
+
}
|
|
396
|
+
else {
|
|
397
|
+
changeAddresses = [];
|
|
398
|
+
}
|
|
399
|
+
if (addressCountMap.nonChange < ADDRESS_GAP) {
|
|
400
|
+
// Scanning for non change addr
|
|
401
|
+
externalAddresses = await this.getAddresses(addressIndex, numAddressPerCall, false);
|
|
402
|
+
addrList = addrList.concat(externalAddresses);
|
|
403
|
+
}
|
|
404
|
+
const fixedUtxos = [];
|
|
405
|
+
if (fixedInputs.length > 0) {
|
|
406
|
+
for (const input of fixedInputs) {
|
|
407
|
+
const txHex = await this.getMethod('getRawTransactionByHash')(input.txid);
|
|
408
|
+
const tx = bitcoin_utils_1.decodeRawTransaction(txHex, this._network);
|
|
409
|
+
const value = new types_1.BigNumber(tx.vout[input.vout].value)
|
|
410
|
+
.times(1e8)
|
|
411
|
+
.toNumber();
|
|
412
|
+
const address = tx.vout[input.vout].scriptPubKey.addresses[0];
|
|
413
|
+
const walletAddress = await this.getWalletAddress(address);
|
|
414
|
+
const utxo = {
|
|
415
|
+
...input,
|
|
416
|
+
value,
|
|
417
|
+
address,
|
|
418
|
+
derivationPath: walletAddress.derivationPath,
|
|
419
|
+
};
|
|
420
|
+
fixedUtxos.push(utxo);
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
if (!sweep || fixedUtxos.length === 0) {
|
|
424
|
+
const _utxos = await this.getMethod('getUnspentTransactions')(addrList);
|
|
425
|
+
utxos.push(..._utxos.map((utxo) => {
|
|
426
|
+
const addr = addrList.find((a) => a.address === utxo.address);
|
|
427
|
+
return {
|
|
428
|
+
...utxo,
|
|
429
|
+
derivationPath: addr.derivationPath,
|
|
430
|
+
};
|
|
431
|
+
}));
|
|
432
|
+
}
|
|
433
|
+
else {
|
|
434
|
+
utxos = fixedUtxos;
|
|
435
|
+
}
|
|
436
|
+
const utxoBalance = utxos.reduce((a, b) => a + (b.value || 0), 0);
|
|
437
|
+
const transactionCounts = await this.getMethod('getAddressTransactionCounts')(addrList);
|
|
438
|
+
if (!feePerByte)
|
|
439
|
+
feePerByte = await feePerBytePromise;
|
|
440
|
+
const minRelayFee = await this.getMethod('getMinRelayFee')();
|
|
441
|
+
if (feePerByte < minRelayFee) {
|
|
442
|
+
throw new Error(`Fee supplied (${feePerByte} sat/b) too low. Minimum relay fee is ${minRelayFee} sat/b`);
|
|
443
|
+
}
|
|
444
|
+
let targets;
|
|
445
|
+
if (sweep) {
|
|
446
|
+
const outputBalance = _targets.reduce((a, b) => a + (b['value'] || 0), 0);
|
|
447
|
+
const sweepOutputSize = 39;
|
|
448
|
+
const paymentOutputSize = _targets.filter((t) => t.value && t.address).length * 39;
|
|
449
|
+
const scriptOutputSize = _targets
|
|
450
|
+
.filter((t) => !t.value && t.script)
|
|
451
|
+
.reduce((size, t) => size + 39 + t.script.byteLength, 0);
|
|
452
|
+
const outputSize = sweepOutputSize + paymentOutputSize + scriptOutputSize;
|
|
453
|
+
const inputSize = utxos.length * 153;
|
|
454
|
+
const sweepFee = feePerByte * (inputSize + outputSize);
|
|
455
|
+
const amountToSend = new types_1.BigNumber(utxoBalance).minus(sweepFee);
|
|
456
|
+
targets = _targets.map((target) => ({
|
|
457
|
+
id: 'main',
|
|
458
|
+
value: target.value,
|
|
459
|
+
script: target.script,
|
|
460
|
+
}));
|
|
461
|
+
targets.push({
|
|
462
|
+
id: 'main',
|
|
463
|
+
value: amountToSend.minus(outputBalance).toNumber(),
|
|
464
|
+
});
|
|
465
|
+
}
|
|
466
|
+
else {
|
|
467
|
+
targets = _targets.map((target) => ({
|
|
468
|
+
id: 'main',
|
|
469
|
+
value: target.value,
|
|
470
|
+
script: target.script,
|
|
471
|
+
}));
|
|
472
|
+
}
|
|
473
|
+
const { inputs, outputs, change, fee } = bitcoin_utils_1.selectCoins(utxos, targets, Math.ceil(feePerByte), fixedUtxos);
|
|
474
|
+
if (inputs && outputs) {
|
|
475
|
+
return {
|
|
476
|
+
inputs,
|
|
477
|
+
change,
|
|
478
|
+
outputs,
|
|
479
|
+
fee,
|
|
480
|
+
};
|
|
481
|
+
}
|
|
482
|
+
for (const address of addrList) {
|
|
483
|
+
const isUsed = transactionCounts[address.address];
|
|
484
|
+
const isChangeAddress = changeAddresses.find((a) => address.address === a.address);
|
|
485
|
+
const key = isChangeAddress ? 'change' : 'nonChange';
|
|
486
|
+
if (isUsed) {
|
|
487
|
+
addressCountMap[key] = 0;
|
|
488
|
+
}
|
|
489
|
+
else {
|
|
490
|
+
addressCountMap[key]++;
|
|
491
|
+
}
|
|
492
|
+
}
|
|
493
|
+
addressIndex += numAddressPerCall;
|
|
494
|
+
}
|
|
495
|
+
throw new errors_1.InsufficientBalanceError('Not enough balance');
|
|
355
496
|
}
|
|
356
497
|
}
|
|
357
|
-
|
|
358
|
-
|
|
498
|
+
return BitcoinWalletProvider;
|
|
499
|
+
};
|
|
359
500
|
//# sourceMappingURL=BitcoinWalletProvider.js.map
|