@atomiqlabs/lp-lib 10.3.11 → 11.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +3 -1
- package/dist/index.js +3 -4
- package/dist/plugins/IPlugin.d.ts +3 -2
- package/dist/plugins/PluginManager.d.ts +3 -2
- package/dist/plugins/PluginManager.js +2 -2
- package/dist/prices/OKXSwapPrice.d.ts +27 -0
- package/dist/prices/OKXSwapPrice.js +106 -0
- package/dist/swaps/FromBtcBaseSwap.d.ts +5 -1
- package/dist/swaps/FromBtcBaseSwap.js +20 -0
- package/dist/swaps/FromBtcBaseSwapHandler.d.ts +1 -0
- package/dist/swaps/FromBtcBaseSwapHandler.js +1 -1
- package/dist/swaps/FromBtcLnBaseSwapHandler.d.ts +8 -6
- package/dist/swaps/FromBtcLnBaseSwapHandler.js +7 -5
- package/dist/swaps/SwapHandler.d.ts +1 -4
- package/dist/swaps/SwapHandler.js +1 -2
- package/dist/swaps/SwapHandlerSwap.d.ts +4 -0
- package/dist/swaps/SwapHandlerSwap.js +9 -1
- package/dist/swaps/ToBtcBaseSwap.d.ts +3 -1
- package/dist/swaps/ToBtcBaseSwap.js +8 -2
- package/dist/swaps/ToBtcBaseSwapHandler.d.ts +1 -0
- package/dist/swaps/ToBtcBaseSwapHandler.js +1 -1
- package/dist/swaps/frombtc_abstract/FromBtcAbs.d.ts +3 -5
- package/dist/swaps/frombtc_abstract/FromBtcAbs.js +18 -25
- package/dist/swaps/frombtc_abstract/FromBtcSwapAbs.d.ts +1 -4
- package/dist/swaps/frombtc_abstract/FromBtcSwapAbs.js +3 -16
- package/dist/swaps/frombtc_trusted/FromBtcTrusted.d.ts +6 -9
- package/dist/swaps/frombtc_trusted/FromBtcTrusted.js +238 -137
- package/dist/swaps/frombtc_trusted/FromBtcTrustedSwap.d.ts +9 -6
- package/dist/swaps/frombtc_trusted/FromBtcTrustedSwap.js +15 -10
- package/dist/swaps/frombtcln_abstract/FromBtcLnAbs.d.ts +2 -2
- package/dist/swaps/frombtcln_abstract/FromBtcLnAbs.js +42 -62
- package/dist/swaps/frombtcln_abstract/FromBtcLnSwapAbs.d.ts +1 -6
- package/dist/swaps/frombtcln_abstract/FromBtcLnSwapAbs.js +2 -14
- package/dist/swaps/frombtcln_trusted/FromBtcLnTrusted.d.ts +3 -5
- package/dist/swaps/frombtcln_trusted/FromBtcLnTrusted.js +90 -87
- package/dist/swaps/frombtcln_trusted/FromBtcLnTrustedSwap.d.ts +1 -2
- package/dist/swaps/frombtcln_trusted/FromBtcLnTrustedSwap.js +5 -8
- package/dist/swaps/tobtc_abstract/ToBtcAbs.d.ts +5 -125
- package/dist/swaps/tobtc_abstract/ToBtcAbs.js +41 -334
- package/dist/swaps/tobtc_abstract/ToBtcSwapAbs.d.ts +1 -4
- package/dist/swaps/tobtc_abstract/ToBtcSwapAbs.js +2 -11
- package/dist/swaps/tobtcln_abstract/ToBtcLnAbs.d.ts +5 -55
- package/dist/swaps/tobtcln_abstract/ToBtcLnAbs.js +152 -398
- package/dist/swaps/tobtcln_abstract/ToBtcLnSwapAbs.d.ts +1 -6
- package/dist/swaps/tobtcln_abstract/ToBtcLnSwapAbs.js +2 -15
- package/dist/utils/Utils.d.ts +0 -10
- package/dist/utils/Utils.js +1 -34
- package/dist/wallets/IBitcoinWallet.d.ts +62 -0
- package/dist/wallets/IBitcoinWallet.js +2 -0
- package/dist/wallets/ILightningWallet.d.ts +118 -0
- package/dist/wallets/ILightningWallet.js +37 -0
- package/package.json +4 -9
- package/src/index.ts +4 -5
- package/src/plugins/IPlugin.ts +4 -2
- package/src/plugins/PluginManager.ts +6 -3
- package/src/prices/OKXSwapPrice.ts +114 -0
- package/src/swaps/FromBtcBaseSwap.ts +24 -1
- package/src/swaps/FromBtcBaseSwapHandler.ts +6 -2
- package/src/swaps/FromBtcLnBaseSwapHandler.ts +22 -6
- package/src/swaps/SwapHandler.ts +1 -8
- package/src/swaps/SwapHandlerSwap.ts +14 -1
- package/src/swaps/ToBtcBaseSwap.ts +12 -3
- package/src/swaps/ToBtcBaseSwapHandler.ts +6 -2
- package/src/swaps/frombtc_abstract/FromBtcAbs.ts +24 -28
- package/src/swaps/frombtc_abstract/FromBtcSwapAbs.ts +3 -18
- package/src/swaps/frombtc_trusted/FromBtcTrusted.ts +260 -159
- package/src/swaps/frombtc_trusted/FromBtcTrustedSwap.ts +22 -15
- package/src/swaps/frombtcln_abstract/FromBtcLnAbs.ts +69 -79
- package/src/swaps/frombtcln_abstract/FromBtcLnSwapAbs.ts +3 -20
- package/src/swaps/frombtcln_trusted/FromBtcLnTrusted.ts +108 -103
- package/src/swaps/frombtcln_trusted/FromBtcLnTrustedSwap.ts +6 -9
- package/src/swaps/tobtc_abstract/ToBtcAbs.ts +52 -410
- package/src/swaps/tobtc_abstract/ToBtcSwapAbs.ts +3 -18
- package/src/swaps/tobtcln_abstract/ToBtcLnAbs.ts +157 -434
- package/src/swaps/tobtcln_abstract/ToBtcLnSwapAbs.ts +3 -20
- package/src/utils/Utils.ts +0 -31
- package/src/wallets/IBitcoinWallet.ts +66 -0
- package/src/wallets/ILightningWallet.ts +179 -0
- package/dist/fees/OneDollarFeeEstimator.d.ts +0 -16
- package/dist/fees/OneDollarFeeEstimator.js +0 -71
- package/dist/utils/coinselect2/accumulative.d.ts +0 -6
- package/dist/utils/coinselect2/accumulative.js +0 -44
- package/dist/utils/coinselect2/blackjack.d.ts +0 -6
- package/dist/utils/coinselect2/blackjack.js +0 -41
- package/dist/utils/coinselect2/index.d.ts +0 -16
- package/dist/utils/coinselect2/index.js +0 -40
- package/dist/utils/coinselect2/utils.d.ts +0 -64
- package/dist/utils/coinselect2/utils.js +0 -121
- package/src/fees/OneDollarFeeEstimator.ts +0 -95
- package/src/utils/coinselect2/accumulative.js +0 -32
- package/src/utils/coinselect2/accumulative.ts +0 -58
- package/src/utils/coinselect2/blackjack.js +0 -29
- package/src/utils/coinselect2/blackjack.ts +0 -54
- package/src/utils/coinselect2/index.js +0 -16
- package/src/utils/coinselect2/index.ts +0 -50
- package/src/utils/coinselect2/utils.js +0 -110
- package/src/utils/coinselect2/utils.ts +0 -183
|
@@ -1,7 +1,5 @@
|
|
|
1
1
|
import {Express, Request, Response} from "express";
|
|
2
2
|
import * as BN from "bn.js";
|
|
3
|
-
import * as bitcoin from "bitcoinjs-lib";
|
|
4
|
-
import * as lncli from "ln-service";
|
|
5
3
|
import {ToBtcSwapAbs, ToBtcSwapState} from "./ToBtcSwapAbs";
|
|
6
4
|
import {MultichainData, SwapHandlerType} from "../SwapHandler";
|
|
7
5
|
import {ISwapPrice} from "../ISwapPrice";
|
|
@@ -16,13 +14,9 @@ import {
|
|
|
16
14
|
BitcoinRpc,
|
|
17
15
|
BtcBlock
|
|
18
16
|
} from "@atomiqlabs/base";
|
|
19
|
-
import {AuthenticatedLnd} from "lightning";
|
|
20
17
|
import {expressHandlerWrapper, HEX_REGEX, isDefinedRuntimeError} from "../../utils/Utils";
|
|
21
18
|
import {PluginManager} from "../../plugins/PluginManager";
|
|
22
19
|
import {IIntermediaryStorage} from "../../storage/IIntermediaryStorage";
|
|
23
|
-
import {IBtcFeeEstimator} from "../../fees/IBtcFeeEstimator";
|
|
24
|
-
import {coinSelect} from "../../utils/coinselect2";
|
|
25
|
-
import {CoinselectTxInput, CoinselectTxOutput, utils} from "../../utils/coinselect2/utils";
|
|
26
20
|
import {randomBytes} from "crypto";
|
|
27
21
|
import {FieldTypeEnum, verifySchema} from "../../utils/paramcoders/SchemaVerifier";
|
|
28
22
|
import {serverParamDecoder} from "../../utils/paramcoders/server/ServerParamDecoder";
|
|
@@ -30,36 +24,22 @@ import {IParamReader} from "../../utils/paramcoders/IParamReader";
|
|
|
30
24
|
import {ServerParamEncoder} from "../../utils/paramcoders/server/ServerParamEncoder";
|
|
31
25
|
import {ToBtcBaseConfig, ToBtcBaseSwapHandler} from "../ToBtcBaseSwapHandler";
|
|
32
26
|
import {PromiseQueue} from "promise-queue-ts";
|
|
27
|
+
import {IBitcoinWallet} from "../../wallets/IBitcoinWallet";
|
|
33
28
|
|
|
34
29
|
const OUTPUT_SCRIPT_MAX_LENGTH = 200;
|
|
35
30
|
|
|
36
|
-
type SpendableUtxo = {
|
|
37
|
-
address: string,
|
|
38
|
-
address_format: string,
|
|
39
|
-
confirmation_count: number,
|
|
40
|
-
output_script: string,
|
|
41
|
-
tokens: number,
|
|
42
|
-
transaction_id: string,
|
|
43
|
-
transaction_vout: number
|
|
44
|
-
};
|
|
45
|
-
|
|
46
31
|
export type ToBtcConfig = ToBtcBaseConfig & {
|
|
47
32
|
sendSafetyFactor: BN,
|
|
48
33
|
|
|
49
|
-
bitcoinNetwork: bitcoin.networks.Network,
|
|
50
|
-
|
|
51
34
|
minChainCltv: BN,
|
|
52
35
|
|
|
53
|
-
|
|
36
|
+
networkFeeMultiplier: number,
|
|
54
37
|
minConfirmations: number,
|
|
55
38
|
maxConfirmations: number,
|
|
56
39
|
maxConfTarget: number,
|
|
57
40
|
minConfTarget: number,
|
|
58
41
|
|
|
59
|
-
txCheckInterval: number
|
|
60
|
-
|
|
61
|
-
feeEstimator?: IBtcFeeEstimator,
|
|
62
|
-
onchainReservedPerChannel?: number
|
|
42
|
+
txCheckInterval: number
|
|
63
43
|
};
|
|
64
44
|
|
|
65
45
|
export type ToBtcRequestType = {
|
|
@@ -77,28 +57,11 @@ export type ToBtcRequestType = {
|
|
|
77
57
|
* Handler for to BTC swaps, utilizing PTLCs (proof-time locked contracts) using btc relay (on-chain bitcoin SPV)
|
|
78
58
|
*/
|
|
79
59
|
export class ToBtcAbs extends ToBtcBaseSwapHandler<ToBtcSwapAbs, ToBtcSwapState> {
|
|
80
|
-
protected readonly CONFIRMATIONS_REQUIRED = 1;
|
|
81
|
-
protected readonly ADDRESS_FORMAT_MAP = {
|
|
82
|
-
"p2wpkh": "p2wpkh",
|
|
83
|
-
"np2wpkh": "p2sh-p2wpkh",
|
|
84
|
-
"p2tr" : "p2tr"
|
|
85
|
-
};
|
|
86
|
-
protected readonly LND_CHANGE_OUTPUT_TYPE = "p2tr";
|
|
87
|
-
protected readonly UTXO_CACHE_TIMEOUT = 5*1000;
|
|
88
|
-
protected readonly CHANNEL_COUNT_CACHE_TIMEOUT = 30*1000;
|
|
89
|
-
|
|
90
60
|
readonly type = SwapHandlerType.TO_BTC;
|
|
91
61
|
|
|
92
62
|
activeSubscriptions: {[txId: string]: ToBtcSwapAbs} = {};
|
|
93
|
-
cachedUtxos: {
|
|
94
|
-
utxos: (CoinselectTxInput & {confirmations: number})[],
|
|
95
|
-
timestamp: number
|
|
96
|
-
};
|
|
97
|
-
cachedChannelCount: {
|
|
98
|
-
count: number,
|
|
99
|
-
timestamp: number
|
|
100
|
-
};
|
|
101
63
|
bitcoinRpc: BitcoinRpc<BtcBlock>;
|
|
64
|
+
bitcoin: IBitcoinWallet;
|
|
102
65
|
sendBtcQueue: PromiseQueue = new PromiseQueue();
|
|
103
66
|
|
|
104
67
|
readonly config: ToBtcConfig;
|
|
@@ -107,15 +70,15 @@ export class ToBtcAbs extends ToBtcBaseSwapHandler<ToBtcSwapAbs, ToBtcSwapState>
|
|
|
107
70
|
storageDirectory: IIntermediaryStorage<ToBtcSwapAbs>,
|
|
108
71
|
path: string,
|
|
109
72
|
chainData: MultichainData,
|
|
110
|
-
|
|
73
|
+
bitcoin: IBitcoinWallet,
|
|
111
74
|
swapPricing: ISwapPrice,
|
|
112
75
|
bitcoinRpc: BitcoinRpc<BtcBlock>,
|
|
113
76
|
config: ToBtcConfig
|
|
114
77
|
) {
|
|
115
|
-
super(storageDirectory, path, chainData,
|
|
78
|
+
super(storageDirectory, path, chainData, swapPricing);
|
|
116
79
|
this.bitcoinRpc = bitcoinRpc;
|
|
80
|
+
this.bitcoin = bitcoin;
|
|
117
81
|
this.config = config;
|
|
118
|
-
this.config.onchainReservedPerChannel = this.config.onchainReservedPerChannel || 40000;
|
|
119
82
|
}
|
|
120
83
|
|
|
121
84
|
/**
|
|
@@ -125,217 +88,18 @@ export class ToBtcAbs extends ToBtcBaseSwapHandler<ToBtcSwapAbs, ToBtcSwapState>
|
|
|
125
88
|
* @param address
|
|
126
89
|
* @param nonce
|
|
127
90
|
* @param amount
|
|
128
|
-
* @param bitcoinNetwork
|
|
129
91
|
*/
|
|
130
|
-
private getHash(chainIdentifier: string, address: string, nonce: BN, amount: BN
|
|
131
|
-
const parsedOutputScript = bitcoin.
|
|
92
|
+
private getHash(chainIdentifier: string, address: string, nonce: BN, amount: BN): Buffer {
|
|
93
|
+
const parsedOutputScript = this.bitcoin.toOutputScript(address);
|
|
132
94
|
const {swapContract} = this.getChain(chainIdentifier);
|
|
133
95
|
return swapContract.getHashForOnchain(parsedOutputScript, amount, nonce);
|
|
134
96
|
}
|
|
135
97
|
|
|
136
|
-
/**
|
|
137
|
-
* Returns spendable UTXOs, these are either confirmed UTXOs, or unconfirmed ones that are either whitelisted,
|
|
138
|
-
* or created by our transactions (and therefore only we could doublespend)
|
|
139
|
-
*
|
|
140
|
-
* @private
|
|
141
|
-
*/
|
|
142
|
-
protected async getSpendableUtxos(): Promise<SpendableUtxo[]> {
|
|
143
|
-
const resBlockheight = await lncli.getHeight({
|
|
144
|
-
lnd: this.LND
|
|
145
|
-
});
|
|
146
|
-
|
|
147
|
-
const blockheight: number = resBlockheight.current_block_height;
|
|
148
|
-
|
|
149
|
-
const resChainTxns = await lncli.getChainTransactions({
|
|
150
|
-
lnd: this.LND,
|
|
151
|
-
after: blockheight-this.CONFIRMATIONS_REQUIRED
|
|
152
|
-
});
|
|
153
|
-
|
|
154
|
-
const selfUTXOs: Set<string> = PluginManager.getWhitelistedTxIds();
|
|
155
|
-
|
|
156
|
-
const transactions = resChainTxns.transactions;
|
|
157
|
-
for(let tx of transactions) {
|
|
158
|
-
if(tx.is_outgoing) {
|
|
159
|
-
selfUTXOs.add(tx.id);
|
|
160
|
-
}
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
const resUtxos = await lncli.getUtxos({
|
|
164
|
-
lnd: this.LND
|
|
165
|
-
});
|
|
166
|
-
|
|
167
|
-
return resUtxos.utxos.filter(utxo => utxo.confirmation_count>=this.CONFIRMATIONS_REQUIRED || selfUTXOs.has(utxo.transaction_id));
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
/**
|
|
171
|
-
* Returns utxo pool to be used by the coinselection algorithm
|
|
172
|
-
*
|
|
173
|
-
* @private
|
|
174
|
-
*/
|
|
175
|
-
protected async getUtxoPool(useCached: boolean = false): Promise<(CoinselectTxInput & {confirmations: number})[]> {
|
|
176
|
-
if(!useCached || this.cachedUtxos==null || this.cachedUtxos.timestamp<Date.now()-this.UTXO_CACHE_TIMEOUT) {
|
|
177
|
-
const utxos = await this.getSpendableUtxos();
|
|
178
|
-
|
|
179
|
-
let totalSpendable = 0;
|
|
180
|
-
const utxoPool = utxos.map(utxo => {
|
|
181
|
-
totalSpendable += utxo.tokens;
|
|
182
|
-
return {
|
|
183
|
-
vout: utxo.transaction_vout,
|
|
184
|
-
txId: utxo.transaction_id,
|
|
185
|
-
value: utxo.tokens,
|
|
186
|
-
type: this.ADDRESS_FORMAT_MAP[utxo.address_format],
|
|
187
|
-
outputScript: Buffer.from(utxo.output_script, "hex"),
|
|
188
|
-
address: utxo.address,
|
|
189
|
-
confirmations: utxo.confirmation_count
|
|
190
|
-
};
|
|
191
|
-
});
|
|
192
|
-
|
|
193
|
-
this.cachedUtxos = {
|
|
194
|
-
utxos: utxoPool,
|
|
195
|
-
timestamp: Date.now()
|
|
196
|
-
};
|
|
197
|
-
|
|
198
|
-
this.logger.info("getUtxoPool(): total spendable value: "+totalSpendable+" num utxos: "+utxoPool.length);
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
return this.cachedUtxos.utxos;
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
/**
|
|
205
|
-
* Checks whether a coinselect result leaves enough funds to cover potential lightning anchor transaction fees
|
|
206
|
-
*
|
|
207
|
-
* @param utxoPool
|
|
208
|
-
* @param obj
|
|
209
|
-
* @param satsPerVbyte
|
|
210
|
-
* @param useCached Whether to use a cached channel count
|
|
211
|
-
* @param initialOutputLength
|
|
212
|
-
* @private
|
|
213
|
-
* @returns true if alright, false if the coinselection doesn't leave enough funds for anchor fees
|
|
214
|
-
*/
|
|
215
|
-
protected async isLeavingEnoughForLightningAnchors(
|
|
216
|
-
utxoPool: CoinselectTxInput[],
|
|
217
|
-
obj: {inputs?: CoinselectTxInput[], outputs?: CoinselectTxOutput[]},
|
|
218
|
-
satsPerVbyte: BN,
|
|
219
|
-
useCached: boolean = false,
|
|
220
|
-
initialOutputLength: number = 1
|
|
221
|
-
): Promise<boolean> {
|
|
222
|
-
if(obj.inputs==null || obj.outputs==null) return false;
|
|
223
|
-
const spentInputs = new Set<string>();
|
|
224
|
-
obj.inputs.forEach(txIn => {
|
|
225
|
-
spentInputs.add(txIn.txId+":"+txIn.vout);
|
|
226
|
-
});
|
|
227
|
-
|
|
228
|
-
let leavesValue: BN = new BN(0);
|
|
229
|
-
utxoPool.forEach(val => {
|
|
230
|
-
const utxoEconomicalValue: BN = new BN(val.value).sub(satsPerVbyte.mul(new BN(utils.inputBytes(val).length)));
|
|
231
|
-
if (
|
|
232
|
-
//Utxo not spent
|
|
233
|
-
!spentInputs.has(val.txId + ":" + val.vout) &&
|
|
234
|
-
//Only economical utxos at current fees
|
|
235
|
-
!utxoEconomicalValue.isNeg()
|
|
236
|
-
) {
|
|
237
|
-
leavesValue = leavesValue.add(utxoEconomicalValue);
|
|
238
|
-
}
|
|
239
|
-
});
|
|
240
|
-
if(obj.outputs.length>initialOutputLength) {
|
|
241
|
-
const changeUtxo = obj.outputs[obj.outputs.length-1];
|
|
242
|
-
leavesValue = leavesValue.add(
|
|
243
|
-
new BN(changeUtxo.value).sub(satsPerVbyte.mul(new BN(utils.inputBytes(changeUtxo).length)))
|
|
244
|
-
);
|
|
245
|
-
}
|
|
246
|
-
|
|
247
|
-
if(!useCached || this.cachedChannelCount==null || this.cachedChannelCount.timestamp<Date.now()-this.CHANNEL_COUNT_CACHE_TIMEOUT) {
|
|
248
|
-
const {channels} = await lncli.getChannels({lnd: this.LND});
|
|
249
|
-
this.cachedChannelCount = {
|
|
250
|
-
count: channels.length,
|
|
251
|
-
timestamp: Date.now()
|
|
252
|
-
}
|
|
253
|
-
}
|
|
254
|
-
|
|
255
|
-
return leavesValue.gt(new BN(this.config.onchainReservedPerChannel).mul(new BN(this.cachedChannelCount.count)));
|
|
256
|
-
}
|
|
257
|
-
|
|
258
|
-
/**
|
|
259
|
-
* Gets the change address from the underlying LND instance
|
|
260
|
-
*
|
|
261
|
-
* @private
|
|
262
|
-
*/
|
|
263
|
-
protected getChangeAddress(): Promise<string> {
|
|
264
|
-
return new Promise((resolve, reject) => {
|
|
265
|
-
this.LND.wallet.nextAddr({
|
|
266
|
-
type: 4,
|
|
267
|
-
change: true
|
|
268
|
-
}, (err, res) => {
|
|
269
|
-
if(err!=null) {
|
|
270
|
-
reject([503, 'UnexpectedErrGettingNextAddr', {err}]);
|
|
271
|
-
return;
|
|
272
|
-
}
|
|
273
|
-
resolve(res.addr);
|
|
274
|
-
});
|
|
275
|
-
});
|
|
276
|
-
}
|
|
277
|
-
|
|
278
|
-
/**
|
|
279
|
-
* Computes bitcoin on-chain network fee, takes channel reserve & network fee multiplier into consideration
|
|
280
|
-
*
|
|
281
|
-
* @param targetAddress Bitcoin address to send the funds to
|
|
282
|
-
* @param targetAmount Amount of funds to send to the address
|
|
283
|
-
* @param estimate Whether the chain fee should be just estimated and therefore cached utxo set could be used
|
|
284
|
-
* @param multiplierPPM Multiplier for the sats/vB returned from the fee estimator in PPM (parts per million)
|
|
285
|
-
* @private
|
|
286
|
-
* @returns Fee estimate & inputs/outputs to use when constructing transaction, or null in case of not enough funds
|
|
287
|
-
*/
|
|
288
|
-
private async getChainFee(targetAddress: string, targetAmount: number, estimate: boolean = false, multiplierPPM?: BN): Promise<{
|
|
289
|
-
satsPerVbyte: BN,
|
|
290
|
-
networkFee: BN,
|
|
291
|
-
inputs: CoinselectTxInput[],
|
|
292
|
-
outputs: CoinselectTxOutput[]
|
|
293
|
-
} | null> {
|
|
294
|
-
let feeRate: number | null = this.config.feeEstimator==null
|
|
295
|
-
? await lncli.getChainFeeRate({lnd: this.LND})
|
|
296
|
-
.then(res => res.tokens_per_vbyte)
|
|
297
|
-
.catch(e => this.logger.error("getChainFee(): LND getChainFeeRate error", e))
|
|
298
|
-
: await this.config.feeEstimator.estimateFee();
|
|
299
|
-
|
|
300
|
-
if(feeRate==null) return null;
|
|
301
|
-
|
|
302
|
-
let satsPerVbyte = new BN(Math.ceil(feeRate));
|
|
303
|
-
if(multiplierPPM!=null) satsPerVbyte = satsPerVbyte.mul(multiplierPPM).div(new BN(1000000));
|
|
304
|
-
|
|
305
|
-
const utxoPool: CoinselectTxInput[] = await this.getUtxoPool(estimate);
|
|
306
|
-
|
|
307
|
-
let obj = coinSelect(utxoPool, [{
|
|
308
|
-
address: targetAddress,
|
|
309
|
-
value: targetAmount,
|
|
310
|
-
script: bitcoin.address.toOutputScript(targetAddress, this.config.bitcoinNetwork)
|
|
311
|
-
}], satsPerVbyte.toNumber(), this.LND_CHANGE_OUTPUT_TYPE);
|
|
312
|
-
|
|
313
|
-
if(obj.inputs==null || obj.outputs==null) return null;
|
|
314
|
-
|
|
315
|
-
if(!await this.isLeavingEnoughForLightningAnchors(utxoPool, obj, satsPerVbyte, estimate)) return null;
|
|
316
|
-
|
|
317
|
-
this.logger.info("getChainFee(): fee estimated,"+
|
|
318
|
-
" target: "+targetAddress+
|
|
319
|
-
" amount: "+targetAmount.toString(10)+
|
|
320
|
-
" fee: "+obj.fee+
|
|
321
|
-
" sats/vB: "+satsPerVbyte+
|
|
322
|
-
" inputs: "+obj.inputs.length+
|
|
323
|
-
" outputs: "+obj.outputs.length+
|
|
324
|
-
" multiplier: "+(multiplierPPM==null ? 1 : multiplierPPM.toNumber()/1000000));
|
|
325
|
-
|
|
326
|
-
return {
|
|
327
|
-
networkFee: new BN(obj.fee),
|
|
328
|
-
satsPerVbyte,
|
|
329
|
-
outputs: obj.outputs,
|
|
330
|
-
inputs: obj.inputs
|
|
331
|
-
};
|
|
332
|
-
}
|
|
333
|
-
|
|
334
98
|
/**
|
|
335
99
|
* Tries to claim the swap after our transaction was confirmed
|
|
336
100
|
*
|
|
337
101
|
* @param tx
|
|
338
|
-
* @param
|
|
102
|
+
* @param swap
|
|
339
103
|
* @param vout
|
|
340
104
|
*/
|
|
341
105
|
private async tryClaimSwap(tx: {blockhash: string, confirmations: number, txid: string, hex: string}, swap: ToBtcSwapAbs, vout: number): Promise<boolean> {
|
|
@@ -365,10 +129,8 @@ export class ToBtcAbs extends ToBtcBaseSwapHandler<ToBtcSwapAbs, ToBtcSwapState>
|
|
|
365
129
|
protected async processPastSwap(swap: ToBtcSwapAbs) {
|
|
366
130
|
const {swapContract, signer} = this.getChain(swap.chainIdentifier);
|
|
367
131
|
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
if(swap.state===ToBtcSwapState.SAVED && swap.signatureExpiry!=null) {
|
|
371
|
-
const isSignatureExpired = swap.signatureExpiry.lt(timestamp);
|
|
132
|
+
if(swap.state===ToBtcSwapState.SAVED) {
|
|
133
|
+
const isSignatureExpired = swapContract.isInitAuthorizationExpired(swap.data, swap);
|
|
372
134
|
if(isSignatureExpired) {
|
|
373
135
|
const isCommitted = await swapContract.isCommited(swap.data);
|
|
374
136
|
if(!isCommitted) {
|
|
@@ -384,8 +146,7 @@ export class ToBtcAbs extends ToBtcBaseSwapHandler<ToBtcSwapAbs, ToBtcSwapState>
|
|
|
384
146
|
}
|
|
385
147
|
|
|
386
148
|
if(swap.state===ToBtcSwapState.NON_PAYABLE || swap.state===ToBtcSwapState.SAVED) {
|
|
387
|
-
|
|
388
|
-
if(isSwapExpired) {
|
|
149
|
+
if(swapContract.isExpired(signer.getAddress(), swap.data)) {
|
|
389
150
|
this.swapLogger.info(swap, "processPastSwap(state=NON_PAYABLE|SAVED): swap expired, cancelling, address: "+swap.address);
|
|
390
151
|
await this.removeSwapData(swap, ToBtcSwapState.CANCELED);
|
|
391
152
|
return;
|
|
@@ -450,7 +211,7 @@ export class ToBtcAbs extends ToBtcBaseSwapHandler<ToBtcSwapAbs, ToBtcSwapState>
|
|
|
450
211
|
this.swapLogger.debug(swap, "processBtcTx(): address: "+swap.address+" amount: "+swap.amount.toString(10)+" btcTx: "+tx);
|
|
451
212
|
|
|
452
213
|
//Search for required transaction output (vout)
|
|
453
|
-
const outputScript = bitcoin.
|
|
214
|
+
const outputScript = this.bitcoin.toOutputScript(swap.address);
|
|
454
215
|
const vout = tx.outs.find(e => new BN(e.value).eq(swap.amount) && Buffer.from(e.scriptPubKey.hex, "hex").equals(outputScript));
|
|
455
216
|
if(vout==null) {
|
|
456
217
|
this.swapLogger.warn(swap, "processBtcTx(): cannot find correct vout,"+
|
|
@@ -477,7 +238,7 @@ export class ToBtcAbs extends ToBtcBaseSwapHandler<ToBtcSwapAbs, ToBtcSwapState>
|
|
|
477
238
|
const swap: ToBtcSwapAbs = this.activeSubscriptions[txId];
|
|
478
239
|
//TODO: RBF the transaction if it's already taking too long to confirm
|
|
479
240
|
try {
|
|
480
|
-
let tx: BtcTx = await this.
|
|
241
|
+
let tx: BtcTx = await this.bitcoin.getWalletTransaction(txId);
|
|
481
242
|
if(tx==null) continue;
|
|
482
243
|
|
|
483
244
|
if(await this.processBtcTx(swap, tx)) {
|
|
@@ -549,133 +310,12 @@ export class ToBtcAbs extends ToBtcBaseSwapHandler<ToBtcSwapAbs, ToBtcSwapState>
|
|
|
549
310
|
code: 90003,
|
|
550
311
|
msg: "Fee changed too much!",
|
|
551
312
|
data: {
|
|
552
|
-
quotedFee:
|
|
553
|
-
actualFee:
|
|
313
|
+
quotedFee: quotedSatsPerVbyte.toString(10),
|
|
314
|
+
actualFee: actualSatsPerVbyte.toString(10)
|
|
554
315
|
}
|
|
555
316
|
};
|
|
556
317
|
}
|
|
557
318
|
|
|
558
|
-
/**
|
|
559
|
-
* Runs sanity check on the calculated fee for the transaction
|
|
560
|
-
*
|
|
561
|
-
* @param psbt
|
|
562
|
-
* @param tx
|
|
563
|
-
* @param maxAllowedSatsPerVbyte
|
|
564
|
-
* @param actualSatsPerVbyte
|
|
565
|
-
* @private
|
|
566
|
-
* @throws {Error} Will throw an error if the fee sanity check doesn't pass
|
|
567
|
-
*/
|
|
568
|
-
protected checkPsbtFee(
|
|
569
|
-
psbt: bitcoin.Psbt,
|
|
570
|
-
tx: bitcoin.Transaction,
|
|
571
|
-
maxAllowedSatsPerVbyte: BN,
|
|
572
|
-
actualSatsPerVbyte: BN
|
|
573
|
-
): BN {
|
|
574
|
-
const txFee = new BN(psbt.getFee());
|
|
575
|
-
|
|
576
|
-
//Sanity check on sats/vB
|
|
577
|
-
const maxAllowedFee = new BN(tx.virtualSize())
|
|
578
|
-
//Considering the extra output was not added, because was detrminetal
|
|
579
|
-
.add(new BN(utils.outputBytes({type: this.LND_CHANGE_OUTPUT_TYPE})))
|
|
580
|
-
//Multiply by maximum allowed feerate
|
|
581
|
-
.mul(maxAllowedSatsPerVbyte)
|
|
582
|
-
//Possibility that extra output was not added due to it being lower than dust
|
|
583
|
-
.add(new BN(utils.dustThreshold({type: this.LND_CHANGE_OUTPUT_TYPE})));
|
|
584
|
-
|
|
585
|
-
if(txFee.gt(maxAllowedFee)) throw new Error("Generated tx fee too high: "+JSON.stringify({
|
|
586
|
-
maxAllowedFee: maxAllowedFee.toString(10),
|
|
587
|
-
actualFee: txFee.toString(10),
|
|
588
|
-
psbtHex: psbt.toHex(),
|
|
589
|
-
maxAllowedSatsPerVbyte: maxAllowedSatsPerVbyte.toString(10),
|
|
590
|
-
actualSatsPerVbyte: actualSatsPerVbyte.toString(10)
|
|
591
|
-
}));
|
|
592
|
-
|
|
593
|
-
return txFee;
|
|
594
|
-
}
|
|
595
|
-
|
|
596
|
-
/**
|
|
597
|
-
* Create PSBT for swap payout from coinselection result
|
|
598
|
-
*
|
|
599
|
-
* @param address
|
|
600
|
-
* @param amount
|
|
601
|
-
* @param escrowNonce
|
|
602
|
-
* @param coinselectResult
|
|
603
|
-
* @private
|
|
604
|
-
*/
|
|
605
|
-
private async getPsbt(
|
|
606
|
-
address: string,
|
|
607
|
-
amount: BN,
|
|
608
|
-
escrowNonce: BN,
|
|
609
|
-
coinselectResult: {inputs: CoinselectTxInput[], outputs: CoinselectTxOutput[]}
|
|
610
|
-
): Promise<bitcoin.Psbt> {
|
|
611
|
-
let psbt = new bitcoin.Psbt();
|
|
612
|
-
|
|
613
|
-
//Apply nonce
|
|
614
|
-
const nonceBuffer = Buffer.from(escrowNonce.toArray("be", 8));
|
|
615
|
-
|
|
616
|
-
const locktimeBN = new BN(nonceBuffer.slice(0, 5), "be");
|
|
617
|
-
let locktime = locktimeBN.toNumber() + 500000000;
|
|
618
|
-
psbt.setLocktime(locktime);
|
|
619
|
-
|
|
620
|
-
const sequenceBN = new BN(nonceBuffer.slice(5, 8), "be");
|
|
621
|
-
const sequence = 0xFE000000 + sequenceBN.toNumber();
|
|
622
|
-
psbt.addInputs(coinselectResult.inputs.map(input => {
|
|
623
|
-
return {
|
|
624
|
-
hash: input.txId,
|
|
625
|
-
index: input.vout,
|
|
626
|
-
witnessUtxo: {
|
|
627
|
-
script: input.outputScript,
|
|
628
|
-
value: input.value
|
|
629
|
-
},
|
|
630
|
-
sighashType: 0x01,
|
|
631
|
-
sequence
|
|
632
|
-
};
|
|
633
|
-
}));
|
|
634
|
-
|
|
635
|
-
psbt.addOutput({
|
|
636
|
-
script: bitcoin.address.toOutputScript(address, this.config.bitcoinNetwork),
|
|
637
|
-
value: amount.toNumber()
|
|
638
|
-
});
|
|
639
|
-
|
|
640
|
-
//Add change output
|
|
641
|
-
if(coinselectResult.outputs.length>1) psbt.addOutput({
|
|
642
|
-
script: bitcoin.address.toOutputScript(await this.getChangeAddress(), this.config.bitcoinNetwork),
|
|
643
|
-
value: coinselectResult.outputs[1].value
|
|
644
|
-
});
|
|
645
|
-
|
|
646
|
-
return psbt;
|
|
647
|
-
}
|
|
648
|
-
|
|
649
|
-
/**
|
|
650
|
-
* Signs provided PSBT and also returns a raw signed transaction
|
|
651
|
-
*
|
|
652
|
-
* @param psbt
|
|
653
|
-
* @private
|
|
654
|
-
*/
|
|
655
|
-
protected async signPsbt(psbt: bitcoin.Psbt): Promise<{psbt: bitcoin.Psbt, rawTx: string}> {
|
|
656
|
-
const signedPsbt = await lncli.signPsbt({
|
|
657
|
-
lnd: this.LND,
|
|
658
|
-
psbt: psbt.toHex()
|
|
659
|
-
});
|
|
660
|
-
return {
|
|
661
|
-
psbt: bitcoin.Psbt.fromHex(signedPsbt.psbt),
|
|
662
|
-
rawTx: signedPsbt.transaction
|
|
663
|
-
};
|
|
664
|
-
}
|
|
665
|
-
|
|
666
|
-
/**
|
|
667
|
-
* Sends raw bitcoin transaction
|
|
668
|
-
*
|
|
669
|
-
* @param rawTx
|
|
670
|
-
* @private
|
|
671
|
-
*/
|
|
672
|
-
protected async sendRawTransaction(rawTx: string): Promise<void> {
|
|
673
|
-
await lncli.broadcastChainTransaction({
|
|
674
|
-
lnd: this.LND,
|
|
675
|
-
transaction: rawTx
|
|
676
|
-
});
|
|
677
|
-
}
|
|
678
|
-
|
|
679
319
|
/**
|
|
680
320
|
* Sends a bitcoin transaction to payout BTC for a swap
|
|
681
321
|
*
|
|
@@ -691,37 +331,32 @@ export class ToBtcAbs extends ToBtcBaseSwapHandler<ToBtcSwapAbs, ToBtcSwapState>
|
|
|
691
331
|
this.checkExpiresTooSoon(swap);
|
|
692
332
|
if(swap.metadata!=null) swap.metadata.times.payCLTVChecked = Date.now();
|
|
693
333
|
|
|
694
|
-
const
|
|
695
|
-
|
|
696
|
-
code: 90002,
|
|
697
|
-
msg: "Failed to run coinselect algorithm (not enough funds?)"
|
|
698
|
-
}
|
|
334
|
+
const satsPerVbyte = await this.bitcoin.getFeeRate();
|
|
335
|
+
this.checkCalculatedTxFee(swap.satsPerVbyte, new BN(satsPerVbyte));
|
|
699
336
|
if(swap.metadata!=null) swap.metadata.times.payChainFee = Date.now();
|
|
700
337
|
|
|
701
|
-
this.
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
338
|
+
const signResult = await this.bitcoin.getSignedTransaction(
|
|
339
|
+
swap.address,
|
|
340
|
+
swap.amount.toNumber(),
|
|
341
|
+
satsPerVbyte,
|
|
342
|
+
swap.data.getEscrowNonce(),
|
|
343
|
+
swap.satsPerVbyte.toNumber()
|
|
344
|
+
);
|
|
345
|
+
if(signResult==null) throw {
|
|
346
|
+
code: 90002,
|
|
347
|
+
msg: "Failed to create signed transaction (not enough funds?)"
|
|
348
|
+
}
|
|
709
349
|
if(swap.metadata!=null) swap.metadata.times.paySignPSBT = Date.now();
|
|
710
|
-
this.swapLogger.debug(swap, "sendBitcoinPayment(): signed raw transaction: "+rawTx);
|
|
711
350
|
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
swap.txId = tx.getId();
|
|
716
|
-
swap.setRealNetworkFee(txFee);
|
|
351
|
+
this.swapLogger.debug(swap, "sendBitcoinPayment(): signed raw transaction: "+signResult.raw);
|
|
352
|
+
swap.txId = signResult.tx.getId();
|
|
353
|
+
swap.setRealNetworkFee(new BN(signResult.networkFee));
|
|
717
354
|
await swap.setState(ToBtcSwapState.BTC_SENDING);
|
|
718
355
|
await this.storageManager.saveData(swap.getHash(), swap.getSequence(), swap);
|
|
719
356
|
|
|
720
|
-
await this.sendRawTransaction(
|
|
357
|
+
await this.bitcoin.sendRawTransaction(signResult.raw);
|
|
721
358
|
if(swap.metadata!=null) swap.metadata.times.payTxSent = Date.now();
|
|
722
|
-
this.swapLogger.info(swap, "sendBitcoinPayment(): btc transaction generated, signed & broadcasted, txId: "+tx.getId()+" address: "+swap.address);
|
|
723
|
-
//Invalidate the UTXO cache
|
|
724
|
-
this.cachedUtxos = null;
|
|
359
|
+
this.swapLogger.info(swap, "sendBitcoinPayment(): btc transaction generated, signed & broadcasted, txId: "+signResult.tx.getId()+" address: "+swap.address);
|
|
725
360
|
|
|
726
361
|
await swap.setState(ToBtcSwapState.BTC_SENT);
|
|
727
362
|
await this.storageManager.saveData(swap.getHash(), swap.getSequence(), swap);
|
|
@@ -736,7 +371,7 @@ export class ToBtcAbs extends ToBtcBaseSwapHandler<ToBtcSwapAbs, ToBtcSwapState>
|
|
|
736
371
|
private async processInitialized(swap: ToBtcSwapAbs) {
|
|
737
372
|
if(swap.state===ToBtcSwapState.BTC_SENDING) {
|
|
738
373
|
//Bitcoin transaction was signed (maybe also sent)
|
|
739
|
-
const tx = await this.
|
|
374
|
+
const tx = await this.bitcoin.getWalletTransaction(swap.txId);
|
|
740
375
|
|
|
741
376
|
const isTxSent = tx!=null;
|
|
742
377
|
if(!isTxSent) {
|
|
@@ -913,7 +548,7 @@ export class ToBtcAbs extends ToBtcBaseSwapHandler<ToBtcSwapAbs, ToBtcSwapState>
|
|
|
913
548
|
let parsedOutputScript: Buffer;
|
|
914
549
|
|
|
915
550
|
try {
|
|
916
|
-
parsedOutputScript = bitcoin.
|
|
551
|
+
parsedOutputScript = this.bitcoin.toOutputScript(address);
|
|
917
552
|
} catch (e) {
|
|
918
553
|
throw {
|
|
919
554
|
code: 20031,
|
|
@@ -934,7 +569,8 @@ export class ToBtcAbs extends ToBtcBaseSwapHandler<ToBtcSwapAbs, ToBtcSwapState>
|
|
|
934
569
|
* @throws {DefinedRuntimeError} will throw an error if the swap is expired
|
|
935
570
|
*/
|
|
936
571
|
protected checkExpired(swap: ToBtcSwapAbs) {
|
|
937
|
-
const
|
|
572
|
+
const {swapContract, signer} = this.getChain(swap.chainIdentifier);
|
|
573
|
+
const isExpired = swapContract.isExpired(signer.getAddress(), swap.data);
|
|
938
574
|
if(isExpired) throw {
|
|
939
575
|
_httpStatus: 200,
|
|
940
576
|
code: 20010,
|
|
@@ -950,7 +586,7 @@ export class ToBtcAbs extends ToBtcBaseSwapHandler<ToBtcSwapAbs, ToBtcSwapState>
|
|
|
950
586
|
* @throws {DefinedRuntimeError} will throw an error if there are not enough BTC funds
|
|
951
587
|
*/
|
|
952
588
|
private async checkAndGetNetworkFee(address: string, amount: BN): Promise<{ networkFee: BN, satsPerVbyte: BN }> {
|
|
953
|
-
let chainFeeResp = await this.
|
|
589
|
+
let chainFeeResp = await this.bitcoin.estimateFee(address, amount.toNumber(), null, this.config.networkFeeMultiplier);
|
|
954
590
|
|
|
955
591
|
const hasEnoughFunds = chainFeeResp!=null;
|
|
956
592
|
if(!hasEnoughFunds) throw {
|
|
@@ -958,7 +594,10 @@ export class ToBtcAbs extends ToBtcBaseSwapHandler<ToBtcSwapAbs, ToBtcSwapState>
|
|
|
958
594
|
msg: "Not enough liquidity"
|
|
959
595
|
};
|
|
960
596
|
|
|
961
|
-
return
|
|
597
|
+
return {
|
|
598
|
+
networkFee: new BN(chainFeeResp.networkFee),
|
|
599
|
+
satsPerVbyte: new BN(chainFeeResp.satsPerVbyte)
|
|
600
|
+
};
|
|
962
601
|
}
|
|
963
602
|
|
|
964
603
|
startRestServer(restServer: Express) {
|
|
@@ -1047,7 +686,7 @@ export class ToBtcAbs extends ToBtcBaseSwapHandler<ToBtcSwapAbs, ToBtcSwapState>
|
|
|
1047
686
|
}, abortController.signal, pricePrefetchPromise);
|
|
1048
687
|
metadata.times.priceCalculated = Date.now();
|
|
1049
688
|
|
|
1050
|
-
const paymentHash = this.getHash(chainIdentifier, parsedBody.address, parsedBody.nonce, amountBD
|
|
689
|
+
const paymentHash = this.getHash(chainIdentifier, parsedBody.address, parsedBody.nonce, amountBD).toString("hex");
|
|
1051
690
|
|
|
1052
691
|
//Add grace period another time, so the user has 1 hour to commit
|
|
1053
692
|
const expirySeconds = this.getExpiryFromCLTV(parsedBody.confirmationTarget, parsedBody.confirmations).add(new BN(this.config.gracePeriod));
|
|
@@ -1087,11 +726,14 @@ export class ToBtcAbs extends ToBtcBaseSwapHandler<ToBtcSwapAbs, ToBtcSwapState>
|
|
|
1087
726
|
networkFeeInToken,
|
|
1088
727
|
networkFeeData.satsPerVbyte,
|
|
1089
728
|
parsedBody.nonce,
|
|
1090
|
-
parsedBody.confirmationTarget
|
|
1091
|
-
new BN(sigData.timeout)
|
|
729
|
+
parsedBody.confirmationTarget
|
|
1092
730
|
);
|
|
1093
731
|
createdSwap.data = payObject;
|
|
1094
732
|
createdSwap.metadata = metadata;
|
|
733
|
+
createdSwap.prefix = sigData.prefix;
|
|
734
|
+
createdSwap.timeout = sigData.timeout;
|
|
735
|
+
createdSwap.signature = sigData.signature
|
|
736
|
+
createdSwap.feeRate = sigData.feeRate;
|
|
1095
737
|
|
|
1096
738
|
await PluginManager.swapCreate(createdSwap);
|
|
1097
739
|
await this.storageManager.saveData(paymentHash, sequence, createdSwap);
|
|
@@ -1147,8 +789,6 @@ export class ToBtcAbs extends ToBtcBaseSwapHandler<ToBtcSwapAbs, ToBtcSwapState>
|
|
|
1147
789
|
msg: "Payment not found"
|
|
1148
790
|
};
|
|
1149
791
|
|
|
1150
|
-
const {swapContract, signer} = this.getChain(payment.chainIdentifier);
|
|
1151
|
-
|
|
1152
792
|
this.checkExpired(payment);
|
|
1153
793
|
|
|
1154
794
|
if (payment.state === ToBtcSwapState.COMMITED) throw {
|
|
@@ -1166,6 +806,8 @@ export class ToBtcAbs extends ToBtcBaseSwapHandler<ToBtcSwapAbs, ToBtcSwapState>
|
|
|
1166
806
|
}
|
|
1167
807
|
};
|
|
1168
808
|
|
|
809
|
+
const {swapContract, signer} = this.getChain(payment.chainIdentifier);
|
|
810
|
+
|
|
1169
811
|
if (payment.state === ToBtcSwapState.NON_PAYABLE) {
|
|
1170
812
|
const isCommited = await swapContract.isCommited(payment.data);
|
|
1171
813
|
if (!isCommited) throw {
|