@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,1169 @@
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.ToBtcLnAbs = void 0;
13
+ const BN = require("bn.js");
14
+ const bolt11 = require("@atomiqlabs/bolt11");
15
+ const lncli = require("ln-service");
16
+ const ToBtcLnSwapAbs_1 = require("./ToBtcLnSwapAbs");
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 crypto_1 = require("crypto");
22
+ const ServerParamDecoder_1 = require("../../utils/paramcoders/server/ServerParamDecoder");
23
+ const SchemaVerifier_1 = require("../../utils/paramcoders/SchemaVerifier");
24
+ const ToBtcBaseSwapHandler_1 = require("../ToBtcBaseSwapHandler");
25
+ const SNOWFLAKE_LIST = new Set([
26
+ "038f8f113c580048d847d6949371726653e02b928196bad310e3eda39ff61723f6"
27
+ ]);
28
+ function routesMatch(routesA, routesB) {
29
+ if (routesA === routesB)
30
+ return true;
31
+ if (routesA == null || routesB == null) {
32
+ return false;
33
+ }
34
+ if (routesA.length !== routesB.length)
35
+ return false;
36
+ for (let i = 0; i < routesA.length; i++) {
37
+ if (routesA[i] === routesB[i])
38
+ continue;
39
+ if (routesA[i] == null || routesB[i] == null) {
40
+ return false;
41
+ }
42
+ if (routesA[i].length !== routesB[i].length)
43
+ return false;
44
+ for (let e = 0; e < routesA[i].length; e++) {
45
+ if (routesA[i][e] === routesB[i][e])
46
+ continue;
47
+ if (routesA[i][e] == null || routesB[i][e] == null) {
48
+ return false;
49
+ }
50
+ if (routesA[i][e].public_key !== routesB[i][e].public_key ||
51
+ routesA[i][e].base_fee_mtokens !== routesB[i][e].base_fee_mtokens ||
52
+ routesA[i][e].channel !== routesB[i][e].channel ||
53
+ routesA[i][e].cltv_delta !== routesB[i][e].cltv_delta ||
54
+ routesA[i][e].fee_rate !== routesB[i][e].fee_rate) {
55
+ return false;
56
+ }
57
+ }
58
+ }
59
+ return true;
60
+ }
61
+ /**
62
+ * Swap handler handling to BTCLN swaps using submarine swaps
63
+ */
64
+ class ToBtcLnAbs extends ToBtcBaseSwapHandler_1.ToBtcBaseSwapHandler {
65
+ constructor(storageDirectory, path, chainData, lnd, swapPricing, config) {
66
+ super(storageDirectory, path, chainData, lnd, swapPricing);
67
+ this.LIGHTNING_LIQUIDITY_CACHE_TIMEOUT = 5 * 1000;
68
+ this.activeSubscriptions = new Set();
69
+ this.type = SwapHandler_1.SwapHandlerType.TO_BTCLN;
70
+ this.exactInAuths = {};
71
+ const anyConfig = config;
72
+ anyConfig.minTsSendCltv = config.gracePeriod.add(config.bitcoinBlocktime.mul(config.minSendCltv).mul(config.safetyFactor));
73
+ this.config = anyConfig;
74
+ this.config.minLnRoutingFeePPM = this.config.minLnRoutingFeePPM || new BN(1000);
75
+ this.config.minLnBaseFee = this.config.minLnBaseFee || new BN(5);
76
+ this.config.exactInExpiry = this.config.exactInExpiry || 10 * 1000;
77
+ }
78
+ /**
79
+ * Fetches the payment info, returns null if payment not found
80
+ *
81
+ * @param paymentHash
82
+ * @private
83
+ */
84
+ getPayment(paymentHash) {
85
+ return __awaiter(this, void 0, void 0, function* () {
86
+ try {
87
+ return yield lncli.getPayment({
88
+ id: paymentHash,
89
+ lnd: this.LND
90
+ });
91
+ }
92
+ catch (e) {
93
+ if (Array.isArray(e) && e[0] === 404 && e[1] === "SentPaymentNotFound")
94
+ return null;
95
+ throw e;
96
+ }
97
+ });
98
+ }
99
+ /**
100
+ * Cleans up exactIn authorization that are already past their expiry
101
+ *
102
+ * @protected
103
+ */
104
+ cleanExpiredExactInAuthorizations() {
105
+ for (let key in this.exactInAuths) {
106
+ const obj = this.exactInAuths[key];
107
+ if (obj.expiry < Date.now()) {
108
+ this.logger.info("cleanExpiredExactInAuthorizations(): remove expired authorization, reqId: " + key);
109
+ delete this.exactInAuths[key];
110
+ }
111
+ }
112
+ }
113
+ processPastSwap(swap) {
114
+ return __awaiter(this, void 0, void 0, function* () {
115
+ //Current timestamp plus maximum allowed on-chain time skew
116
+ const timestamp = new BN(Math.floor(Date.now() / 1000)).sub(new BN(this.config.maxSkew));
117
+ if (swap.state === ToBtcLnSwapAbs_1.ToBtcLnSwapState.SAVED) {
118
+ //Cancel the swaps where signature is expired
119
+ const isSignatureExpired = swap.signatureExpiry != null && swap.signatureExpiry.lt(timestamp);
120
+ if (isSignatureExpired) {
121
+ this.swapLogger.info(swap, "processPastSwap(state=SAVED): signature expired, cancel uncommited swap, invoice: " + swap.pr);
122
+ yield this.removeSwapData(swap, ToBtcLnSwapAbs_1.ToBtcLnSwapState.CANCELED);
123
+ return;
124
+ }
125
+ //Cancel the swaps where lightning invoice is expired
126
+ const decodedPR = bolt11.decode(swap.pr);
127
+ const isInvoiceExpired = decodedPR.timeExpireDate < Date.now() / 1000;
128
+ if (isInvoiceExpired) {
129
+ this.swapLogger.info(swap, "processPastSwap(state=SAVED): invoice expired, cancel uncommited swap, invoice: " + swap.pr);
130
+ yield this.removeSwapData(swap, ToBtcLnSwapAbs_1.ToBtcLnSwapState.CANCELED);
131
+ return;
132
+ }
133
+ }
134
+ if (swap.state === ToBtcLnSwapAbs_1.ToBtcLnSwapState.COMMITED || swap.state === ToBtcLnSwapAbs_1.ToBtcLnSwapState.PAID) {
135
+ //Process swaps in commited & paid state
136
+ yield this.processInitialized(swap);
137
+ }
138
+ if (swap.state === ToBtcLnSwapAbs_1.ToBtcLnSwapState.NON_PAYABLE) {
139
+ //Remove expired swaps (as these can already be unilaterally refunded by the client), so we don't need
140
+ // to be able to cooperatively refund them
141
+ const isSwapExpired = swap.data.getExpiry().lt(timestamp);
142
+ if (isSwapExpired) {
143
+ this.swapLogger.info(swap, "processPastSwap(state=NON_PAYABLE): swap expired, removing swap data, invoice: " + swap.pr);
144
+ yield this.removeSwapData(swap);
145
+ }
146
+ }
147
+ });
148
+ }
149
+ /**
150
+ * Checks past swaps, deletes ones that are already expired, and tries to process ones that are committed.
151
+ */
152
+ processPastSwaps() {
153
+ return __awaiter(this, void 0, void 0, function* () {
154
+ this.cleanExpiredExactInAuthorizations();
155
+ const queriedData = yield this.storageManager.query([
156
+ {
157
+ key: "state",
158
+ value: [
159
+ ToBtcLnSwapAbs_1.ToBtcLnSwapState.SAVED,
160
+ ToBtcLnSwapAbs_1.ToBtcLnSwapState.COMMITED,
161
+ ToBtcLnSwapAbs_1.ToBtcLnSwapState.PAID,
162
+ ToBtcLnSwapAbs_1.ToBtcLnSwapState.NON_PAYABLE
163
+ ]
164
+ }
165
+ ]);
166
+ for (let swap of queriedData) {
167
+ yield this.processPastSwap(swap);
168
+ }
169
+ });
170
+ }
171
+ /**
172
+ * Tries to claim the swap funds on the SC side, returns false if the swap is already locked (claim tx is already being sent)
173
+ *
174
+ * @param swap
175
+ * @private
176
+ * @returns Whether the transaction was successfully sent
177
+ */
178
+ tryClaimSwap(swap) {
179
+ return __awaiter(this, void 0, void 0, function* () {
180
+ if (swap.secret == null)
181
+ throw new Error("Invalid swap state, needs payment pre-image!");
182
+ const { swapContract, signer } = this.getChain(swap.chainIdentifier);
183
+ //Set flag that we are sending the transaction already, so we don't end up with race condition
184
+ const unlock = swap.lock(swapContract.claimWithSecretTimeout);
185
+ if (unlock == null)
186
+ return false;
187
+ try {
188
+ this.swapLogger.debug(swap, "tryClaimSwap(): initiate claim of swap, secret: " + swap.secret);
189
+ const success = yield swapContract.claimWithSecret(signer, swap.data, swap.secret, false, false, {
190
+ waitForConfirmation: true
191
+ });
192
+ this.swapLogger.info(swap, "tryClaimSwap(): swap claimed successfully, secret: " + swap.secret + " invoice: " + swap.pr);
193
+ if (swap.metadata != null)
194
+ swap.metadata.times.txClaimed = Date.now();
195
+ unlock();
196
+ return true;
197
+ }
198
+ catch (e) {
199
+ this.swapLogger.error(swap, "tryClaimSwap(): error occurred claiming swap, secret: " + swap.secret + " invoice: " + swap.pr, e);
200
+ return false;
201
+ }
202
+ });
203
+ }
204
+ /**
205
+ * Process the result of attempted lightning network payment
206
+ *
207
+ * @param swap
208
+ * @param lnPaymentStatus
209
+ */
210
+ processPaymentResult(swap, lnPaymentStatus) {
211
+ return __awaiter(this, void 0, void 0, function* () {
212
+ if (lnPaymentStatus.is_pending) {
213
+ return;
214
+ }
215
+ if (lnPaymentStatus.is_failed) {
216
+ this.swapLogger.info(swap, "processPaymentResult(): invoice payment failed, cancelling swap, invoice: " + swap.pr);
217
+ yield swap.setState(ToBtcLnSwapAbs_1.ToBtcLnSwapState.NON_PAYABLE);
218
+ yield this.storageManager.saveData(swap.data.getHash(), swap.data.getSequence(), swap);
219
+ return;
220
+ }
221
+ const { swapContract, signer } = this.getChain(swap.chainIdentifier);
222
+ if (lnPaymentStatus.is_confirmed) {
223
+ //Save pre-image & real network fee
224
+ swap.secret = lnPaymentStatus.payment.secret;
225
+ swap.setRealNetworkFee(new BN(lnPaymentStatus.payment.fee_mtokens).div(new BN(1000)));
226
+ this.swapLogger.info(swap, "processPaymentResult(): invoice paid, secret: " + swap.secret + " realRoutingFee: " + swap.realNetworkFee.toString(10) + " invoice: " + swap.pr);
227
+ yield swap.setState(ToBtcLnSwapAbs_1.ToBtcLnSwapState.PAID);
228
+ yield this.storageManager.saveData(swap.data.getHash(), swap.data.getSequence(), swap);
229
+ //Check if escrow state exists
230
+ const isCommited = yield swapContract.isCommited(swap.data);
231
+ if (!isCommited) {
232
+ const status = yield swapContract.getCommitStatus(signer.getAddress(), swap.data);
233
+ if (status === base_1.SwapCommitStatus.PAID) {
234
+ //This is alright, we got the money
235
+ yield this.removeSwapData(swap, ToBtcLnSwapAbs_1.ToBtcLnSwapState.CLAIMED);
236
+ return;
237
+ }
238
+ else if (status === base_1.SwapCommitStatus.EXPIRED) {
239
+ //This means the user was able to refund before we were able to claim, no good
240
+ yield this.removeSwapData(swap, ToBtcLnSwapAbs_1.ToBtcLnSwapState.REFUNDED);
241
+ }
242
+ this.swapLogger.warn(swap, "processPaymentResult(): tried to claim but escrow doesn't exist anymore," +
243
+ " status: " + status +
244
+ " invoice: " + swap.pr);
245
+ return;
246
+ }
247
+ const success = yield this.tryClaimSwap(swap);
248
+ if (success)
249
+ this.swapLogger.info(swap, "processPaymentResult(): swap claimed successfully, invoice: " + swap.pr);
250
+ return;
251
+ }
252
+ //This should never happen
253
+ throw new Error("Invalid lnPaymentStatus");
254
+ });
255
+ }
256
+ /**
257
+ * Subscribe to a pending lightning network payment attempt
258
+ *
259
+ * @param invoiceData
260
+ */
261
+ subscribeToPayment(invoiceData) {
262
+ const paymentHash = invoiceData.data.getHash();
263
+ if (this.activeSubscriptions.has(paymentHash))
264
+ return false;
265
+ const subscription = lncli.subscribeToPastPayment({ id: paymentHash, lnd: this.LND });
266
+ const onResult = (lnPaymentStatus) => {
267
+ const outcome = lnPaymentStatus.is_confirmed ? "success" : lnPaymentStatus.is_failed ? "failure" : null;
268
+ this.swapLogger.info(invoiceData, "subscribeToPayment(): result callback, outcome: " + outcome + " invoice: " + invoiceData.pr);
269
+ this.processPaymentResult(invoiceData, lnPaymentStatus).catch(e => this.swapLogger.error(invoiceData, "subscribeToPayment(): process payment result", e));
270
+ subscription.removeAllListeners();
271
+ this.activeSubscriptions.delete(paymentHash);
272
+ };
273
+ subscription.on('confirmed', (payment) => onResult({
274
+ is_confirmed: true,
275
+ payment
276
+ }));
277
+ subscription.on('failed', (data) => onResult({
278
+ is_failed: true,
279
+ error: data
280
+ }));
281
+ this.swapLogger.info(invoiceData, "subscribeToPayment(): subscribe to payment outcome, invoice: " + invoiceData.pr);
282
+ this.activeSubscriptions.add(paymentHash);
283
+ return true;
284
+ }
285
+ sendLightningPayment(swap) {
286
+ return __awaiter(this, void 0, void 0, function* () {
287
+ const decodedPR = bolt11.decode(swap.pr);
288
+ const expiryTimestamp = swap.data.getExpiry();
289
+ const currentTimestamp = new BN(Math.floor(Date.now() / 1000));
290
+ //Run checks
291
+ const hasEnoughTimeToPay = expiryTimestamp.sub(currentTimestamp).gte(this.config.minTsSendCltv);
292
+ if (!hasEnoughTimeToPay)
293
+ throw {
294
+ code: 90005,
295
+ msg: "Not enough time to reliably pay the invoice"
296
+ };
297
+ const isInvoiceExpired = decodedPR.timeExpireDate < Date.now() / 1000;
298
+ if (isInvoiceExpired)
299
+ throw {
300
+ code: 90006,
301
+ msg: "Invoice already expired"
302
+ };
303
+ //Compute max cltv delta
304
+ const maxFee = swap.quotedNetworkFee;
305
+ const maxUsableCLTVdelta = expiryTimestamp.sub(currentTimestamp).sub(this.config.gracePeriod).div(this.config.bitcoinBlocktime.mul(this.config.safetyFactor));
306
+ yield swap.setState(ToBtcLnSwapAbs_1.ToBtcLnSwapState.COMMITED);
307
+ yield this.storageManager.saveData(decodedPR.tagsObject.payment_hash, swap.data.getSequence(), swap);
308
+ //Initiate payment
309
+ const { current_block_height } = yield lncli.getHeight({ lnd: this.LND });
310
+ const obj = {
311
+ request: swap.pr,
312
+ max_fee: maxFee.toString(10),
313
+ max_timeout_height: new BN(current_block_height).add(maxUsableCLTVdelta).toString(10),
314
+ lnd: this.LND
315
+ };
316
+ this.swapLogger.info(swap, "sendLightningPayment(): paying lightning network invoice," +
317
+ " cltvDelta: " + maxUsableCLTVdelta.toString(10) +
318
+ " maxFee: " + maxFee.toString(10) +
319
+ " invoice: " + swap.pr);
320
+ try {
321
+ yield lncli.pay(obj);
322
+ }
323
+ catch (e) {
324
+ throw {
325
+ code: 90007,
326
+ msg: "Failed to initiate invoice payment",
327
+ data: {
328
+ error: JSON.stringify(e)
329
+ }
330
+ };
331
+ }
332
+ if (swap.metadata != null)
333
+ swap.metadata.times.payComplete = Date.now();
334
+ });
335
+ }
336
+ /**
337
+ * Begins a lightning network payment attempt, if not attempted already
338
+ *
339
+ * @param swap
340
+ */
341
+ processInitialized(swap) {
342
+ return __awaiter(this, void 0, void 0, function* () {
343
+ //Check if payment was already made
344
+ let lnPaymentStatus = yield this.getPayment(swap.getHash());
345
+ if (swap.metadata != null)
346
+ swap.metadata.times.payPaymentChecked = Date.now();
347
+ const paymentExists = lnPaymentStatus != null;
348
+ if (!paymentExists) {
349
+ try {
350
+ yield this.sendLightningPayment(swap);
351
+ }
352
+ catch (e) {
353
+ this.swapLogger.error(swap, "processInitialized(): lightning payment error", e);
354
+ if ((0, Utils_1.isDefinedRuntimeError)(e)) {
355
+ if (swap.metadata != null)
356
+ swap.metadata.payError = e;
357
+ yield swap.setState(ToBtcLnSwapAbs_1.ToBtcLnSwapState.NON_PAYABLE);
358
+ yield this.storageManager.saveData(swap.data.getHash(), swap.data.getSequence(), swap);
359
+ return;
360
+ }
361
+ else
362
+ throw e;
363
+ }
364
+ this.subscribeToPayment(swap);
365
+ return;
366
+ }
367
+ if (lnPaymentStatus.is_pending) {
368
+ this.subscribeToPayment(swap);
369
+ return;
370
+ }
371
+ //Payment has already concluded, process the result
372
+ yield this.processPaymentResult(swap, lnPaymentStatus);
373
+ });
374
+ }
375
+ processInitializeEvent(chainIdentifier, event) {
376
+ var _a;
377
+ return __awaiter(this, void 0, void 0, function* () {
378
+ if (event.swapType !== base_1.ChainSwapType.HTLC)
379
+ return;
380
+ const paymentHash = event.paymentHash;
381
+ const swap = yield this.storageManager.getData(paymentHash, event.sequence);
382
+ if (swap == null || swap.chainIdentifier !== chainIdentifier)
383
+ return;
384
+ swap.txIds.init = (_a = event.meta) === null || _a === void 0 ? void 0 : _a.txId;
385
+ if (swap.metadata != null)
386
+ swap.metadata.times.txReceived = Date.now();
387
+ this.swapLogger.info(swap, "SC: InitializeEvent: swap initialized by the client, invoice: " + swap.pr);
388
+ //Only process swaps in SAVED state
389
+ if (swap.state !== ToBtcLnSwapAbs_1.ToBtcLnSwapState.SAVED)
390
+ return;
391
+ yield this.processInitialized(swap);
392
+ });
393
+ }
394
+ processClaimEvent(chainIdentifier, event) {
395
+ var _a;
396
+ return __awaiter(this, void 0, void 0, function* () {
397
+ const paymentHash = event.paymentHash;
398
+ const swap = yield this.storageManager.getData(paymentHash, event.sequence);
399
+ if (swap == null || swap.chainIdentifier !== chainIdentifier)
400
+ return;
401
+ swap.txIds.claim = (_a = event.meta) === null || _a === void 0 ? void 0 : _a.txId;
402
+ this.swapLogger.info(swap, "SC: ClaimEvent: swap claimed to us, secret: " + event.secret + " invoice: " + swap.pr);
403
+ yield this.removeSwapData(swap, ToBtcLnSwapAbs_1.ToBtcLnSwapState.CLAIMED);
404
+ });
405
+ }
406
+ processRefundEvent(chainIdentifier, event) {
407
+ var _a;
408
+ return __awaiter(this, void 0, void 0, function* () {
409
+ const paymentHash = event.paymentHash;
410
+ const swap = yield this.storageManager.getData(paymentHash, event.sequence);
411
+ if (swap == null || swap.chainIdentifier !== chainIdentifier)
412
+ return;
413
+ swap.txIds.refund = (_a = event.meta) === null || _a === void 0 ? void 0 : _a.txId;
414
+ this.swapLogger.info(swap, "SC: RefundEvent: swap refunded back to the client, invoice: " + swap.pr);
415
+ yield this.removeSwapData(swap, ToBtcLnSwapAbs_1.ToBtcLnSwapState.REFUNDED);
416
+ });
417
+ }
418
+ /**
419
+ * Checks if the amount was supplied in the exactIn request
420
+ *
421
+ * @param amount
422
+ * @param exactIn
423
+ * @throws {DefinedRuntimeError} will throw an error if the swap was exactIn, but amount not specified
424
+ */
425
+ checkAmount(amount, exactIn) {
426
+ if (exactIn) {
427
+ if (amount == null) {
428
+ throw {
429
+ code: 20040,
430
+ msg: "Invalid request body (amount not specified)!"
431
+ };
432
+ }
433
+ }
434
+ }
435
+ /**
436
+ * Checks if the maxFee parameter is in valid range (>0)
437
+ *
438
+ * @param maxFee
439
+ * @throws {DefinedRuntimeError} will throw an error if the maxFee is zero or negative
440
+ */
441
+ checkMaxFee(maxFee) {
442
+ if (maxFee.isNeg() || maxFee.isZero()) {
443
+ throw {
444
+ code: 20030,
445
+ msg: "Invalid request body (maxFee too low)!"
446
+ };
447
+ }
448
+ }
449
+ /**
450
+ * Checks and parses a payment request (bolt11 invoice), additionally also checks expiration time of the invoice
451
+ *
452
+ * @param pr
453
+ * @throws {DefinedRuntimeError} will throw an error if the pr is invalid, without amount or expired
454
+ */
455
+ checkPaymentRequest(pr) {
456
+ let parsedPR;
457
+ try {
458
+ parsedPR = bolt11.decode(pr);
459
+ }
460
+ catch (e) {
461
+ throw {
462
+ code: 20021,
463
+ msg: "Invalid request body (pr - cannot be parsed)"
464
+ };
465
+ }
466
+ if (parsedPR.millisatoshis == null)
467
+ throw {
468
+ code: 20022,
469
+ msg: "Invalid request body (pr - needs to have amount)"
470
+ };
471
+ let halfConfidence = false;
472
+ if (parsedPR.timeExpireDate < ((Date.now() / 1000) + (this.config.authorizationTimeout + (2 * 60)))) {
473
+ if (!this.config.allowShortExpiry) {
474
+ throw {
475
+ code: 20020,
476
+ msg: "Invalid request body (pr - expired)"
477
+ };
478
+ }
479
+ else if (parsedPR.timeExpireDate < Date.now() / 1000) {
480
+ throw {
481
+ code: 20020,
482
+ msg: "Invalid request body (pr - expired)"
483
+ };
484
+ }
485
+ halfConfidence = true;
486
+ }
487
+ return { parsedPR, halfConfidence };
488
+ }
489
+ /**
490
+ * Checks if the request specified too short of an expiry
491
+ *
492
+ * @param expiryTimestamp
493
+ * @param currentTimestamp
494
+ * @throws {DefinedRuntimeError} will throw an error if the expiry time is too short
495
+ */
496
+ checkExpiry(expiryTimestamp, currentTimestamp) {
497
+ const expiresTooSoon = expiryTimestamp.sub(currentTimestamp).lt(this.config.minTsSendCltv);
498
+ if (expiresTooSoon) {
499
+ throw {
500
+ code: 20001,
501
+ msg: "Expiry time too low!"
502
+ };
503
+ }
504
+ }
505
+ /**
506
+ * Checks if the prior payment with the same paymentHash exists
507
+ *
508
+ * @param paymentHash
509
+ * @param abortSignal
510
+ * @throws {DefinedRuntimeError} will throw an error if payment already exists
511
+ */
512
+ checkPriorPayment(paymentHash, abortSignal) {
513
+ return __awaiter(this, void 0, void 0, function* () {
514
+ const payment = yield this.getPayment(paymentHash);
515
+ if (payment != null)
516
+ throw {
517
+ code: 20010,
518
+ msg: "Already processed"
519
+ };
520
+ abortSignal.throwIfAborted();
521
+ });
522
+ }
523
+ /**
524
+ * Checks if the underlying LND backend has enough liquidity in channels to honor the swap
525
+ *
526
+ * @param amount
527
+ * @param abortSignal
528
+ * @param useCached Whether to use cached liquidity values
529
+ * @throws {DefinedRuntimeError} will throw an error if there isn't enough liquidity
530
+ */
531
+ checkLiquidity(amount, abortSignal, useCached = false) {
532
+ return __awaiter(this, void 0, void 0, function* () {
533
+ const amountBDMtokens = amount.mul(new BN(1000));
534
+ if (!useCached || this.lightningLiquidityCache == null || this.lightningLiquidityCache.timestamp < Date.now() - this.LIGHTNING_LIQUIDITY_CACHE_TIMEOUT) {
535
+ const channelBalances = yield lncli.getChannelBalance({ lnd: this.LND });
536
+ this.lightningLiquidityCache = {
537
+ liquidityMTokens: new BN(channelBalances.channel_balance_mtokens),
538
+ timestamp: Date.now()
539
+ };
540
+ }
541
+ if (amountBDMtokens.gt(this.lightningLiquidityCache.liquidityMTokens)) {
542
+ throw {
543
+ code: 20002,
544
+ msg: "Not enough liquidity"
545
+ };
546
+ }
547
+ abortSignal.throwIfAborted();
548
+ });
549
+ }
550
+ /**
551
+ * Computes the route paying to the specified bolt11 invoice, estimating the fee, uses bLIP-39 blinded paths
552
+ *
553
+ * @param amountSats
554
+ * @param maxFee
555
+ * @param parsedRequest
556
+ * @param maxTimeoutBlockheight
557
+ * @param metadata
558
+ * @param maxUsableCLTV
559
+ * @private
560
+ */
561
+ getRoutesInvoiceBLIP39(amountSats, maxFee, parsedRequest, maxTimeoutBlockheight, metadata, maxUsableCLTV) {
562
+ return __awaiter(this, void 0, void 0, function* () {
563
+ metadata.routeReq = [];
564
+ const routeReqs = parsedRequest.blindedPaths.map((blindedPath) => __awaiter(this, void 0, void 0, function* () {
565
+ if (new BN(blindedPath.cltv_expiry_delta + 10).gt(maxUsableCLTV))
566
+ return null;
567
+ const originalMsatAmount = amountSats.mul(new BN(1000));
568
+ const blindedFeeTotalMsat = new BN(blindedPath.fee_base_msat)
569
+ .add(originalMsatAmount.mul(new BN(blindedPath.fee_proportional_millionths)).div(new BN(1000000)));
570
+ const routeReq = {
571
+ destination: blindedPath.introduction_node,
572
+ cltv_delta: Math.max(blindedPath.cltv_expiry_delta, parsedRequest.cltv_delta),
573
+ mtokens: originalMsatAmount.add(blindedFeeTotalMsat).toString(10),
574
+ max_fee_mtokens: maxFee.mul(new BN(1000)).sub(blindedFeeTotalMsat).toString(10),
575
+ max_timeout_height: maxTimeoutBlockheight.toString(10),
576
+ // total_mtokens: amountSats.mul(new BN(1000)).toString(10),
577
+ routes: parsedRequest.routes,
578
+ is_ignoring_past_failures: true,
579
+ lnd: null
580
+ };
581
+ metadata.routeReq.push(Object.assign({}, routeReq));
582
+ routeReq.lnd = this.LND;
583
+ let resp;
584
+ try {
585
+ resp = yield lncli.getRouteToDestination(routeReq);
586
+ }
587
+ catch (e) {
588
+ (0, Utils_1.handleLndError)(e);
589
+ }
590
+ if (resp == null || resp.route == null)
591
+ return null;
592
+ const adjustedFeeMsats = new BN(resp.route.fee_mtokens).add(blindedFeeTotalMsat);
593
+ resp.route.fee_mtokens = adjustedFeeMsats.toString(10);
594
+ resp.route.fee = adjustedFeeMsats.div(new BN(1000)).toNumber();
595
+ resp.route.safe_fee = adjustedFeeMsats.add(new BN(999)).div(new BN(1000)).toNumber();
596
+ const totalAdjustedMsats = new BN(routeReq.mtokens).add(blindedFeeTotalMsat);
597
+ resp.route.mtokens = totalAdjustedMsats.toString(10);
598
+ resp.route.tokens = totalAdjustedMsats.div(new BN(1000)).toNumber();
599
+ resp.route.safe_tokens = totalAdjustedMsats.add(new BN(999)).div(new BN(1000)).toNumber();
600
+ return resp.route;
601
+ }));
602
+ const responses = yield Promise.all(routeReqs);
603
+ metadata.routeResponsesBLIP39 = responses.map(resp => { return Object.assign({}, resp); });
604
+ return responses.reduce((prev, current) => {
605
+ if (prev == null)
606
+ return current;
607
+ if (current == null)
608
+ return prev;
609
+ current.fee_mtokens = BN.max(new BN(prev.fee_mtokens), new BN(current.fee_mtokens)).toString(10);
610
+ current.fee = Math.max(prev.fee, current.fee);
611
+ current.safe_fee = Math.max(prev.safe_fee, current.safe_fee);
612
+ current.mtokens = BN.max(new BN(prev.mtokens), new BN(current.mtokens)).toString(10);
613
+ current.tokens = Math.max(prev.tokens, current.tokens);
614
+ current.safe_tokens = Math.max(prev.safe_tokens, current.safe_tokens);
615
+ current.timeout = Math.max(prev.timeout, current.timeout);
616
+ return current;
617
+ });
618
+ });
619
+ }
620
+ /**
621
+ * Computes the route paying to the specified bolt11 invoice, estimating the fee
622
+ *
623
+ * @param amountSats
624
+ * @param maxFee
625
+ * @param parsedRequest
626
+ * @param maxTimeoutBlockheight
627
+ * @param metadata
628
+ * @param maxUsableCLTV
629
+ * @private
630
+ */
631
+ getRoutesInvoice(amountSats, maxFee, parsedRequest, maxTimeoutBlockheight, metadata, maxUsableCLTV) {
632
+ return __awaiter(this, void 0, void 0, function* () {
633
+ if (parsedRequest.blindedPaths != null && parsedRequest.blindedPaths.length > 0)
634
+ return yield this.getRoutesInvoiceBLIP39(amountSats, maxFee, parsedRequest, maxTimeoutBlockheight, metadata, maxUsableCLTV);
635
+ const routesReq = {
636
+ destination: parsedRequest.destination,
637
+ cltv_delta: parsedRequest.cltv_delta,
638
+ mtokens: amountSats.mul(new BN(1000)).toString(10),
639
+ max_fee_mtokens: maxFee.mul(new BN(1000)).toString(10),
640
+ payment: parsedRequest.payment,
641
+ max_timeout_height: maxTimeoutBlockheight.toString(10),
642
+ total_mtokens: amountSats.mul(new BN(1000)).toString(10),
643
+ routes: parsedRequest.routes,
644
+ is_ignoring_past_failures: true
645
+ };
646
+ metadata.routeReq = Object.assign({}, routesReq);
647
+ routesReq.lnd = this.LND;
648
+ let obj;
649
+ try {
650
+ obj = yield lncli.getRouteToDestination(routesReq);
651
+ }
652
+ catch (e) {
653
+ (0, Utils_1.handleLndError)(e);
654
+ }
655
+ return (obj === null || obj === void 0 ? void 0 : obj.route) == null ? null : obj.route;
656
+ });
657
+ }
658
+ /**
659
+ * Sends a probe payment to the specified bolt11 invoice to check if it is reachable
660
+ *
661
+ * @param amountSats
662
+ * @param maxFee
663
+ * @param parsedRequest
664
+ * @param maxTimeoutBlockheight
665
+ * @param metadata
666
+ * @private
667
+ */
668
+ probeInvoice(amountSats, maxFee, parsedRequest, maxTimeoutBlockheight, metadata) {
669
+ return __awaiter(this, void 0, void 0, function* () {
670
+ const probeReq = {
671
+ destination: parsedRequest.destination,
672
+ cltv_delta: parsedRequest.cltv_delta,
673
+ mtokens: amountSats.mul(new BN(1000)).toString(10),
674
+ max_fee_mtokens: maxFee.mul(new BN(1000)).toString(10),
675
+ max_timeout_height: maxTimeoutBlockheight.toString(10),
676
+ payment: parsedRequest.payment,
677
+ total_mtokens: amountSats.mul(new BN(1000)).toString(10),
678
+ routes: parsedRequest.routes
679
+ };
680
+ metadata.probeRequest = Object.assign({}, probeReq);
681
+ probeReq.lnd = this.LND;
682
+ let is_snowflake = false;
683
+ if (parsedRequest.routes != null) {
684
+ for (let route of parsedRequest.routes) {
685
+ if (SNOWFLAKE_LIST.has(route[0].public_key) || SNOWFLAKE_LIST.has(route[1].public_key)) {
686
+ is_snowflake = true;
687
+ }
688
+ }
689
+ }
690
+ let obj;
691
+ if (!is_snowflake)
692
+ try {
693
+ obj = yield lncli.probeForRoute(probeReq);
694
+ }
695
+ catch (e) {
696
+ (0, Utils_1.handleLndError)(e);
697
+ }
698
+ return (obj === null || obj === void 0 ? void 0 : obj.route) == null ? null : obj.route;
699
+ });
700
+ }
701
+ /**
702
+ * Estimates the routing fee & confidence by either probing or routing (if probing fails), the fee is also adjusted
703
+ * according to routing fee multiplier, and subject to minimums set in config
704
+ *
705
+ * @param amountBD
706
+ * @param maxFee
707
+ * @param expiryTimestamp
708
+ * @param currentTimestamp
709
+ * @param pr
710
+ * @param metadata
711
+ * @param abortSignal
712
+ * @throws {DefinedRuntimeError} will throw an error if the destination is unreachable
713
+ */
714
+ checkAndGetNetworkFee(amountBD, maxFee, expiryTimestamp, currentTimestamp, pr, metadata, abortSignal) {
715
+ return __awaiter(this, void 0, void 0, function* () {
716
+ const maxUsableCLTV = expiryTimestamp.sub(currentTimestamp).sub(this.config.gracePeriod).div(this.config.bitcoinBlocktime.mul(this.config.safetyFactor));
717
+ const { current_block_height } = yield lncli.getHeight({ lnd: this.LND });
718
+ abortSignal.throwIfAborted();
719
+ metadata.times.blockheightFetched = Date.now();
720
+ const maxTimeoutBlockheight = new BN(current_block_height).add(maxUsableCLTV);
721
+ const parsedRequest = lncli.parsePaymentRequest({ request: pr });
722
+ const bolt11Parsed = bolt11.decode(pr);
723
+ if (bolt11Parsed.tagsObject.blinded_payinfo != null && bolt11Parsed.tagsObject.blinded_payinfo.length > 0) {
724
+ parsedRequest.blindedPaths = bolt11Parsed.tagsObject.blinded_payinfo;
725
+ }
726
+ let probeOrRouteResp;
727
+ if (parsedRequest.blindedPaths == null) {
728
+ probeOrRouteResp = yield this.probeInvoice(amountBD, maxFee, parsedRequest, maxTimeoutBlockheight, metadata);
729
+ metadata.times.probeResult = Date.now();
730
+ metadata.probeResponse = Object.assign({}, probeOrRouteResp);
731
+ abortSignal.throwIfAborted();
732
+ }
733
+ if (probeOrRouteResp == null) {
734
+ if (!this.config.allowProbeFailedSwaps)
735
+ throw {
736
+ code: 20002,
737
+ msg: "Cannot route the payment!"
738
+ };
739
+ const routeResp = yield this.getRoutesInvoice(amountBD, maxFee, parsedRequest, maxTimeoutBlockheight, metadata, maxUsableCLTV);
740
+ metadata.times.routingResult = Date.now();
741
+ metadata.routeResponse = Object.assign({}, routeResp);
742
+ abortSignal.throwIfAborted();
743
+ if (routeResp == null)
744
+ throw {
745
+ code: 20002,
746
+ msg: "Cannot route the payment!"
747
+ };
748
+ this.logger.info("checkAndGetNetworkFee(): routing result," +
749
+ " destination: " + parsedRequest.destination +
750
+ " confidence: " + routeResp.confidence +
751
+ " safe fee: " + routeResp.safe_fee);
752
+ probeOrRouteResp = routeResp;
753
+ if (parsedRequest.blindedPaths == null)
754
+ probeOrRouteResp.confidence = 0;
755
+ }
756
+ else {
757
+ this.logger.info("checkAndGetNetworkFee(): route probed," +
758
+ " destination: " + parsedRequest.destination +
759
+ " confidence: " + probeOrRouteResp.confidence +
760
+ " safe fee: " + probeOrRouteResp.safe_fee);
761
+ }
762
+ let actualRoutingFee = new BN(probeOrRouteResp.safe_fee).mul(this.config.routingFeeMultiplier);
763
+ const minRoutingFee = amountBD.mul(this.config.minLnRoutingFeePPM).div(new BN(1000000)).add(this.config.minLnBaseFee);
764
+ if (actualRoutingFee.lt(minRoutingFee)) {
765
+ actualRoutingFee = minRoutingFee;
766
+ if (actualRoutingFee.gt(maxFee)) {
767
+ probeOrRouteResp.confidence = 0;
768
+ }
769
+ }
770
+ if (actualRoutingFee.gt(maxFee)) {
771
+ actualRoutingFee = maxFee;
772
+ }
773
+ return {
774
+ networkFee: actualRoutingFee,
775
+ confidence: probeOrRouteResp.confidence,
776
+ routes: parsedRequest.routes
777
+ };
778
+ });
779
+ }
780
+ /**
781
+ * Checks and consumes (deletes & returns) exactIn authorizaton with a specific reqId
782
+ *
783
+ * @param reqId
784
+ * @throws {DefinedRuntimeError} will throw an error if the authorization doesn't exist
785
+ */
786
+ checkExactInAuthorization(reqId) {
787
+ const parsedAuth = this.exactInAuths[reqId];
788
+ if (parsedAuth == null) {
789
+ throw {
790
+ code: 20070,
791
+ msg: "Invalid reqId"
792
+ };
793
+ }
794
+ delete this.exactInAuths[reqId];
795
+ if (parsedAuth.expiry < Date.now()) {
796
+ throw {
797
+ code: 20200,
798
+ msg: "Authorization already expired!"
799
+ };
800
+ }
801
+ return parsedAuth;
802
+ }
803
+ /**
804
+ * Checks if the newly submitted PR has the same parameters (destination, cltv_delta, routes) as the initial dummy
805
+ * invoice sent for exactIn swap quote
806
+ *
807
+ * @param pr
808
+ * @param parsedAuth
809
+ * @throws {DefinedRuntimeError} will throw an error if the details don't match
810
+ */
811
+ checkPaymentRequestMatchesInitial(pr, parsedAuth) {
812
+ return __awaiter(this, void 0, void 0, function* () {
813
+ const parsedRequest = yield lncli.parsePaymentRequest({
814
+ request: pr
815
+ });
816
+ if (parsedRequest.destination !== parsedAuth.destination ||
817
+ parsedRequest.cltv_delta !== parsedAuth.cltvDelta ||
818
+ !new BN(parsedRequest.mtokens).eq(parsedAuth.amount.mul(new BN(1000)))) {
819
+ throw {
820
+ code: 20102,
821
+ msg: "Provided PR doesn't match initial!"
822
+ };
823
+ }
824
+ if (!routesMatch(parsedRequest.routes, parsedAuth.routes)) {
825
+ throw {
826
+ code: 20102,
827
+ msg: "Provided PR doesn't match initial (routes)!"
828
+ };
829
+ }
830
+ });
831
+ }
832
+ startRestServer(restServer) {
833
+ restServer.use(this.path + "/payInvoiceExactIn", (0, ServerParamDecoder_1.serverParamDecoder)(10 * 1000));
834
+ restServer.post(this.path + "/payInvoiceExactIn", (0, Utils_1.expressHandlerWrapper)((req, res) => __awaiter(this, void 0, void 0, function* () {
835
+ /**
836
+ * pr: string bolt11 lightning invoice
837
+ * reqId: string Identifier of the swap
838
+ * feeRate: string Fee rate to use for the init tx
839
+ */
840
+ const parsedBody = yield req.paramReader.getParams({
841
+ pr: SchemaVerifier_1.FieldTypeEnum.String,
842
+ reqId: SchemaVerifier_1.FieldTypeEnum.String,
843
+ feeRate: SchemaVerifier_1.FieldTypeEnum.String
844
+ });
845
+ if (parsedBody == null) {
846
+ throw {
847
+ code: 20100,
848
+ msg: "Invalid request body"
849
+ };
850
+ }
851
+ const responseStream = res.responseStream;
852
+ const abortSignal = responseStream.getAbortSignal();
853
+ //Check request params
854
+ const parsedAuth = this.checkExactInAuthorization(parsedBody.reqId);
855
+ const { parsedPR, halfConfidence } = this.checkPaymentRequest(parsedBody.pr);
856
+ yield this.checkPaymentRequestMatchesInitial(parsedBody.pr, parsedAuth);
857
+ const metadata = parsedAuth.metadata;
858
+ const sequence = new BN((0, crypto_1.randomBytes)(8));
859
+ const { swapContract, signer } = this.getChain(parsedAuth.chainIdentifier);
860
+ //Create swap data
861
+ const payObject = yield swapContract.createSwapData(base_1.ChainSwapType.HTLC, parsedAuth.offerer, signer.getAddress(), parsedAuth.token, parsedAuth.total, parsedPR.tagsObject.payment_hash, sequence, parsedAuth.swapExpiry, new BN(0), 0, true, false, new BN(0), new BN(0));
862
+ metadata.times.swapCreated = Date.now();
863
+ //Sign swap data
864
+ const prefetchedSignData = parsedAuth.preFetchSignData;
865
+ const sigData = yield this.getToBtcSignatureData(parsedAuth.chainIdentifier, payObject, req, abortSignal, prefetchedSignData);
866
+ metadata.times.swapSigned = Date.now();
867
+ //Create swap
868
+ const createdSwap = new ToBtcLnSwapAbs_1.ToBtcLnSwapAbs(parsedAuth.chainIdentifier, parsedBody.pr, parsedAuth.swapFee, parsedAuth.swapFeeInToken, parsedAuth.quotedNetworkFee, parsedAuth.quotedNetworkFeeInToken, new BN(sigData.timeout));
869
+ createdSwap.data = payObject;
870
+ createdSwap.metadata = metadata;
871
+ yield PluginManager_1.PluginManager.swapCreate(createdSwap);
872
+ yield this.storageManager.saveData(parsedPR.tagsObject.payment_hash, sequence, createdSwap);
873
+ this.swapLogger.info(createdSwap, "REST: /payInvoiceExactIn: created exact in swap," +
874
+ " reqId: " + parsedBody.reqId +
875
+ " amount: " + new BN(parsedPR.millisatoshis).div(new BN(1000)).toString(10) +
876
+ " invoice: " + createdSwap.pr);
877
+ yield responseStream.writeParamsAndEnd({
878
+ code: 20000,
879
+ msg: "Success",
880
+ data: {
881
+ maxFee: parsedAuth.quotedNetworkFeeInToken.toString(10),
882
+ swapFee: parsedAuth.swapFeeInToken.toString(10),
883
+ total: parsedAuth.total.toString(10),
884
+ confidence: halfConfidence ? parsedAuth.confidence / 2000000 : parsedAuth.confidence / 1000000,
885
+ address: signer.getAddress(),
886
+ routingFeeSats: parsedAuth.quotedNetworkFee.toString(10),
887
+ data: payObject.serialize(),
888
+ prefix: sigData.prefix,
889
+ timeout: sigData.timeout,
890
+ signature: sigData.signature
891
+ }
892
+ });
893
+ })));
894
+ restServer.use(this.path + "/payInvoice", (0, ServerParamDecoder_1.serverParamDecoder)(10 * 1000));
895
+ restServer.post(this.path + "/payInvoice", (0, Utils_1.expressHandlerWrapper)((req, res) => __awaiter(this, void 0, void 0, function* () {
896
+ var _a;
897
+ const metadata = { request: {}, times: {} };
898
+ const chainIdentifier = (_a = req.query.chain) !== null && _a !== void 0 ? _a : this.chains.default;
899
+ const { swapContract, signer } = this.getChain(chainIdentifier);
900
+ metadata.times.requestReceived = Date.now();
901
+ /**
902
+ *Sent initially:
903
+ * pr: string bolt11 lightning invoice
904
+ * maxFee: string maximum routing fee
905
+ * expiryTimestamp: string expiry timestamp of the to be created HTLC, determines how many LN paths can be considered
906
+ * token: string Desired token to use
907
+ * offerer: string Address of the caller
908
+ * exactIn: boolean Whether to do an exact in swap instead of exact out
909
+ * amount: string Input amount for exactIn swaps
910
+ *
911
+ *Sent later:
912
+ * feeRate: string Fee rate to use for the init signature
913
+ */
914
+ const parsedBody = yield req.paramReader.getParams({
915
+ pr: SchemaVerifier_1.FieldTypeEnum.String,
916
+ maxFee: SchemaVerifier_1.FieldTypeEnum.BN,
917
+ expiryTimestamp: SchemaVerifier_1.FieldTypeEnum.BN,
918
+ token: (val) => val != null &&
919
+ typeof (val) === "string" &&
920
+ this.isTokenSupported(chainIdentifier, val) ? val : null,
921
+ offerer: (val) => val != null &&
922
+ typeof (val) === "string" &&
923
+ swapContract.isValidAddress(val) ? val : null,
924
+ exactIn: SchemaVerifier_1.FieldTypeEnum.BooleanOptional,
925
+ amount: SchemaVerifier_1.FieldTypeEnum.BNOptional
926
+ });
927
+ if (parsedBody == null) {
928
+ throw {
929
+ code: 20100,
930
+ msg: "Invalid request body"
931
+ };
932
+ }
933
+ metadata.request = parsedBody;
934
+ const request = {
935
+ chainIdentifier,
936
+ raw: req,
937
+ parsed: parsedBody,
938
+ metadata
939
+ };
940
+ const useToken = parsedBody.token;
941
+ const responseStream = res.responseStream;
942
+ const currentTimestamp = new BN(Math.floor(Date.now() / 1000));
943
+ //Check request params
944
+ this.checkAmount(parsedBody.amount, parsedBody.exactIn);
945
+ this.checkMaxFee(parsedBody.maxFee);
946
+ this.checkExpiry(parsedBody.expiryTimestamp, currentTimestamp);
947
+ yield this.checkVaultInitialized(chainIdentifier, parsedBody.token);
948
+ const { parsedPR, halfConfidence } = this.checkPaymentRequest(parsedBody.pr);
949
+ const requestedAmount = {
950
+ input: !!parsedBody.exactIn,
951
+ amount: !!parsedBody.exactIn ? parsedBody.amount : new BN(parsedPR.millisatoshis).add(new BN(999)).div(new BN(1000))
952
+ };
953
+ const fees = yield this.preCheckAmounts(request, requestedAmount, useToken);
954
+ metadata.times.requestChecked = Date.now();
955
+ //Create abort controller for parallel pre-fetches
956
+ const abortController = this.getAbortController(responseStream);
957
+ //Pre-fetch
958
+ const { pricePrefetchPromise, signDataPrefetchPromise } = this.getToBtcPrefetches(chainIdentifier, useToken, responseStream, abortController);
959
+ //Check if prior payment has been made
960
+ yield this.checkPriorPayment(parsedPR.tagsObject.payment_hash, abortController.signal);
961
+ metadata.times.priorPaymentChecked = Date.now();
962
+ //Check amounts
963
+ const { amountBD, networkFeeData, totalInToken, swapFee, swapFeeInToken, networkFeeInToken } = yield this.checkToBtcAmount(request, requestedAmount, fees, useToken, (amountBD) => __awaiter(this, void 0, void 0, function* () {
964
+ //Check if we have enough liquidity to process the swap
965
+ yield this.checkLiquidity(amountBD, abortController.signal, true);
966
+ metadata.times.liquidityChecked = Date.now();
967
+ const maxFee = parsedBody.exactIn ?
968
+ yield this.swapPricing.getToBtcSwapAmount(parsedBody.maxFee, useToken, chainIdentifier, null, pricePrefetchPromise) :
969
+ parsedBody.maxFee;
970
+ return yield this.checkAndGetNetworkFee(amountBD, maxFee, parsedBody.expiryTimestamp, currentTimestamp, parsedBody.pr, metadata, abortController.signal);
971
+ }), abortController.signal, pricePrefetchPromise);
972
+ metadata.times.priceCalculated = Date.now();
973
+ //For exactIn swap, just save and wait for the actual invoice to be submitted
974
+ if (parsedBody.exactIn) {
975
+ const reqId = (0, crypto_1.randomBytes)(32).toString("hex");
976
+ this.exactInAuths[reqId] = {
977
+ chainIdentifier,
978
+ reqId,
979
+ expiry: Date.now() + this.config.exactInExpiry,
980
+ amount: amountBD,
981
+ destination: parsedPR.payeeNodeKey,
982
+ cltvDelta: parsedPR.tagsObject.min_final_cltv_expiry,
983
+ routes: networkFeeData.routes,
984
+ quotedNetworkFeeInToken: networkFeeInToken,
985
+ swapFeeInToken,
986
+ total: totalInToken,
987
+ confidence: networkFeeData.confidence,
988
+ quotedNetworkFee: networkFeeData.networkFee,
989
+ swapFee,
990
+ token: useToken,
991
+ swapExpiry: parsedBody.expiryTimestamp,
992
+ offerer: parsedBody.offerer,
993
+ preFetchSignData: signDataPrefetchPromise != null ? yield signDataPrefetchPromise : null,
994
+ metadata
995
+ };
996
+ this.logger.info("REST: /payInvoice: created exact in swap," +
997
+ " reqId: " + reqId +
998
+ " amount: " + amountBD.toString(10) +
999
+ " destination: " + parsedPR.payeeNodeKey);
1000
+ yield responseStream.writeParamsAndEnd({
1001
+ code: 20000,
1002
+ msg: "Success",
1003
+ data: {
1004
+ amount: amountBD.toString(10),
1005
+ reqId
1006
+ }
1007
+ });
1008
+ return;
1009
+ }
1010
+ const sequence = new BN((0, crypto_1.randomBytes)(8));
1011
+ //Create swap data
1012
+ const payObject = yield swapContract.createSwapData(base_1.ChainSwapType.HTLC, parsedBody.offerer, signer.getAddress(), useToken, totalInToken, parsedPR.tagsObject.payment_hash, sequence, parsedBody.expiryTimestamp, new BN(0), 0, true, false, new BN(0), new BN(0));
1013
+ abortController.signal.throwIfAborted();
1014
+ metadata.times.swapCreated = Date.now();
1015
+ //Sign swap data
1016
+ const sigData = yield this.getToBtcSignatureData(chainIdentifier, payObject, req, abortController.signal, signDataPrefetchPromise);
1017
+ metadata.times.swapSigned = Date.now();
1018
+ //Create swap
1019
+ const createdSwap = new ToBtcLnSwapAbs_1.ToBtcLnSwapAbs(chainIdentifier, parsedBody.pr, swapFee, swapFeeInToken, networkFeeData.networkFee, networkFeeInToken, new BN(sigData.timeout));
1020
+ createdSwap.data = payObject;
1021
+ createdSwap.metadata = metadata;
1022
+ yield PluginManager_1.PluginManager.swapCreate(createdSwap);
1023
+ yield this.storageManager.saveData(parsedPR.tagsObject.payment_hash, sequence, createdSwap);
1024
+ this.swapLogger.info(createdSwap, "REST: /payInvoice: created swap," +
1025
+ " amount: " + amountBD.toString(10) +
1026
+ " invoice: " + createdSwap.pr);
1027
+ yield responseStream.writeParamsAndEnd({
1028
+ code: 20000,
1029
+ msg: "Success",
1030
+ data: {
1031
+ maxFee: networkFeeInToken.toString(10),
1032
+ swapFee: swapFeeInToken.toString(10),
1033
+ total: totalInToken.toString(10),
1034
+ confidence: halfConfidence ? networkFeeData.confidence / 2000000 : networkFeeData.confidence / 1000000,
1035
+ address: signer.getAddress(),
1036
+ routingFeeSats: networkFeeData.networkFee.toString(10),
1037
+ data: payObject.serialize(),
1038
+ prefix: sigData.prefix,
1039
+ timeout: sigData.timeout,
1040
+ signature: sigData.signature
1041
+ }
1042
+ });
1043
+ })));
1044
+ const getRefundAuthorization = (0, Utils_1.expressHandlerWrapper)((req, res) => __awaiter(this, void 0, void 0, function* () {
1045
+ /**
1046
+ * paymentHash: string Identifier of the swap
1047
+ * sequence: BN Sequence identifier of the swap
1048
+ */
1049
+ const parsedBody = (0, SchemaVerifier_1.verifySchema)(Object.assign(Object.assign({}, req.body), req.query), {
1050
+ paymentHash: (val) => val != null &&
1051
+ typeof (val) === "string" &&
1052
+ val.length === 64 &&
1053
+ Utils_1.HEX_REGEX.test(val) ? val : null,
1054
+ sequence: SchemaVerifier_1.FieldTypeEnum.BN
1055
+ });
1056
+ if (parsedBody == null)
1057
+ throw {
1058
+ code: 20100,
1059
+ msg: "Invalid request body/query (paymentHash/sequence)"
1060
+ };
1061
+ this.checkSequence(parsedBody.sequence);
1062
+ const data = yield this.storageManager.getData(parsedBody.paymentHash, parsedBody.sequence);
1063
+ const isSwapFound = data != null;
1064
+ if (isSwapFound) {
1065
+ const isExpired = data.data.getExpiry().lt(new BN(Math.floor(Date.now() / 1000)).sub(new BN(this.config.maxSkew)));
1066
+ if (isExpired)
1067
+ throw {
1068
+ _httpStatus: 200,
1069
+ code: 20010,
1070
+ msg: "Payment expired"
1071
+ };
1072
+ const { signer, swapContract } = this.getChain(data.chainIdentifier);
1073
+ if (data.state === ToBtcLnSwapAbs_1.ToBtcLnSwapState.NON_PAYABLE) {
1074
+ const refundSigData = yield swapContract.getRefundSignature(signer, data.data, this.config.authorizationTimeout);
1075
+ //Double check the state after promise result
1076
+ if (data.state !== ToBtcLnSwapAbs_1.ToBtcLnSwapState.NON_PAYABLE)
1077
+ throw {
1078
+ code: 20005,
1079
+ msg: "Not committed"
1080
+ };
1081
+ this.swapLogger.info(data, "REST: /getRefundAuthorization: returning refund authorization, because invoice in NON_PAYABLE state, invoice: " + data.pr);
1082
+ res.status(200).json({
1083
+ code: 20000,
1084
+ msg: "Success",
1085
+ data: {
1086
+ address: signer.getAddress(),
1087
+ prefix: refundSigData.prefix,
1088
+ timeout: refundSigData.timeout,
1089
+ signature: refundSigData.signature
1090
+ }
1091
+ });
1092
+ return;
1093
+ }
1094
+ }
1095
+ const payment = yield this.getPayment(parsedBody.paymentHash);
1096
+ if (payment == null)
1097
+ throw {
1098
+ _httpStatus: 200,
1099
+ code: 20007,
1100
+ msg: "Payment not found"
1101
+ };
1102
+ if (payment.is_pending)
1103
+ throw {
1104
+ _httpStatus: 200,
1105
+ code: 20008,
1106
+ msg: "Payment in-flight"
1107
+ };
1108
+ if (payment.is_confirmed)
1109
+ throw {
1110
+ _httpStatus: 200,
1111
+ code: 20006,
1112
+ msg: "Already paid",
1113
+ data: {
1114
+ secret: payment.payment.secret
1115
+ }
1116
+ };
1117
+ if (payment.is_failed)
1118
+ throw {
1119
+ _httpStatus: 200,
1120
+ code: 20010,
1121
+ msg: "Payment expired"
1122
+ };
1123
+ // NOTE: Fixed by not removing swap data until the HTLC is either expired, claimed or refunded.
1124
+ // //TODO_old: Fix this by providing chain identifier as part of the invoice description, or maybe just do it the proper
1125
+ // // way and just keep storing the data until the HTLC expiry
1126
+ // if(payment.is_failed) {
1127
+ // //TODO_old: This might not be the best idea with EVM chains
1128
+ // const commitedData = await this.swapContract.getCommitedData(parsedBody.paymentHash);
1129
+ //
1130
+ // if(commitedData==null) throw {
1131
+ // code: 20005,
1132
+ // msg: "Not committed"
1133
+ // };
1134
+ //
1135
+ // const refundSigData = await this.swapContract.getRefundSignature(commitedData, this.config.authorizationTimeout);
1136
+ //
1137
+ // this.swapLogger.info(commitedData, "REST: /getRefundAuthorization: returning refund authorization, because invoice payment failed");
1138
+ //
1139
+ // res.status(200).json({
1140
+ // code: 20000,
1141
+ // msg: "Success",
1142
+ // data: {
1143
+ // address: this.swapContract.getAddress(),
1144
+ // prefix: refundSigData.prefix,
1145
+ // timeout: refundSigData.timeout,
1146
+ // signature: refundSigData.signature
1147
+ // }
1148
+ // });
1149
+ // }
1150
+ }));
1151
+ restServer.post(this.path + '/getRefundAuthorization', getRefundAuthorization);
1152
+ restServer.get(this.path + '/getRefundAuthorization', getRefundAuthorization);
1153
+ this.logger.info("started at path: ", this.path);
1154
+ }
1155
+ init() {
1156
+ return __awaiter(this, void 0, void 0, function* () {
1157
+ yield this.storageManager.loadData(ToBtcLnSwapAbs_1.ToBtcLnSwapAbs);
1158
+ this.subscribeToEvents();
1159
+ yield PluginManager_1.PluginManager.serviceInitialize(this);
1160
+ });
1161
+ }
1162
+ getInfoData() {
1163
+ return {
1164
+ minCltv: this.config.minSendCltv.toNumber(),
1165
+ minTimestampCltv: this.config.minTsSendCltv.toNumber()
1166
+ };
1167
+ }
1168
+ }
1169
+ exports.ToBtcLnAbs = ToBtcLnAbs;