@atomiqlabs/lp-lib 10.3.11

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.
Files changed (138) hide show
  1. package/LICENSE +201 -0
  2. package/dist/fees/IBtcFeeEstimator.d.ts +3 -0
  3. package/dist/fees/IBtcFeeEstimator.js +2 -0
  4. package/dist/fees/OneDollarFeeEstimator.d.ts +16 -0
  5. package/dist/fees/OneDollarFeeEstimator.js +71 -0
  6. package/dist/index.d.ts +33 -0
  7. package/dist/index.js +52 -0
  8. package/dist/info/InfoHandler.d.ts +17 -0
  9. package/dist/info/InfoHandler.js +70 -0
  10. package/dist/plugins/IPlugin.d.ts +118 -0
  11. package/dist/plugins/IPlugin.js +33 -0
  12. package/dist/plugins/PluginManager.d.ts +89 -0
  13. package/dist/plugins/PluginManager.js +263 -0
  14. package/dist/prices/BinanceSwapPrice.d.ts +27 -0
  15. package/dist/prices/BinanceSwapPrice.js +106 -0
  16. package/dist/prices/CoinGeckoSwapPrice.d.ts +31 -0
  17. package/dist/prices/CoinGeckoSwapPrice.js +76 -0
  18. package/dist/storage/IIntermediaryStorage.d.ts +15 -0
  19. package/dist/storage/IIntermediaryStorage.js +2 -0
  20. package/dist/storagemanager/IntermediaryStorageManager.d.ts +15 -0
  21. package/dist/storagemanager/IntermediaryStorageManager.js +113 -0
  22. package/dist/storagemanager/StorageManager.d.ts +12 -0
  23. package/dist/storagemanager/StorageManager.js +74 -0
  24. package/dist/swaps/FromBtcBaseSwap.d.ts +12 -0
  25. package/dist/swaps/FromBtcBaseSwap.js +16 -0
  26. package/dist/swaps/FromBtcBaseSwapHandler.d.ts +118 -0
  27. package/dist/swaps/FromBtcBaseSwapHandler.js +294 -0
  28. package/dist/swaps/FromBtcLnBaseSwapHandler.d.ts +25 -0
  29. package/dist/swaps/FromBtcLnBaseSwapHandler.js +55 -0
  30. package/dist/swaps/ISwapPrice.d.ts +44 -0
  31. package/dist/swaps/ISwapPrice.js +73 -0
  32. package/dist/swaps/SwapHandler.d.ts +186 -0
  33. package/dist/swaps/SwapHandler.js +292 -0
  34. package/dist/swaps/SwapHandlerSwap.d.ts +75 -0
  35. package/dist/swaps/SwapHandlerSwap.js +72 -0
  36. package/dist/swaps/ToBtcBaseSwap.d.ts +35 -0
  37. package/dist/swaps/ToBtcBaseSwap.js +61 -0
  38. package/dist/swaps/ToBtcBaseSwapHandler.d.ts +94 -0
  39. package/dist/swaps/ToBtcBaseSwapHandler.js +233 -0
  40. package/dist/swaps/frombtc_abstract/FromBtcAbs.d.ts +92 -0
  41. package/dist/swaps/frombtc_abstract/FromBtcAbs.js +386 -0
  42. package/dist/swaps/frombtc_abstract/FromBtcSwapAbs.d.ts +26 -0
  43. package/dist/swaps/frombtc_abstract/FromBtcSwapAbs.js +63 -0
  44. package/dist/swaps/frombtc_trusted/FromBtcTrusted.d.ts +55 -0
  45. package/dist/swaps/frombtc_trusted/FromBtcTrusted.js +586 -0
  46. package/dist/swaps/frombtc_trusted/FromBtcTrustedSwap.d.ts +43 -0
  47. package/dist/swaps/frombtc_trusted/FromBtcTrustedSwap.js +99 -0
  48. package/dist/swaps/frombtcln_abstract/FromBtcLnAbs.d.ts +105 -0
  49. package/dist/swaps/frombtcln_abstract/FromBtcLnAbs.js +731 -0
  50. package/dist/swaps/frombtcln_abstract/FromBtcLnSwapAbs.d.ts +29 -0
  51. package/dist/swaps/frombtcln_abstract/FromBtcLnSwapAbs.js +64 -0
  52. package/dist/swaps/frombtcln_trusted/FromBtcLnTrusted.d.ts +79 -0
  53. package/dist/swaps/frombtcln_trusted/FromBtcLnTrusted.js +514 -0
  54. package/dist/swaps/frombtcln_trusted/FromBtcLnTrustedSwap.d.ts +28 -0
  55. package/dist/swaps/frombtcln_trusted/FromBtcLnTrustedSwap.js +66 -0
  56. package/dist/swaps/tobtc_abstract/ToBtcAbs.d.ts +290 -0
  57. package/dist/swaps/tobtc_abstract/ToBtcAbs.js +1056 -0
  58. package/dist/swaps/tobtc_abstract/ToBtcSwapAbs.d.ts +29 -0
  59. package/dist/swaps/tobtc_abstract/ToBtcSwapAbs.js +70 -0
  60. package/dist/swaps/tobtcln_abstract/ToBtcLnAbs.d.ts +246 -0
  61. package/dist/swaps/tobtcln_abstract/ToBtcLnAbs.js +1169 -0
  62. package/dist/swaps/tobtcln_abstract/ToBtcLnSwapAbs.d.ts +27 -0
  63. package/dist/swaps/tobtcln_abstract/ToBtcLnSwapAbs.js +65 -0
  64. package/dist/utils/Utils.d.ts +32 -0
  65. package/dist/utils/Utils.js +109 -0
  66. package/dist/utils/coinselect2/accumulative.d.ts +6 -0
  67. package/dist/utils/coinselect2/accumulative.js +44 -0
  68. package/dist/utils/coinselect2/blackjack.d.ts +6 -0
  69. package/dist/utils/coinselect2/blackjack.js +41 -0
  70. package/dist/utils/coinselect2/index.d.ts +16 -0
  71. package/dist/utils/coinselect2/index.js +40 -0
  72. package/dist/utils/coinselect2/utils.d.ts +64 -0
  73. package/dist/utils/coinselect2/utils.js +121 -0
  74. package/dist/utils/paramcoders/IParamReader.d.ts +5 -0
  75. package/dist/utils/paramcoders/IParamReader.js +2 -0
  76. package/dist/utils/paramcoders/IParamWriter.d.ts +4 -0
  77. package/dist/utils/paramcoders/IParamWriter.js +2 -0
  78. package/dist/utils/paramcoders/LegacyParamEncoder.d.ts +10 -0
  79. package/dist/utils/paramcoders/LegacyParamEncoder.js +33 -0
  80. package/dist/utils/paramcoders/ParamDecoder.d.ts +25 -0
  81. package/dist/utils/paramcoders/ParamDecoder.js +234 -0
  82. package/dist/utils/paramcoders/ParamEncoder.d.ts +9 -0
  83. package/dist/utils/paramcoders/ParamEncoder.js +22 -0
  84. package/dist/utils/paramcoders/SchemaVerifier.d.ts +22 -0
  85. package/dist/utils/paramcoders/SchemaVerifier.js +85 -0
  86. package/dist/utils/paramcoders/server/ServerParamDecoder.d.ts +8 -0
  87. package/dist/utils/paramcoders/server/ServerParamDecoder.js +105 -0
  88. package/dist/utils/paramcoders/server/ServerParamEncoder.d.ts +11 -0
  89. package/dist/utils/paramcoders/server/ServerParamEncoder.js +76 -0
  90. package/package.json +43 -0
  91. package/src/fees/IBtcFeeEstimator.ts +7 -0
  92. package/src/fees/OneDollarFeeEstimator.ts +95 -0
  93. package/src/index.ts +46 -0
  94. package/src/info/InfoHandler.ts +106 -0
  95. package/src/plugins/IPlugin.ts +155 -0
  96. package/src/plugins/PluginManager.ts +310 -0
  97. package/src/prices/BinanceSwapPrice.ts +114 -0
  98. package/src/prices/CoinGeckoSwapPrice.ts +88 -0
  99. package/src/storage/IIntermediaryStorage.ts +21 -0
  100. package/src/storagemanager/IntermediaryStorageManager.ts +101 -0
  101. package/src/storagemanager/StorageManager.ts +68 -0
  102. package/src/swaps/FromBtcBaseSwap.ts +21 -0
  103. package/src/swaps/FromBtcBaseSwapHandler.ts +375 -0
  104. package/src/swaps/FromBtcLnBaseSwapHandler.ts +48 -0
  105. package/src/swaps/ISwapPrice.ts +94 -0
  106. package/src/swaps/SwapHandler.ts +404 -0
  107. package/src/swaps/SwapHandlerSwap.ts +133 -0
  108. package/src/swaps/ToBtcBaseSwap.ts +76 -0
  109. package/src/swaps/ToBtcBaseSwapHandler.ts +309 -0
  110. package/src/swaps/frombtc_abstract/FromBtcAbs.ts +484 -0
  111. package/src/swaps/frombtc_abstract/FromBtcSwapAbs.ts +77 -0
  112. package/src/swaps/frombtc_trusted/FromBtcTrusted.ts +661 -0
  113. package/src/swaps/frombtc_trusted/FromBtcTrustedSwap.ts +158 -0
  114. package/src/swaps/frombtcln_abstract/FromBtcLnAbs.ts +864 -0
  115. package/src/swaps/frombtcln_abstract/FromBtcLnSwapAbs.ts +82 -0
  116. package/src/swaps/frombtcln_trusted/FromBtcLnTrusted.ts +592 -0
  117. package/src/swaps/frombtcln_trusted/FromBtcLnTrustedSwap.ts +90 -0
  118. package/src/swaps/tobtc_abstract/ToBtcAbs.ts +1249 -0
  119. package/src/swaps/tobtc_abstract/ToBtcSwapAbs.ts +112 -0
  120. package/src/swaps/tobtcln_abstract/ToBtcLnAbs.ts +1422 -0
  121. package/src/swaps/tobtcln_abstract/ToBtcLnSwapAbs.ts +87 -0
  122. package/src/utils/Utils.ts +108 -0
  123. package/src/utils/coinselect2/accumulative.js +32 -0
  124. package/src/utils/coinselect2/accumulative.ts +58 -0
  125. package/src/utils/coinselect2/blackjack.js +29 -0
  126. package/src/utils/coinselect2/blackjack.ts +54 -0
  127. package/src/utils/coinselect2/index.js +16 -0
  128. package/src/utils/coinselect2/index.ts +50 -0
  129. package/src/utils/coinselect2/utils.js +110 -0
  130. package/src/utils/coinselect2/utils.ts +183 -0
  131. package/src/utils/paramcoders/IParamReader.ts +8 -0
  132. package/src/utils/paramcoders/IParamWriter.ts +8 -0
  133. package/src/utils/paramcoders/LegacyParamEncoder.ts +28 -0
  134. package/src/utils/paramcoders/ParamDecoder.ts +219 -0
  135. package/src/utils/paramcoders/ParamEncoder.ts +30 -0
  136. package/src/utils/paramcoders/SchemaVerifier.ts +97 -0
  137. package/src/utils/paramcoders/server/ServerParamDecoder.ts +115 -0
  138. package/src/utils/paramcoders/server/ServerParamEncoder.ts +76 -0
@@ -0,0 +1,1056 @@
1
+ "use strict";
2
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
3
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
4
+ return new (P || (P = Promise))(function (resolve, reject) {
5
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
6
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
7
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
8
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
9
+ });
10
+ };
11
+ Object.defineProperty(exports, "__esModule", { value: true });
12
+ exports.ToBtcAbs = void 0;
13
+ const BN = require("bn.js");
14
+ const bitcoin = require("bitcoinjs-lib");
15
+ const lncli = require("ln-service");
16
+ const ToBtcSwapAbs_1 = require("./ToBtcSwapAbs");
17
+ const SwapHandler_1 = require("../SwapHandler");
18
+ const base_1 = require("@atomiqlabs/base");
19
+ const Utils_1 = require("../../utils/Utils");
20
+ const PluginManager_1 = require("../../plugins/PluginManager");
21
+ const coinselect2_1 = require("../../utils/coinselect2");
22
+ const utils_1 = require("../../utils/coinselect2/utils");
23
+ const crypto_1 = require("crypto");
24
+ const SchemaVerifier_1 = require("../../utils/paramcoders/SchemaVerifier");
25
+ const ServerParamDecoder_1 = require("../../utils/paramcoders/server/ServerParamDecoder");
26
+ const ToBtcBaseSwapHandler_1 = require("../ToBtcBaseSwapHandler");
27
+ const promise_queue_ts_1 = require("promise-queue-ts");
28
+ const OUTPUT_SCRIPT_MAX_LENGTH = 200;
29
+ /**
30
+ * Handler for to BTC swaps, utilizing PTLCs (proof-time locked contracts) using btc relay (on-chain bitcoin SPV)
31
+ */
32
+ class ToBtcAbs extends ToBtcBaseSwapHandler_1.ToBtcBaseSwapHandler {
33
+ constructor(storageDirectory, path, chainData, lnd, swapPricing, bitcoinRpc, config) {
34
+ super(storageDirectory, path, chainData, lnd, swapPricing);
35
+ this.CONFIRMATIONS_REQUIRED = 1;
36
+ this.ADDRESS_FORMAT_MAP = {
37
+ "p2wpkh": "p2wpkh",
38
+ "np2wpkh": "p2sh-p2wpkh",
39
+ "p2tr": "p2tr"
40
+ };
41
+ this.LND_CHANGE_OUTPUT_TYPE = "p2tr";
42
+ this.UTXO_CACHE_TIMEOUT = 5 * 1000;
43
+ this.CHANNEL_COUNT_CACHE_TIMEOUT = 30 * 1000;
44
+ this.type = SwapHandler_1.SwapHandlerType.TO_BTC;
45
+ this.activeSubscriptions = {};
46
+ this.sendBtcQueue = new promise_queue_ts_1.PromiseQueue();
47
+ this.bitcoinRpc = bitcoinRpc;
48
+ this.config = config;
49
+ this.config.onchainReservedPerChannel = this.config.onchainReservedPerChannel || 40000;
50
+ }
51
+ /**
52
+ * Returns the payment hash of the swap, takes swap nonce into account. Payment hash is chain-specific.
53
+ *
54
+ * @param chainIdentifier
55
+ * @param address
56
+ * @param nonce
57
+ * @param amount
58
+ * @param bitcoinNetwork
59
+ */
60
+ getHash(chainIdentifier, address, nonce, amount, bitcoinNetwork) {
61
+ const parsedOutputScript = bitcoin.address.toOutputScript(address, bitcoinNetwork);
62
+ const { swapContract } = this.getChain(chainIdentifier);
63
+ return swapContract.getHashForOnchain(parsedOutputScript, amount, nonce);
64
+ }
65
+ /**
66
+ * Returns spendable UTXOs, these are either confirmed UTXOs, or unconfirmed ones that are either whitelisted,
67
+ * or created by our transactions (and therefore only we could doublespend)
68
+ *
69
+ * @private
70
+ */
71
+ getSpendableUtxos() {
72
+ return __awaiter(this, void 0, void 0, function* () {
73
+ const resBlockheight = yield lncli.getHeight({
74
+ lnd: this.LND
75
+ });
76
+ const blockheight = resBlockheight.current_block_height;
77
+ const resChainTxns = yield lncli.getChainTransactions({
78
+ lnd: this.LND,
79
+ after: blockheight - this.CONFIRMATIONS_REQUIRED
80
+ });
81
+ const selfUTXOs = PluginManager_1.PluginManager.getWhitelistedTxIds();
82
+ const transactions = resChainTxns.transactions;
83
+ for (let tx of transactions) {
84
+ if (tx.is_outgoing) {
85
+ selfUTXOs.add(tx.id);
86
+ }
87
+ }
88
+ const resUtxos = yield lncli.getUtxos({
89
+ lnd: this.LND
90
+ });
91
+ return resUtxos.utxos.filter(utxo => utxo.confirmation_count >= this.CONFIRMATIONS_REQUIRED || selfUTXOs.has(utxo.transaction_id));
92
+ });
93
+ }
94
+ /**
95
+ * Returns utxo pool to be used by the coinselection algorithm
96
+ *
97
+ * @private
98
+ */
99
+ getUtxoPool(useCached = false) {
100
+ return __awaiter(this, void 0, void 0, function* () {
101
+ if (!useCached || this.cachedUtxos == null || this.cachedUtxos.timestamp < Date.now() - this.UTXO_CACHE_TIMEOUT) {
102
+ const utxos = yield this.getSpendableUtxos();
103
+ let totalSpendable = 0;
104
+ const utxoPool = utxos.map(utxo => {
105
+ totalSpendable += utxo.tokens;
106
+ return {
107
+ vout: utxo.transaction_vout,
108
+ txId: utxo.transaction_id,
109
+ value: utxo.tokens,
110
+ type: this.ADDRESS_FORMAT_MAP[utxo.address_format],
111
+ outputScript: Buffer.from(utxo.output_script, "hex"),
112
+ address: utxo.address,
113
+ confirmations: utxo.confirmation_count
114
+ };
115
+ });
116
+ this.cachedUtxos = {
117
+ utxos: utxoPool,
118
+ timestamp: Date.now()
119
+ };
120
+ this.logger.info("getUtxoPool(): total spendable value: " + totalSpendable + " num utxos: " + utxoPool.length);
121
+ }
122
+ return this.cachedUtxos.utxos;
123
+ });
124
+ }
125
+ /**
126
+ * Checks whether a coinselect result leaves enough funds to cover potential lightning anchor transaction fees
127
+ *
128
+ * @param utxoPool
129
+ * @param obj
130
+ * @param satsPerVbyte
131
+ * @param useCached Whether to use a cached channel count
132
+ * @param initialOutputLength
133
+ * @private
134
+ * @returns true if alright, false if the coinselection doesn't leave enough funds for anchor fees
135
+ */
136
+ isLeavingEnoughForLightningAnchors(utxoPool, obj, satsPerVbyte, useCached = false, initialOutputLength = 1) {
137
+ return __awaiter(this, void 0, void 0, function* () {
138
+ if (obj.inputs == null || obj.outputs == null)
139
+ return false;
140
+ const spentInputs = new Set();
141
+ obj.inputs.forEach(txIn => {
142
+ spentInputs.add(txIn.txId + ":" + txIn.vout);
143
+ });
144
+ let leavesValue = new BN(0);
145
+ utxoPool.forEach(val => {
146
+ const utxoEconomicalValue = new BN(val.value).sub(satsPerVbyte.mul(new BN(utils_1.utils.inputBytes(val).length)));
147
+ if (
148
+ //Utxo not spent
149
+ !spentInputs.has(val.txId + ":" + val.vout) &&
150
+ //Only economical utxos at current fees
151
+ !utxoEconomicalValue.isNeg()) {
152
+ leavesValue = leavesValue.add(utxoEconomicalValue);
153
+ }
154
+ });
155
+ if (obj.outputs.length > initialOutputLength) {
156
+ const changeUtxo = obj.outputs[obj.outputs.length - 1];
157
+ leavesValue = leavesValue.add(new BN(changeUtxo.value).sub(satsPerVbyte.mul(new BN(utils_1.utils.inputBytes(changeUtxo).length))));
158
+ }
159
+ if (!useCached || this.cachedChannelCount == null || this.cachedChannelCount.timestamp < Date.now() - this.CHANNEL_COUNT_CACHE_TIMEOUT) {
160
+ const { channels } = yield lncli.getChannels({ lnd: this.LND });
161
+ this.cachedChannelCount = {
162
+ count: channels.length,
163
+ timestamp: Date.now()
164
+ };
165
+ }
166
+ return leavesValue.gt(new BN(this.config.onchainReservedPerChannel).mul(new BN(this.cachedChannelCount.count)));
167
+ });
168
+ }
169
+ /**
170
+ * Gets the change address from the underlying LND instance
171
+ *
172
+ * @private
173
+ */
174
+ getChangeAddress() {
175
+ return new Promise((resolve, reject) => {
176
+ this.LND.wallet.nextAddr({
177
+ type: 4,
178
+ change: true
179
+ }, (err, res) => {
180
+ if (err != null) {
181
+ reject([503, 'UnexpectedErrGettingNextAddr', { err }]);
182
+ return;
183
+ }
184
+ resolve(res.addr);
185
+ });
186
+ });
187
+ }
188
+ /**
189
+ * Computes bitcoin on-chain network fee, takes channel reserve & network fee multiplier into consideration
190
+ *
191
+ * @param targetAddress Bitcoin address to send the funds to
192
+ * @param targetAmount Amount of funds to send to the address
193
+ * @param estimate Whether the chain fee should be just estimated and therefore cached utxo set could be used
194
+ * @param multiplierPPM Multiplier for the sats/vB returned from the fee estimator in PPM (parts per million)
195
+ * @private
196
+ * @returns Fee estimate & inputs/outputs to use when constructing transaction, or null in case of not enough funds
197
+ */
198
+ getChainFee(targetAddress, targetAmount, estimate = false, multiplierPPM) {
199
+ return __awaiter(this, void 0, void 0, function* () {
200
+ let feeRate = this.config.feeEstimator == null
201
+ ? yield lncli.getChainFeeRate({ lnd: this.LND })
202
+ .then(res => res.tokens_per_vbyte)
203
+ .catch(e => this.logger.error("getChainFee(): LND getChainFeeRate error", e))
204
+ : yield this.config.feeEstimator.estimateFee();
205
+ if (feeRate == null)
206
+ return null;
207
+ let satsPerVbyte = new BN(Math.ceil(feeRate));
208
+ if (multiplierPPM != null)
209
+ satsPerVbyte = satsPerVbyte.mul(multiplierPPM).div(new BN(1000000));
210
+ const utxoPool = yield this.getUtxoPool(estimate);
211
+ let obj = (0, coinselect2_1.coinSelect)(utxoPool, [{
212
+ address: targetAddress,
213
+ value: targetAmount,
214
+ script: bitcoin.address.toOutputScript(targetAddress, this.config.bitcoinNetwork)
215
+ }], satsPerVbyte.toNumber(), this.LND_CHANGE_OUTPUT_TYPE);
216
+ if (obj.inputs == null || obj.outputs == null)
217
+ return null;
218
+ if (!(yield this.isLeavingEnoughForLightningAnchors(utxoPool, obj, satsPerVbyte, estimate)))
219
+ return null;
220
+ this.logger.info("getChainFee(): fee estimated," +
221
+ " target: " + targetAddress +
222
+ " amount: " + targetAmount.toString(10) +
223
+ " fee: " + obj.fee +
224
+ " sats/vB: " + satsPerVbyte +
225
+ " inputs: " + obj.inputs.length +
226
+ " outputs: " + obj.outputs.length +
227
+ " multiplier: " + (multiplierPPM == null ? 1 : multiplierPPM.toNumber() / 1000000));
228
+ return {
229
+ networkFee: new BN(obj.fee),
230
+ satsPerVbyte,
231
+ outputs: obj.outputs,
232
+ inputs: obj.inputs
233
+ };
234
+ });
235
+ }
236
+ /**
237
+ * Tries to claim the swap after our transaction was confirmed
238
+ *
239
+ * @param tx
240
+ * @param payment
241
+ * @param vout
242
+ */
243
+ tryClaimSwap(tx, swap, vout) {
244
+ return __awaiter(this, void 0, void 0, function* () {
245
+ const { swapContract, signer } = this.getChain(swap.chainIdentifier);
246
+ const blockHeader = yield this.bitcoinRpc.getBlockHeader(tx.blockhash);
247
+ //Set flag that we are sending the transaction already, so we don't end up with race condition
248
+ const unlock = swap.lock(swapContract.claimWithTxDataTimeout);
249
+ if (unlock == null)
250
+ return false;
251
+ try {
252
+ this.swapLogger.debug(swap, "tryClaimSwap(): initiate claim of swap, height: " + blockHeader.getHeight() + " utxo: " + tx.txid + ":" + vout);
253
+ const result = yield swapContract.claimWithTxData(signer, swap.data, blockHeader.getHeight(), tx, vout, null, null, false, {
254
+ waitForConfirmation: true
255
+ });
256
+ this.swapLogger.info(swap, "tryClaimSwap(): swap claimed successfully, height: " + blockHeader.getHeight() + " utxo: " + tx.txid + ":" + vout + " address: " + swap.address);
257
+ if (swap.metadata != null)
258
+ swap.metadata.times.txClaimed = Date.now();
259
+ unlock();
260
+ return true;
261
+ }
262
+ catch (e) {
263
+ this.swapLogger.error(swap, "tryClaimSwap(): error occurred claiming swap, height: " + blockHeader.getHeight() + " utxo: " + tx.txid + ":" + vout + " address: " + swap.address, e);
264
+ return false;
265
+ }
266
+ });
267
+ }
268
+ processPastSwap(swap) {
269
+ return __awaiter(this, void 0, void 0, function* () {
270
+ const { swapContract, signer } = this.getChain(swap.chainIdentifier);
271
+ const timestamp = new BN(Math.floor(Date.now() / 1000)).sub(new BN(this.config.maxSkew));
272
+ if (swap.state === ToBtcSwapAbs_1.ToBtcSwapState.SAVED && swap.signatureExpiry != null) {
273
+ const isSignatureExpired = swap.signatureExpiry.lt(timestamp);
274
+ if (isSignatureExpired) {
275
+ const isCommitted = yield swapContract.isCommited(swap.data);
276
+ if (!isCommitted) {
277
+ this.swapLogger.info(swap, "processPastSwap(state=SAVED): authorization expired & swap not committed, cancelling swap, address: " + swap.address);
278
+ yield this.removeSwapData(swap, ToBtcSwapAbs_1.ToBtcSwapState.CANCELED);
279
+ }
280
+ else {
281
+ this.swapLogger.info(swap, "processPastSwap(state=SAVED): swap committed (detected from processPastSwap), address: " + swap.address);
282
+ yield swap.setState(ToBtcSwapAbs_1.ToBtcSwapState.COMMITED);
283
+ yield this.storageManager.saveData(swap.getHash(), swap.data.getSequence(), swap);
284
+ }
285
+ return;
286
+ }
287
+ }
288
+ if (swap.state === ToBtcSwapAbs_1.ToBtcSwapState.NON_PAYABLE || swap.state === ToBtcSwapAbs_1.ToBtcSwapState.SAVED) {
289
+ const isSwapExpired = swap.data.getExpiry().lt(timestamp);
290
+ if (isSwapExpired) {
291
+ this.swapLogger.info(swap, "processPastSwap(state=NON_PAYABLE|SAVED): swap expired, cancelling, address: " + swap.address);
292
+ yield this.removeSwapData(swap, ToBtcSwapAbs_1.ToBtcSwapState.CANCELED);
293
+ return;
294
+ }
295
+ }
296
+ //Sanity check for sent swaps
297
+ if (swap.state === ToBtcSwapAbs_1.ToBtcSwapState.BTC_SENT) {
298
+ const isCommited = yield swapContract.isCommited(swap.data);
299
+ if (!isCommited) {
300
+ const status = yield swapContract.getCommitStatus(signer.getAddress(), swap.data);
301
+ if (status === base_1.SwapCommitStatus.PAID) {
302
+ this.swapLogger.info(swap, "processPastSwap(state=BTC_SENT): swap claimed (detected from processPastSwap), address: " + swap.address);
303
+ this.unsubscribePayment(swap);
304
+ yield this.removeSwapData(swap, ToBtcSwapAbs_1.ToBtcSwapState.CLAIMED);
305
+ }
306
+ else if (status === base_1.SwapCommitStatus.EXPIRED) {
307
+ this.swapLogger.warn(swap, "processPastSwap(state=BTC_SENT): swap expired, but bitcoin was probably already sent, txId: " + swap.txId + " address: " + swap.address);
308
+ this.unsubscribePayment(swap);
309
+ yield this.removeSwapData(swap, ToBtcSwapAbs_1.ToBtcSwapState.REFUNDED);
310
+ }
311
+ return;
312
+ }
313
+ }
314
+ if (swap.state === ToBtcSwapAbs_1.ToBtcSwapState.COMMITED || swap.state === ToBtcSwapAbs_1.ToBtcSwapState.BTC_SENDING || swap.state === ToBtcSwapAbs_1.ToBtcSwapState.BTC_SENT) {
315
+ yield this.processInitialized(swap);
316
+ return;
317
+ }
318
+ });
319
+ }
320
+ /**
321
+ * Checks past swaps, deletes ones that are already expired.
322
+ */
323
+ processPastSwaps() {
324
+ return __awaiter(this, void 0, void 0, function* () {
325
+ const queriedData = yield this.storageManager.query([
326
+ {
327
+ key: "state",
328
+ values: [
329
+ ToBtcSwapAbs_1.ToBtcSwapState.SAVED,
330
+ ToBtcSwapAbs_1.ToBtcSwapState.NON_PAYABLE,
331
+ ToBtcSwapAbs_1.ToBtcSwapState.COMMITED,
332
+ ToBtcSwapAbs_1.ToBtcSwapState.BTC_SENDING,
333
+ ToBtcSwapAbs_1.ToBtcSwapState.BTC_SENT,
334
+ ]
335
+ }
336
+ ]);
337
+ for (let swap of queriedData) {
338
+ yield this.processPastSwap(swap);
339
+ }
340
+ });
341
+ }
342
+ processBtcTx(swap, tx) {
343
+ return __awaiter(this, void 0, void 0, function* () {
344
+ tx.confirmations = tx.confirmations || 0;
345
+ //Check transaction has enough confirmations
346
+ const hasEnoughConfirmations = tx.confirmations >= swap.data.getConfirmations();
347
+ if (!hasEnoughConfirmations) {
348
+ return false;
349
+ }
350
+ this.swapLogger.debug(swap, "processBtcTx(): address: " + swap.address + " amount: " + swap.amount.toString(10) + " btcTx: " + tx);
351
+ //Search for required transaction output (vout)
352
+ const outputScript = bitcoin.address.toOutputScript(swap.address, this.config.bitcoinNetwork);
353
+ const vout = tx.outs.find(e => new BN(e.value).eq(swap.amount) && Buffer.from(e.scriptPubKey.hex, "hex").equals(outputScript));
354
+ if (vout == null) {
355
+ this.swapLogger.warn(swap, "processBtcTx(): cannot find correct vout," +
356
+ " required output script: " + outputScript.toString("hex") +
357
+ " required amount: " + swap.amount.toString(10) +
358
+ " vouts: ", tx.outs);
359
+ return false;
360
+ }
361
+ if (swap.metadata != null)
362
+ swap.metadata.times.payTxConfirmed = Date.now();
363
+ const success = yield this.tryClaimSwap(tx, swap, vout.n);
364
+ return success;
365
+ });
366
+ }
367
+ /**
368
+ * Checks active sent out bitcoin transactions
369
+ */
370
+ processBtcTxs() {
371
+ return __awaiter(this, void 0, void 0, function* () {
372
+ const unsubscribeSwaps = [];
373
+ for (let txId in this.activeSubscriptions) {
374
+ const swap = this.activeSubscriptions[txId];
375
+ //TODO: RBF the transaction if it's already taking too long to confirm
376
+ try {
377
+ let tx = yield this.bitcoinRpc.getTransaction(txId);
378
+ if (tx == null)
379
+ continue;
380
+ if (yield this.processBtcTx(swap, tx)) {
381
+ this.swapLogger.info(swap, "processBtcTxs(): swap claimed successfully, txId: " + tx.txid + " address: " + swap.address);
382
+ unsubscribeSwaps.push(swap);
383
+ }
384
+ }
385
+ catch (e) {
386
+ this.swapLogger.error(swap, "processBtcTxs(): error processing btc transaction", e);
387
+ }
388
+ }
389
+ unsubscribeSwaps.forEach(swap => {
390
+ this.unsubscribePayment(swap);
391
+ });
392
+ });
393
+ }
394
+ /**
395
+ * Subscribes to and periodically checks txId used to send out funds for the swap for enough confirmations
396
+ *
397
+ * @param payment
398
+ */
399
+ subscribeToPayment(payment) {
400
+ this.swapLogger.info(payment, "subscribeToPayment(): subscribing to swap, txId: " + payment.txId + " address: " + payment.address);
401
+ this.activeSubscriptions[payment.txId] = payment;
402
+ }
403
+ unsubscribePayment(payment) {
404
+ if (payment.txId != null) {
405
+ if (this.activeSubscriptions[payment.txId] != null) {
406
+ this.swapLogger.info(payment, "unsubscribePayment(): unsubscribing swap, txId: " + payment.txId + " address: " + payment.address);
407
+ delete this.activeSubscriptions[payment.txId];
408
+ }
409
+ }
410
+ }
411
+ /**
412
+ * Checks if expiry time on the swap leaves us enough room to send a transaction and for the transaction to confirm
413
+ *
414
+ * @param swap
415
+ * @private
416
+ * @throws DefinedRuntimeError will throw an error in case there isn't enough time for us to send a BTC payout tx
417
+ */
418
+ checkExpiresTooSoon(swap) {
419
+ const currentTimestamp = new BN(Math.floor(Date.now() / 1000));
420
+ const tsDelta = swap.data.getExpiry().sub(currentTimestamp);
421
+ const minRequiredCLTV = this.getExpiryFromCLTV(swap.preferedConfirmationTarget, swap.data.getConfirmations());
422
+ const hasRequiredCLTVDelta = tsDelta.gte(minRequiredCLTV);
423
+ if (!hasRequiredCLTVDelta)
424
+ throw {
425
+ code: 90001,
426
+ msg: "TS delta too low",
427
+ data: {
428
+ required: minRequiredCLTV.toString(10),
429
+ actual: tsDelta.toString(10)
430
+ }
431
+ };
432
+ }
433
+ /**
434
+ * Checks if the actual fee for the swap is no higher than the quoted estimate
435
+ *
436
+ * @param quotedSatsPerVbyte
437
+ * @param actualSatsPerVbyte
438
+ * @private
439
+ * @throws DefinedRuntimeError will throw an error in case the actual fee is higher than quoted fee
440
+ */
441
+ checkCalculatedTxFee(quotedSatsPerVbyte, actualSatsPerVbyte) {
442
+ const swapPaysEnoughNetworkFee = quotedSatsPerVbyte.gte(actualSatsPerVbyte);
443
+ if (!swapPaysEnoughNetworkFee)
444
+ throw {
445
+ code: 90003,
446
+ msg: "Fee changed too much!",
447
+ data: {
448
+ quotedFee: actualSatsPerVbyte.toString(10),
449
+ actualFee: quotedSatsPerVbyte.toString(10)
450
+ }
451
+ };
452
+ }
453
+ /**
454
+ * Runs sanity check on the calculated fee for the transaction
455
+ *
456
+ * @param psbt
457
+ * @param tx
458
+ * @param maxAllowedSatsPerVbyte
459
+ * @param actualSatsPerVbyte
460
+ * @private
461
+ * @throws {Error} Will throw an error if the fee sanity check doesn't pass
462
+ */
463
+ checkPsbtFee(psbt, tx, maxAllowedSatsPerVbyte, actualSatsPerVbyte) {
464
+ const txFee = new BN(psbt.getFee());
465
+ //Sanity check on sats/vB
466
+ const maxAllowedFee = new BN(tx.virtualSize())
467
+ //Considering the extra output was not added, because was detrminetal
468
+ .add(new BN(utils_1.utils.outputBytes({ type: this.LND_CHANGE_OUTPUT_TYPE })))
469
+ //Multiply by maximum allowed feerate
470
+ .mul(maxAllowedSatsPerVbyte)
471
+ //Possibility that extra output was not added due to it being lower than dust
472
+ .add(new BN(utils_1.utils.dustThreshold({ type: this.LND_CHANGE_OUTPUT_TYPE })));
473
+ if (txFee.gt(maxAllowedFee))
474
+ throw new Error("Generated tx fee too high: " + JSON.stringify({
475
+ maxAllowedFee: maxAllowedFee.toString(10),
476
+ actualFee: txFee.toString(10),
477
+ psbtHex: psbt.toHex(),
478
+ maxAllowedSatsPerVbyte: maxAllowedSatsPerVbyte.toString(10),
479
+ actualSatsPerVbyte: actualSatsPerVbyte.toString(10)
480
+ }));
481
+ return txFee;
482
+ }
483
+ /**
484
+ * Create PSBT for swap payout from coinselection result
485
+ *
486
+ * @param address
487
+ * @param amount
488
+ * @param escrowNonce
489
+ * @param coinselectResult
490
+ * @private
491
+ */
492
+ getPsbt(address, amount, escrowNonce, coinselectResult) {
493
+ return __awaiter(this, void 0, void 0, function* () {
494
+ let psbt = new bitcoin.Psbt();
495
+ //Apply nonce
496
+ const nonceBuffer = Buffer.from(escrowNonce.toArray("be", 8));
497
+ const locktimeBN = new BN(nonceBuffer.slice(0, 5), "be");
498
+ let locktime = locktimeBN.toNumber() + 500000000;
499
+ psbt.setLocktime(locktime);
500
+ const sequenceBN = new BN(nonceBuffer.slice(5, 8), "be");
501
+ const sequence = 0xFE000000 + sequenceBN.toNumber();
502
+ psbt.addInputs(coinselectResult.inputs.map(input => {
503
+ return {
504
+ hash: input.txId,
505
+ index: input.vout,
506
+ witnessUtxo: {
507
+ script: input.outputScript,
508
+ value: input.value
509
+ },
510
+ sighashType: 0x01,
511
+ sequence
512
+ };
513
+ }));
514
+ psbt.addOutput({
515
+ script: bitcoin.address.toOutputScript(address, this.config.bitcoinNetwork),
516
+ value: amount.toNumber()
517
+ });
518
+ //Add change output
519
+ if (coinselectResult.outputs.length > 1)
520
+ psbt.addOutput({
521
+ script: bitcoin.address.toOutputScript(yield this.getChangeAddress(), this.config.bitcoinNetwork),
522
+ value: coinselectResult.outputs[1].value
523
+ });
524
+ return psbt;
525
+ });
526
+ }
527
+ /**
528
+ * Signs provided PSBT and also returns a raw signed transaction
529
+ *
530
+ * @param psbt
531
+ * @private
532
+ */
533
+ signPsbt(psbt) {
534
+ return __awaiter(this, void 0, void 0, function* () {
535
+ const signedPsbt = yield lncli.signPsbt({
536
+ lnd: this.LND,
537
+ psbt: psbt.toHex()
538
+ });
539
+ return {
540
+ psbt: bitcoin.Psbt.fromHex(signedPsbt.psbt),
541
+ rawTx: signedPsbt.transaction
542
+ };
543
+ });
544
+ }
545
+ /**
546
+ * Sends raw bitcoin transaction
547
+ *
548
+ * @param rawTx
549
+ * @private
550
+ */
551
+ sendRawTransaction(rawTx) {
552
+ return __awaiter(this, void 0, void 0, function* () {
553
+ yield lncli.broadcastChainTransaction({
554
+ lnd: this.LND,
555
+ transaction: rawTx
556
+ });
557
+ });
558
+ }
559
+ /**
560
+ * Sends a bitcoin transaction to payout BTC for a swap
561
+ *
562
+ * @param swap
563
+ * @private
564
+ * @throws DefinedRuntimeError will throw an error in case the payment cannot be initiated
565
+ */
566
+ sendBitcoinPayment(swap) {
567
+ //Make sure that bitcoin payouts are processed sequentially to avoid race conditions between multiple payouts,
568
+ // e.g. that 2 payouts share the same input and would effectively double-spend each other
569
+ return this.sendBtcQueue.enqueue(() => __awaiter(this, void 0, void 0, function* () {
570
+ //Run checks
571
+ this.checkExpiresTooSoon(swap);
572
+ if (swap.metadata != null)
573
+ swap.metadata.times.payCLTVChecked = Date.now();
574
+ const coinselectResult = yield this.getChainFee(swap.address, swap.amount.toNumber());
575
+ if (coinselectResult == null)
576
+ throw {
577
+ code: 90002,
578
+ msg: "Failed to run coinselect algorithm (not enough funds?)"
579
+ };
580
+ if (swap.metadata != null)
581
+ swap.metadata.times.payChainFee = Date.now();
582
+ this.checkCalculatedTxFee(swap.satsPerVbyte, coinselectResult.satsPerVbyte);
583
+ //Construct payment PSBT
584
+ let unsignedPsbt = yield this.getPsbt(swap.address, swap.amount, swap.data.getEscrowNonce(), coinselectResult);
585
+ this.swapLogger.debug(swap, "sendBitcoinPayment(): generated psbt: " + unsignedPsbt.toHex());
586
+ //Sign the PSBT
587
+ const { psbt, rawTx } = yield this.signPsbt(unsignedPsbt);
588
+ if (swap.metadata != null)
589
+ swap.metadata.times.paySignPSBT = Date.now();
590
+ this.swapLogger.debug(swap, "sendBitcoinPayment(): signed raw transaction: " + rawTx);
591
+ const tx = bitcoin.Transaction.fromHex(rawTx);
592
+ const txFee = this.checkPsbtFee(psbt, tx, swap.satsPerVbyte, coinselectResult.satsPerVbyte);
593
+ swap.txId = tx.getId();
594
+ swap.setRealNetworkFee(txFee);
595
+ yield swap.setState(ToBtcSwapAbs_1.ToBtcSwapState.BTC_SENDING);
596
+ yield this.storageManager.saveData(swap.getHash(), swap.getSequence(), swap);
597
+ yield this.sendRawTransaction(rawTx);
598
+ if (swap.metadata != null)
599
+ swap.metadata.times.payTxSent = Date.now();
600
+ this.swapLogger.info(swap, "sendBitcoinPayment(): btc transaction generated, signed & broadcasted, txId: " + tx.getId() + " address: " + swap.address);
601
+ //Invalidate the UTXO cache
602
+ this.cachedUtxos = null;
603
+ yield swap.setState(ToBtcSwapAbs_1.ToBtcSwapState.BTC_SENT);
604
+ yield this.storageManager.saveData(swap.getHash(), swap.getSequence(), swap);
605
+ }));
606
+ }
607
+ /**
608
+ * Called after swap was successfully committed, will check if bitcoin tx is already sent, if not tries to send it and subscribes to it
609
+ *
610
+ * @param swap
611
+ */
612
+ processInitialized(swap) {
613
+ return __awaiter(this, void 0, void 0, function* () {
614
+ if (swap.state === ToBtcSwapAbs_1.ToBtcSwapState.BTC_SENDING) {
615
+ //Bitcoin transaction was signed (maybe also sent)
616
+ const tx = yield this.bitcoinRpc.getTransaction(swap.txId);
617
+ const isTxSent = tx != null;
618
+ if (!isTxSent) {
619
+ //Reset the state to COMMITED
620
+ this.swapLogger.info(swap, "processInitialized(state=BTC_SENDING): btc transaction not found, resetting to COMMITED state, txId: " + swap.txId + " address: " + swap.address);
621
+ yield swap.setState(ToBtcSwapAbs_1.ToBtcSwapState.COMMITED);
622
+ }
623
+ else {
624
+ this.swapLogger.info(swap, "processInitialized(state=BTC_SENDING): btc transaction found, advancing to BTC_SENT state, txId: " + swap.txId + " address: " + swap.address);
625
+ yield swap.setState(ToBtcSwapAbs_1.ToBtcSwapState.BTC_SENT);
626
+ yield this.storageManager.saveData(swap.getHash(), swap.data.getSequence(), swap);
627
+ }
628
+ }
629
+ if (swap.state === ToBtcSwapAbs_1.ToBtcSwapState.SAVED) {
630
+ this.swapLogger.info(swap, "processInitialized(state=SAVED): advancing to COMMITED state, address: " + swap.address);
631
+ yield swap.setState(ToBtcSwapAbs_1.ToBtcSwapState.COMMITED);
632
+ yield this.storageManager.saveData(swap.getHash(), swap.data.getSequence(), swap);
633
+ }
634
+ if (swap.state === ToBtcSwapAbs_1.ToBtcSwapState.COMMITED) {
635
+ const unlock = swap.lock(60);
636
+ if (unlock == null)
637
+ return;
638
+ this.swapLogger.debug(swap, "processInitialized(state=COMMITED): sending bitcoin transaction, address: " + swap.address);
639
+ try {
640
+ yield this.sendBitcoinPayment(swap);
641
+ this.swapLogger.info(swap, "processInitialized(state=COMMITED): btc transaction sent, address: " + swap.address);
642
+ }
643
+ catch (e) {
644
+ if ((0, Utils_1.isDefinedRuntimeError)(e)) {
645
+ this.swapLogger.error(swap, "processInitialized(state=COMMITED): setting state to NON_PAYABLE due to send bitcoin payment error", e);
646
+ if (swap.metadata != null)
647
+ swap.metadata.payError = e;
648
+ yield swap.setState(ToBtcSwapAbs_1.ToBtcSwapState.NON_PAYABLE);
649
+ yield this.storageManager.saveData(swap.getHash(), swap.data.getSequence(), swap);
650
+ }
651
+ else {
652
+ this.swapLogger.error(swap, "processInitialized(state=COMMITED): send bitcoin payment error", e);
653
+ throw e;
654
+ }
655
+ }
656
+ unlock();
657
+ }
658
+ if (swap.state === ToBtcSwapAbs_1.ToBtcSwapState.NON_PAYABLE)
659
+ return;
660
+ this.subscribeToPayment(swap);
661
+ });
662
+ }
663
+ processInitializeEvent(chainIdentifier, event) {
664
+ var _a;
665
+ return __awaiter(this, void 0, void 0, function* () {
666
+ if (event.swapType !== base_1.ChainSwapType.CHAIN_NONCED)
667
+ return;
668
+ const paymentHash = event.paymentHash;
669
+ const swap = yield this.storageManager.getData(paymentHash, event.sequence);
670
+ if (swap == null || swap.chainIdentifier !== chainIdentifier)
671
+ return;
672
+ swap.txIds.init = (_a = event.meta) === null || _a === void 0 ? void 0 : _a.txId;
673
+ if (swap.metadata != null)
674
+ swap.metadata.times.txReceived = Date.now();
675
+ this.swapLogger.info(swap, "SC: InitializeEvent: swap initialized by the client, address: " + swap.address);
676
+ yield this.processInitialized(swap);
677
+ });
678
+ }
679
+ processClaimEvent(chainIdentifier, event) {
680
+ var _a;
681
+ return __awaiter(this, void 0, void 0, function* () {
682
+ const paymentHash = event.paymentHash;
683
+ const swap = yield this.storageManager.getData(paymentHash, event.sequence);
684
+ if (swap == null || swap.chainIdentifier !== chainIdentifier)
685
+ return;
686
+ swap.txIds.claim = (_a = event.meta) === null || _a === void 0 ? void 0 : _a.txId;
687
+ this.swapLogger.info(swap, "SC: ClaimEvent: swap successfully claimed to us, address: " + swap.address);
688
+ //Also remove transaction from active subscriptions
689
+ this.unsubscribePayment(swap);
690
+ yield this.removeSwapData(swap, ToBtcSwapAbs_1.ToBtcSwapState.CLAIMED);
691
+ });
692
+ }
693
+ processRefundEvent(chainIdentifier, event) {
694
+ var _a;
695
+ return __awaiter(this, void 0, void 0, function* () {
696
+ const paymentHash = event.paymentHash;
697
+ const swap = yield this.storageManager.getData(paymentHash, event.sequence);
698
+ if (swap == null || swap.chainIdentifier !== chainIdentifier)
699
+ return;
700
+ swap.txIds.refund = (_a = event.meta) === null || _a === void 0 ? void 0 : _a.txId;
701
+ this.swapLogger.info(swap, "SC: RefundEvent: swap successfully refunded by the user, address: " + swap.address);
702
+ //Also remove transaction from active subscriptions
703
+ this.unsubscribePayment(swap);
704
+ yield this.removeSwapData(swap, ToBtcSwapAbs_1.ToBtcSwapState.REFUNDED);
705
+ });
706
+ }
707
+ /**
708
+ * Returns required expiry delta for swap params
709
+ *
710
+ * @param confirmationTarget
711
+ * @param confirmations
712
+ */
713
+ getExpiryFromCLTV(confirmationTarget, confirmations) {
714
+ //Blocks = 10 + (confirmations + confirmationTarget)*2
715
+ //Time = 3600 + (600*blocks*2)
716
+ const cltv = this.config.minChainCltv.add(new BN(confirmations).add(new BN(confirmationTarget)).mul(this.config.sendSafetyFactor));
717
+ return this.config.gracePeriod.add(this.config.bitcoinBlocktime.mul(cltv).mul(this.config.safetyFactor));
718
+ }
719
+ /**
720
+ * Checks if the requested nonce is valid
721
+ *
722
+ * @param nonce
723
+ * @throws {DefinedRuntimeError} will throw an error if the nonce is invalid
724
+ */
725
+ checkNonceValid(nonce) {
726
+ if (nonce.isNeg() || nonce.gte(new BN(2).pow(new BN(64))))
727
+ throw {
728
+ code: 20021,
729
+ msg: "Invalid request body (nonce - cannot be parsed)"
730
+ };
731
+ const nonceBuffer = Buffer.from(nonce.toArray("be", 8));
732
+ const firstPart = new BN(nonceBuffer.slice(0, 5), "be");
733
+ const maxAllowedValue = new BN(Math.floor(Date.now() / 1000) - 600000000);
734
+ if (firstPart.gt(maxAllowedValue))
735
+ throw {
736
+ code: 20022,
737
+ msg: "Invalid request body (nonce - too high)"
738
+ };
739
+ }
740
+ /**
741
+ * Checks if confirmation target is within configured bounds
742
+ *
743
+ * @param confirmationTarget
744
+ * @throws {DefinedRuntimeError} will throw an error if the confirmationTarget is out of bounds
745
+ */
746
+ checkConfirmationTarget(confirmationTarget) {
747
+ if (confirmationTarget > this.config.maxConfTarget)
748
+ throw {
749
+ code: 20023,
750
+ msg: "Invalid request body (confirmationTarget - too high)"
751
+ };
752
+ if (confirmationTarget < this.config.minConfTarget)
753
+ throw {
754
+ code: 20024,
755
+ msg: "Invalid request body (confirmationTarget - too low)"
756
+ };
757
+ }
758
+ /**
759
+ * Checks if the required confirmations are within configured bounds
760
+ *
761
+ * @param confirmations
762
+ * @throws {DefinedRuntimeError} will throw an error if the confirmations are out of bounds
763
+ */
764
+ checkRequiredConfirmations(confirmations) {
765
+ if (confirmations > this.config.maxConfirmations)
766
+ throw {
767
+ code: 20025,
768
+ msg: "Invalid request body (confirmations - too high)"
769
+ };
770
+ if (confirmations < this.config.minConfirmations)
771
+ throw {
772
+ code: 20026,
773
+ msg: "Invalid request body (confirmations - too low)"
774
+ };
775
+ }
776
+ /**
777
+ * Checks the validity of the provided address, also checks if the resulting output script isn't too large
778
+ *
779
+ * @param address
780
+ * @throws {DefinedRuntimeError} will throw an error if the address is invalid
781
+ */
782
+ checkAddress(address) {
783
+ let parsedOutputScript;
784
+ try {
785
+ parsedOutputScript = bitcoin.address.toOutputScript(address, this.config.bitcoinNetwork);
786
+ }
787
+ catch (e) {
788
+ throw {
789
+ code: 20031,
790
+ msg: "Invalid request body (address - cannot be parsed)"
791
+ };
792
+ }
793
+ if (parsedOutputScript.length > OUTPUT_SCRIPT_MAX_LENGTH)
794
+ throw {
795
+ code: 20032,
796
+ msg: "Invalid request body (address's output script - too long)"
797
+ };
798
+ }
799
+ /**
800
+ * Checks if the swap is expired, taking into consideration on-chain time skew
801
+ *
802
+ * @param swap
803
+ * @throws {DefinedRuntimeError} will throw an error if the swap is expired
804
+ */
805
+ checkExpired(swap) {
806
+ const isExpired = swap.data.getExpiry().lt(new BN(Math.floor(Date.now() / 1000)).sub(new BN(this.config.maxSkew)));
807
+ if (isExpired)
808
+ throw {
809
+ _httpStatus: 200,
810
+ code: 20010,
811
+ msg: "Payment expired"
812
+ };
813
+ }
814
+ /**
815
+ * Checks & returns the network fee needed for a transaction
816
+ *
817
+ * @param address
818
+ * @param amount
819
+ * @throws {DefinedRuntimeError} will throw an error if there are not enough BTC funds
820
+ */
821
+ checkAndGetNetworkFee(address, amount) {
822
+ return __awaiter(this, void 0, void 0, function* () {
823
+ let chainFeeResp = yield this.getChainFee(address, amount.toNumber(), true, this.config.networkFeeMultiplierPPM);
824
+ const hasEnoughFunds = chainFeeResp != null;
825
+ if (!hasEnoughFunds)
826
+ throw {
827
+ code: 20002,
828
+ msg: "Not enough liquidity"
829
+ };
830
+ return chainFeeResp;
831
+ });
832
+ }
833
+ startRestServer(restServer) {
834
+ restServer.use(this.path + "/payInvoice", (0, ServerParamDecoder_1.serverParamDecoder)(10 * 1000));
835
+ restServer.post(this.path + "/payInvoice", (0, Utils_1.expressHandlerWrapper)((req, res) => __awaiter(this, void 0, void 0, function* () {
836
+ var _a;
837
+ const metadata = { request: {}, times: {} };
838
+ const chainIdentifier = (_a = req.query.chain) !== null && _a !== void 0 ? _a : this.chains.default;
839
+ const { swapContract, signer } = this.getChain(chainIdentifier);
840
+ metadata.times.requestReceived = Date.now();
841
+ /**
842
+ *Sent initially:
843
+ * address: string Bitcoin destination address
844
+ * amount: string Amount to send (in satoshis)
845
+ * confirmationTarget: number Desired confirmation target for the swap, how big of a fee should be assigned to TX
846
+ * confirmations: number Required number of confirmations for us to claim the swap
847
+ * nonce: string Nonce for the swap (used for replay protection)
848
+ * token: string Desired token to use
849
+ * offerer: string Address of the caller
850
+ * exactIn: boolean Whether the swap should be an exact in instead of exact out swap
851
+ *
852
+ *Sent later:
853
+ * feeRate: string Fee rate to use for the init signature
854
+ */
855
+ const parsedBody = yield req.paramReader.getParams({
856
+ address: SchemaVerifier_1.FieldTypeEnum.String,
857
+ amount: SchemaVerifier_1.FieldTypeEnum.BN,
858
+ confirmationTarget: SchemaVerifier_1.FieldTypeEnum.Number,
859
+ confirmations: SchemaVerifier_1.FieldTypeEnum.Number,
860
+ nonce: SchemaVerifier_1.FieldTypeEnum.BN,
861
+ token: (val) => val != null &&
862
+ typeof (val) === "string" &&
863
+ this.isTokenSupported(chainIdentifier, val) ? val : null,
864
+ offerer: (val) => val != null &&
865
+ typeof (val) === "string" &&
866
+ swapContract.isValidAddress(val) ? val : null,
867
+ exactIn: SchemaVerifier_1.FieldTypeEnum.BooleanOptional
868
+ });
869
+ if (parsedBody == null)
870
+ throw {
871
+ code: 20100,
872
+ msg: "Invalid request body"
873
+ };
874
+ metadata.request = parsedBody;
875
+ const requestedAmount = { input: !!parsedBody.exactIn, amount: parsedBody.amount };
876
+ const request = {
877
+ chainIdentifier,
878
+ raw: req,
879
+ parsed: parsedBody,
880
+ metadata
881
+ };
882
+ const useToken = parsedBody.token;
883
+ const responseStream = res.responseStream;
884
+ this.checkNonceValid(parsedBody.nonce);
885
+ this.checkConfirmationTarget(parsedBody.confirmationTarget);
886
+ this.checkRequiredConfirmations(parsedBody.confirmations);
887
+ this.checkAddress(parsedBody.address);
888
+ yield this.checkVaultInitialized(chainIdentifier, parsedBody.token);
889
+ const fees = yield this.preCheckAmounts(request, requestedAmount, useToken);
890
+ metadata.times.requestChecked = Date.now();
891
+ //Initialize abort controller for the parallel async operations
892
+ const abortController = this.getAbortController(responseStream);
893
+ const { pricePrefetchPromise, signDataPrefetchPromise } = this.getToBtcPrefetches(chainIdentifier, useToken, responseStream, abortController);
894
+ const { amountBD, networkFeeData, totalInToken, swapFee, swapFeeInToken, networkFeeInToken } = yield this.checkToBtcAmount(request, requestedAmount, fees, useToken, (amount) => __awaiter(this, void 0, void 0, function* () {
895
+ metadata.times.amountsChecked = Date.now();
896
+ const resp = yield this.checkAndGetNetworkFee(parsedBody.address, amount);
897
+ metadata.times.chainFeeCalculated = Date.now();
898
+ return resp;
899
+ }), abortController.signal, pricePrefetchPromise);
900
+ metadata.times.priceCalculated = Date.now();
901
+ const paymentHash = this.getHash(chainIdentifier, parsedBody.address, parsedBody.nonce, amountBD, this.config.bitcoinNetwork).toString("hex");
902
+ //Add grace period another time, so the user has 1 hour to commit
903
+ const expirySeconds = this.getExpiryFromCLTV(parsedBody.confirmationTarget, parsedBody.confirmations).add(new BN(this.config.gracePeriod));
904
+ const currentTimestamp = new BN(Math.floor(Date.now() / 1000));
905
+ const minRequiredExpiry = currentTimestamp.add(expirySeconds);
906
+ const sequence = new BN((0, crypto_1.randomBytes)(8));
907
+ const payObject = yield swapContract.createSwapData(base_1.ChainSwapType.CHAIN_NONCED, parsedBody.offerer, signer.getAddress(), useToken, totalInToken, paymentHash, sequence, minRequiredExpiry, parsedBody.nonce, parsedBody.confirmations, true, false, new BN(0), new BN(0));
908
+ abortController.signal.throwIfAborted();
909
+ metadata.times.swapCreated = Date.now();
910
+ const sigData = yield this.getToBtcSignatureData(chainIdentifier, payObject, req, abortController.signal, signDataPrefetchPromise);
911
+ metadata.times.swapSigned = Date.now();
912
+ const createdSwap = new ToBtcSwapAbs_1.ToBtcSwapAbs(chainIdentifier, parsedBody.address, amountBD, swapFee, swapFeeInToken, networkFeeData.networkFee, networkFeeInToken, networkFeeData.satsPerVbyte, parsedBody.nonce, parsedBody.confirmationTarget, new BN(sigData.timeout));
913
+ createdSwap.data = payObject;
914
+ createdSwap.metadata = metadata;
915
+ yield PluginManager_1.PluginManager.swapCreate(createdSwap);
916
+ yield this.storageManager.saveData(paymentHash, sequence, createdSwap);
917
+ this.swapLogger.info(createdSwap, "REST: /payInvoice: created swap address: " + createdSwap.address + " amount: " + amountBD.toString(10));
918
+ yield responseStream.writeParamsAndEnd({
919
+ code: 20000,
920
+ msg: "Success",
921
+ data: {
922
+ amount: amountBD.toString(10),
923
+ address: signer.getAddress(),
924
+ satsPervByte: networkFeeData.satsPerVbyte.toString(10),
925
+ networkFee: networkFeeInToken.toString(10),
926
+ swapFee: swapFeeInToken.toString(10),
927
+ totalFee: swapFeeInToken.add(networkFeeInToken).toString(10),
928
+ total: totalInToken.toString(10),
929
+ minRequiredExpiry: minRequiredExpiry.toString(10),
930
+ data: payObject.serialize(),
931
+ prefix: sigData.prefix,
932
+ timeout: sigData.timeout,
933
+ signature: sigData.signature
934
+ }
935
+ });
936
+ })));
937
+ const getRefundAuthorization = (0, Utils_1.expressHandlerWrapper)((req, res) => __awaiter(this, void 0, void 0, function* () {
938
+ /**
939
+ * paymentHash: string Payment hash identifier of the swap
940
+ * sequence: BN Sequence identifier of the swap
941
+ */
942
+ const parsedBody = (0, SchemaVerifier_1.verifySchema)(Object.assign(Object.assign({}, req.body), req.query), {
943
+ paymentHash: (val) => val != null &&
944
+ typeof (val) === "string" &&
945
+ val.length === 64 &&
946
+ Utils_1.HEX_REGEX.test(val) ? val : null,
947
+ sequence: SchemaVerifier_1.FieldTypeEnum.BN
948
+ });
949
+ if (parsedBody == null)
950
+ throw {
951
+ code: 20100,
952
+ msg: "Invalid request body/query (paymentHash/sequence)"
953
+ };
954
+ this.checkSequence(parsedBody.sequence);
955
+ const payment = yield this.storageManager.getData(parsedBody.paymentHash, parsedBody.sequence);
956
+ if (payment == null || payment.state === ToBtcSwapAbs_1.ToBtcSwapState.SAVED)
957
+ throw {
958
+ _httpStatus: 200,
959
+ code: 20007,
960
+ msg: "Payment not found"
961
+ };
962
+ const { swapContract, signer } = this.getChain(payment.chainIdentifier);
963
+ this.checkExpired(payment);
964
+ if (payment.state === ToBtcSwapAbs_1.ToBtcSwapState.COMMITED)
965
+ throw {
966
+ _httpStatus: 200,
967
+ code: 20008,
968
+ msg: "Payment processing"
969
+ };
970
+ if (payment.state === ToBtcSwapAbs_1.ToBtcSwapState.BTC_SENT || payment.state === ToBtcSwapAbs_1.ToBtcSwapState.BTC_SENDING)
971
+ throw {
972
+ _httpStatus: 200,
973
+ code: 20006,
974
+ msg: "Already paid",
975
+ data: {
976
+ txId: payment.txId
977
+ }
978
+ };
979
+ if (payment.state === ToBtcSwapAbs_1.ToBtcSwapState.NON_PAYABLE) {
980
+ const isCommited = yield swapContract.isCommited(payment.data);
981
+ if (!isCommited)
982
+ throw {
983
+ code: 20005,
984
+ msg: "Not committed"
985
+ };
986
+ const refundResponse = yield swapContract.getRefundSignature(signer, payment.data, this.config.authorizationTimeout);
987
+ //Double check the state after promise result
988
+ if (payment.state !== ToBtcSwapAbs_1.ToBtcSwapState.NON_PAYABLE)
989
+ throw {
990
+ code: 20005,
991
+ msg: "Not committed"
992
+ };
993
+ this.swapLogger.info(payment, "REST: /getRefundAuthorization: returning refund authorization, because swap is in NON_PAYABLE state, address: " + payment.address);
994
+ res.status(200).json({
995
+ code: 20000,
996
+ msg: "Success",
997
+ data: {
998
+ address: signer.getAddress(),
999
+ prefix: refundResponse.prefix,
1000
+ timeout: refundResponse.timeout,
1001
+ signature: refundResponse.signature
1002
+ }
1003
+ });
1004
+ return;
1005
+ }
1006
+ throw {
1007
+ _httpStatus: 500,
1008
+ code: 20009,
1009
+ msg: "Invalid payment status"
1010
+ };
1011
+ }));
1012
+ restServer.post(this.path + "/getRefundAuthorization", getRefundAuthorization);
1013
+ restServer.get(this.path + "/getRefundAuthorization", getRefundAuthorization);
1014
+ this.logger.info("started at path: ", this.path);
1015
+ }
1016
+ /**
1017
+ * Starts watchdog checking sent bitcoin transactions
1018
+ */
1019
+ startTxTimer() {
1020
+ return __awaiter(this, void 0, void 0, function* () {
1021
+ let rerun;
1022
+ rerun = () => __awaiter(this, void 0, void 0, function* () {
1023
+ yield this.processBtcTxs().catch(e => this.logger.error("startTxTimer(): call to processBtcTxs() errored", e));
1024
+ setTimeout(rerun, this.config.txCheckInterval);
1025
+ });
1026
+ yield rerun();
1027
+ });
1028
+ }
1029
+ startWatchdog() {
1030
+ const _super = Object.create(null, {
1031
+ startWatchdog: { get: () => super.startWatchdog }
1032
+ });
1033
+ return __awaiter(this, void 0, void 0, function* () {
1034
+ yield _super.startWatchdog.call(this);
1035
+ yield this.startTxTimer();
1036
+ });
1037
+ }
1038
+ init() {
1039
+ return __awaiter(this, void 0, void 0, function* () {
1040
+ yield this.storageManager.loadData(ToBtcSwapAbs_1.ToBtcSwapAbs);
1041
+ this.subscribeToEvents();
1042
+ yield PluginManager_1.PluginManager.serviceInitialize(this);
1043
+ });
1044
+ }
1045
+ getInfoData() {
1046
+ return {
1047
+ minCltv: this.config.minChainCltv.toNumber(),
1048
+ minConfirmations: this.config.minConfirmations,
1049
+ maxConfirmations: this.config.maxConfirmations,
1050
+ minConfTarget: this.config.minConfTarget,
1051
+ maxConfTarget: this.config.maxConfTarget,
1052
+ maxOutputScriptLen: OUTPUT_SCRIPT_MAX_LENGTH
1053
+ };
1054
+ }
1055
+ }
1056
+ exports.ToBtcAbs = ToBtcAbs;