@atomiqlabs/lp-lib 17.5.1 → 17.5.2

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 (170) hide show
  1. package/LICENSE +201 -201
  2. package/dist/fees/IBtcFeeEstimator.d.ts +3 -3
  3. package/dist/fees/IBtcFeeEstimator.js +2 -2
  4. package/dist/index.d.ts +43 -43
  5. package/dist/index.js +59 -59
  6. package/dist/info/InfoHandler.d.ts +17 -17
  7. package/dist/info/InfoHandler.js +61 -61
  8. package/dist/plugins/IPlugin.d.ts +158 -158
  9. package/dist/plugins/IPlugin.js +35 -35
  10. package/dist/plugins/PluginManager.d.ts +115 -115
  11. package/dist/plugins/PluginManager.js +304 -304
  12. package/dist/prices/BinanceSwapPrice.d.ts +29 -29
  13. package/dist/prices/BinanceSwapPrice.js +79 -79
  14. package/dist/prices/CoinGeckoSwapPrice.d.ts +33 -33
  15. package/dist/prices/CoinGeckoSwapPrice.js +51 -51
  16. package/dist/prices/ISwapPrice.d.ts +43 -43
  17. package/dist/prices/ISwapPrice.js +55 -55
  18. package/dist/prices/OKXSwapPrice.d.ts +29 -29
  19. package/dist/prices/OKXSwapPrice.js +79 -79
  20. package/dist/storage/IIntermediaryStorage.d.ts +18 -18
  21. package/dist/storage/IIntermediaryStorage.js +2 -2
  22. package/dist/storagemanager/IntermediaryStorageManager.d.ts +19 -19
  23. package/dist/storagemanager/IntermediaryStorageManager.js +111 -111
  24. package/dist/storagemanager/StorageManager.d.ts +13 -13
  25. package/dist/storagemanager/StorageManager.js +64 -64
  26. package/dist/swaps/SwapHandler.d.ts +178 -178
  27. package/dist/swaps/SwapHandler.js +225 -225
  28. package/dist/swaps/SwapHandlerSwap.d.ts +80 -80
  29. package/dist/swaps/SwapHandlerSwap.js +78 -78
  30. package/dist/swaps/assertions/AmountAssertions.d.ts +37 -37
  31. package/dist/swaps/assertions/AmountAssertions.js +83 -83
  32. package/dist/swaps/assertions/FromBtcAmountAssertions.d.ts +76 -76
  33. package/dist/swaps/assertions/FromBtcAmountAssertions.js +189 -189
  34. package/dist/swaps/assertions/LightningAssertions.d.ts +44 -44
  35. package/dist/swaps/assertions/LightningAssertions.js +86 -86
  36. package/dist/swaps/assertions/ToBtcAmountAssertions.d.ts +53 -53
  37. package/dist/swaps/assertions/ToBtcAmountAssertions.js +154 -154
  38. package/dist/swaps/escrow/EscrowHandler.d.ts +50 -50
  39. package/dist/swaps/escrow/EscrowHandler.js +151 -151
  40. package/dist/swaps/escrow/EscrowHandlerSwap.d.ts +35 -35
  41. package/dist/swaps/escrow/EscrowHandlerSwap.js +69 -69
  42. package/dist/swaps/escrow/FromBtcBaseSwap.d.ts +14 -14
  43. package/dist/swaps/escrow/FromBtcBaseSwap.js +32 -32
  44. package/dist/swaps/escrow/FromBtcBaseSwapHandler.d.ts +102 -102
  45. package/dist/swaps/escrow/FromBtcBaseSwapHandler.js +210 -210
  46. package/dist/swaps/escrow/ToBtcBaseSwap.d.ts +36 -36
  47. package/dist/swaps/escrow/ToBtcBaseSwap.js +67 -67
  48. package/dist/swaps/escrow/ToBtcBaseSwapHandler.d.ts +53 -53
  49. package/dist/swaps/escrow/ToBtcBaseSwapHandler.js +81 -81
  50. package/dist/swaps/escrow/frombtc_abstract/FromBtcAbs.d.ts +84 -84
  51. package/dist/swaps/escrow/frombtc_abstract/FromBtcAbs.js +329 -329
  52. package/dist/swaps/escrow/frombtc_abstract/FromBtcSwapAbs.d.ts +22 -22
  53. package/dist/swaps/escrow/frombtc_abstract/FromBtcSwapAbs.js +53 -53
  54. package/dist/swaps/escrow/frombtcln_abstract/FromBtcLnAbs.d.ts +115 -115
  55. package/dist/swaps/escrow/frombtcln_abstract/FromBtcLnAbs.js +719 -719
  56. package/dist/swaps/escrow/frombtcln_abstract/FromBtcLnSwapAbs.d.ts +34 -34
  57. package/dist/swaps/escrow/frombtcln_abstract/FromBtcLnSwapAbs.js +94 -94
  58. package/dist/swaps/escrow/frombtcln_autoinit/FromBtcLnAuto.d.ts +119 -119
  59. package/dist/swaps/escrow/frombtcln_autoinit/FromBtcLnAuto.js +730 -730
  60. package/dist/swaps/escrow/frombtcln_autoinit/FromBtcLnAutoSwap.d.ts +56 -56
  61. package/dist/swaps/escrow/frombtcln_autoinit/FromBtcLnAutoSwap.js +123 -123
  62. package/dist/swaps/escrow/tobtc_abstract/ToBtcAbs.d.ts +170 -170
  63. package/dist/swaps/escrow/tobtc_abstract/ToBtcAbs.js +769 -769
  64. package/dist/swaps/escrow/tobtc_abstract/ToBtcSwapAbs.d.ts +29 -29
  65. package/dist/swaps/escrow/tobtc_abstract/ToBtcSwapAbs.js +67 -67
  66. package/dist/swaps/escrow/tobtcln_abstract/ToBtcLnAbs.d.ts +178 -178
  67. package/dist/swaps/escrow/tobtcln_abstract/ToBtcLnAbs.js +912 -912
  68. package/dist/swaps/escrow/tobtcln_abstract/ToBtcLnSwapAbs.d.ts +25 -25
  69. package/dist/swaps/escrow/tobtcln_abstract/ToBtcLnSwapAbs.js +61 -61
  70. package/dist/swaps/spv_vault_swap/SpvVault.d.ts +45 -45
  71. package/dist/swaps/spv_vault_swap/SpvVault.js +148 -148
  72. package/dist/swaps/spv_vault_swap/SpvVaultSwap.d.ts +71 -71
  73. package/dist/swaps/spv_vault_swap/SpvVaultSwap.js +165 -165
  74. package/dist/swaps/spv_vault_swap/SpvVaultSwapHandler.d.ts +72 -72
  75. package/dist/swaps/spv_vault_swap/SpvVaultSwapHandler.js +710 -710
  76. package/dist/swaps/spv_vault_swap/SpvVaults.d.ts +70 -70
  77. package/dist/swaps/spv_vault_swap/SpvVaults.js +554 -554
  78. package/dist/swaps/spv_vault_swap/StickyAddress.d.ts +6 -6
  79. package/dist/swaps/spv_vault_swap/StickyAddress.js +19 -19
  80. package/dist/swaps/trusted/frombtc_trusted/FromBtcTrusted.d.ts +52 -52
  81. package/dist/swaps/trusted/frombtc_trusted/FromBtcTrusted.js +677 -677
  82. package/dist/swaps/trusted/frombtc_trusted/FromBtcTrustedSwap.d.ts +53 -53
  83. package/dist/swaps/trusted/frombtc_trusted/FromBtcTrustedSwap.js +121 -121
  84. package/dist/swaps/trusted/frombtcln_trusted/FromBtcLnTrusted.d.ts +77 -77
  85. package/dist/swaps/trusted/frombtcln_trusted/FromBtcLnTrusted.js +518 -518
  86. package/dist/swaps/trusted/frombtcln_trusted/FromBtcLnTrustedSwap.d.ts +35 -35
  87. package/dist/swaps/trusted/frombtcln_trusted/FromBtcLnTrustedSwap.js +84 -84
  88. package/dist/utils/BitcoinUtils.d.ts +4 -4
  89. package/dist/utils/BitcoinUtils.js +61 -61
  90. package/dist/utils/Utils.d.ts +32 -32
  91. package/dist/utils/Utils.js +130 -130
  92. package/dist/utils/paramcoders/IParamReader.d.ts +5 -5
  93. package/dist/utils/paramcoders/IParamReader.js +2 -2
  94. package/dist/utils/paramcoders/IParamWriter.d.ts +4 -4
  95. package/dist/utils/paramcoders/IParamWriter.js +2 -2
  96. package/dist/utils/paramcoders/LegacyParamEncoder.d.ts +10 -10
  97. package/dist/utils/paramcoders/LegacyParamEncoder.js +22 -22
  98. package/dist/utils/paramcoders/ParamDecoder.d.ts +25 -25
  99. package/dist/utils/paramcoders/ParamDecoder.js +222 -222
  100. package/dist/utils/paramcoders/ParamEncoder.d.ts +9 -9
  101. package/dist/utils/paramcoders/ParamEncoder.js +22 -22
  102. package/dist/utils/paramcoders/SchemaVerifier.d.ts +21 -21
  103. package/dist/utils/paramcoders/SchemaVerifier.js +84 -84
  104. package/dist/utils/paramcoders/server/ServerParamDecoder.d.ts +8 -8
  105. package/dist/utils/paramcoders/server/ServerParamDecoder.js +107 -107
  106. package/dist/utils/paramcoders/server/ServerParamEncoder.d.ts +11 -11
  107. package/dist/utils/paramcoders/server/ServerParamEncoder.js +65 -65
  108. package/dist/wallets/IBitcoinWallet.d.ts +155 -155
  109. package/dist/wallets/IBitcoinWallet.js +100 -100
  110. package/dist/wallets/ILightningWallet.d.ts +137 -137
  111. package/dist/wallets/ILightningWallet.js +37 -37
  112. package/dist/wallets/ISpvVaultSigner.d.ts +7 -7
  113. package/dist/wallets/ISpvVaultSigner.js +2 -2
  114. package/package.json +37 -37
  115. package/src/fees/IBtcFeeEstimator.ts +6 -6
  116. package/src/index.ts +54 -54
  117. package/src/info/InfoHandler.ts +106 -106
  118. package/src/plugins/IPlugin.ts +196 -196
  119. package/src/plugins/PluginManager.ts +389 -389
  120. package/src/prices/BinanceSwapPrice.ts +101 -101
  121. package/src/prices/CoinGeckoSwapPrice.ts +75 -75
  122. package/src/prices/ISwapPrice.ts +88 -88
  123. package/src/prices/OKXSwapPrice.ts +101 -101
  124. package/src/storage/IIntermediaryStorage.ts +19 -19
  125. package/src/storagemanager/IntermediaryStorageManager.ts +118 -118
  126. package/src/storagemanager/StorageManager.ts +78 -78
  127. package/src/swaps/SwapHandler.ts +337 -337
  128. package/src/swaps/SwapHandlerSwap.ts +143 -143
  129. package/src/swaps/assertions/AmountAssertions.ts +97 -97
  130. package/src/swaps/assertions/FromBtcAmountAssertions.ts +257 -257
  131. package/src/swaps/assertions/LightningAssertions.ts +103 -103
  132. package/src/swaps/assertions/ToBtcAmountAssertions.ts +209 -209
  133. package/src/swaps/escrow/EscrowHandler.ts +172 -172
  134. package/src/swaps/escrow/EscrowHandlerSwap.ts +86 -86
  135. package/src/swaps/escrow/FromBtcBaseSwap.ts +38 -38
  136. package/src/swaps/escrow/FromBtcBaseSwapHandler.ts +286 -286
  137. package/src/swaps/escrow/ToBtcBaseSwap.ts +85 -85
  138. package/src/swaps/escrow/ToBtcBaseSwapHandler.ts +129 -129
  139. package/src/swaps/escrow/frombtc_abstract/FromBtcAbs.ts +468 -468
  140. package/src/swaps/escrow/frombtc_abstract/FromBtcSwapAbs.ts +65 -65
  141. package/src/swaps/escrow/frombtcln_abstract/FromBtcLnAbs.ts +900 -900
  142. package/src/swaps/escrow/frombtcln_abstract/FromBtcLnSwapAbs.ts +145 -145
  143. package/src/swaps/escrow/frombtcln_autoinit/FromBtcLnAuto.ts +892 -892
  144. package/src/swaps/escrow/frombtcln_autoinit/FromBtcLnAutoSwap.ts +200 -200
  145. package/src/swaps/escrow/tobtc_abstract/ToBtcAbs.ts +947 -947
  146. package/src/swaps/escrow/tobtc_abstract/ToBtcSwapAbs.ts +112 -112
  147. package/src/swaps/escrow/tobtcln_abstract/ToBtcLnAbs.ts +1167 -1167
  148. package/src/swaps/escrow/tobtcln_abstract/ToBtcLnSwapAbs.ts +84 -84
  149. package/src/swaps/spv_vault_swap/SpvVault.ts +182 -182
  150. package/src/swaps/spv_vault_swap/SpvVaultSwap.ts +239 -239
  151. package/src/swaps/spv_vault_swap/SpvVaultSwapHandler.ts +897 -897
  152. package/src/swaps/spv_vault_swap/SpvVaults.ts +640 -640
  153. package/src/swaps/spv_vault_swap/StickyAddress.ts +20 -20
  154. package/src/swaps/trusted/frombtc_trusted/FromBtcTrusted.ts +782 -782
  155. package/src/swaps/trusted/frombtc_trusted/FromBtcTrustedSwap.ts +189 -189
  156. package/src/swaps/trusted/frombtcln_trusted/FromBtcLnTrusted.ts +622 -622
  157. package/src/swaps/trusted/frombtcln_trusted/FromBtcLnTrustedSwap.ts +125 -125
  158. package/src/utils/BitcoinUtils.ts +59 -59
  159. package/src/utils/Utils.ts +152 -152
  160. package/src/utils/paramcoders/IParamReader.ts +7 -7
  161. package/src/utils/paramcoders/IParamWriter.ts +8 -8
  162. package/src/utils/paramcoders/LegacyParamEncoder.ts +27 -27
  163. package/src/utils/paramcoders/ParamDecoder.ts +218 -218
  164. package/src/utils/paramcoders/ParamEncoder.ts +29 -29
  165. package/src/utils/paramcoders/SchemaVerifier.ts +96 -96
  166. package/src/utils/paramcoders/server/ServerParamDecoder.ts +118 -118
  167. package/src/utils/paramcoders/server/ServerParamEncoder.ts +75 -75
  168. package/src/wallets/IBitcoinWallet.ts +246 -246
  169. package/src/wallets/ILightningWallet.ts +201 -201
  170. package/src/wallets/ISpvVaultSigner.ts +10 -10
@@ -1,622 +1,622 @@
1
- import {Express, Request, Response} from "express";
2
- import {createHash, randomBytes} from "crypto";
3
- import {FromBtcLnTrustedSwap, FromBtcLnTrustedSwapState} from "./FromBtcLnTrustedSwap";
4
- import {ISwapPrice} from "../../../prices/ISwapPrice";
5
- import {MultichainData, SwapBaseConfig, SwapHandler, SwapHandlerType} from "../../SwapHandler";
6
- import {IIntermediaryStorage} from "../../../storage/IIntermediaryStorage";
7
- import {expressHandlerWrapper, getAbortController, HEX_REGEX} from "../../../utils/Utils";
8
- import {IParamReader} from "../../../utils/paramcoders/IParamReader";
9
- import {ServerParamEncoder} from "../../../utils/paramcoders/server/ServerParamEncoder";
10
- import {FieldTypeEnum, verifySchema} from "../../../utils/paramcoders/SchemaVerifier";
11
- import {PluginManager} from "../../../plugins/PluginManager";
12
- import {
13
- HodlInvoiceInit,
14
- ILightningWallet,
15
- LightningNetworkChannel,
16
- LightningNetworkInvoice
17
- } from "../../../wallets/ILightningWallet";
18
- import {FromBtcAmountAssertions} from "../../assertions/FromBtcAmountAssertions";
19
- import {LightningAssertions} from "../../assertions/LightningAssertions";
20
- import {isQuoteThrow} from "../../../plugins/IPlugin";
21
-
22
- export type SwapForGasServerConfig = SwapBaseConfig & {
23
- minCltv: bigint,
24
-
25
- invoiceTimeoutSeconds?: number
26
- }
27
-
28
- export type FromBtcLnTrustedRequestType = {
29
- address: string,
30
- amount: bigint,
31
- exactIn?: boolean,
32
- token?: string
33
- };
34
-
35
- /**
36
- * Swap handler handling from BTCLN swaps using submarine swaps
37
- */
38
- export class FromBtcLnTrusted extends SwapHandler<FromBtcLnTrustedSwap, FromBtcLnTrustedSwapState> {
39
- readonly type = SwapHandlerType.FROM_BTCLN_TRUSTED;
40
- readonly inflightSwapStates = new Set([FromBtcLnTrustedSwapState.RECEIVED, FromBtcLnTrustedSwapState.SENT, FromBtcLnTrustedSwapState.CONFIRMED]);
41
-
42
- activeSubscriptions: Map<string, AbortController> = new Map<string, AbortController>();
43
- processedTxIds: Map<string, string> = new Map<string, string>();
44
-
45
- readonly config: SwapForGasServerConfig;
46
- readonly lightning: ILightningWallet;
47
- readonly LightningAssertions: LightningAssertions;
48
- readonly AmountAssertions: FromBtcAmountAssertions;
49
-
50
- constructor(
51
- storageDirectory: IIntermediaryStorage<FromBtcLnTrustedSwap>,
52
- path: string,
53
- chains: MultichainData,
54
- lightning: ILightningWallet,
55
- swapPricing: ISwapPrice,
56
- config: SwapForGasServerConfig
57
- ) {
58
- super(storageDirectory, path, chains, swapPricing);
59
- this.lightning = lightning;
60
- this.LightningAssertions = new LightningAssertions(this.logger, lightning);
61
- this.AmountAssertions = new FromBtcAmountAssertions(config, swapPricing);
62
- this.config = config;
63
- this.config.invoiceTimeoutSeconds = this.config.invoiceTimeoutSeconds || 90;
64
- }
65
-
66
- /**
67
- * Unsubscribe from the pending lightning network invoice
68
- *
69
- * @param paymentHash
70
- * @private
71
- */
72
- private unsubscribeInvoice(paymentHash: string): boolean {
73
- const controller = this.activeSubscriptions.get(paymentHash);
74
- if(controller==null) return false;
75
- controller.abort("Unsubscribed");
76
- this.activeSubscriptions.delete(paymentHash);
77
- return true;
78
- }
79
-
80
- /**
81
- * Subscribe to a pending lightning network invoice
82
- *
83
- * @param invoiceData
84
- */
85
- private subscribeToInvoice(invoiceData: FromBtcLnTrustedSwap) {
86
- const hash = invoiceData.getIdentifierHash();
87
-
88
- //Already subscribed
89
- if(this.activeSubscriptions.has(hash)) return;
90
-
91
- const abortController = new AbortController();
92
- this.lightning.waitForInvoice(hash, abortController.signal).then(invoice => {
93
- this.swapLogger.debug(invoiceData, "subscribeToInvoice(): invoice_updated: ", invoice);
94
- if(invoice.status!=="held") return;
95
- this.htlcReceived(invoiceData, invoice).catch(e => this.swapLogger.error(invoiceData, "subscribeToInvoice(): Error calling htlcReceived(): ", e));
96
- this.activeSubscriptions.delete(hash);
97
- });
98
-
99
- this.swapLogger.debug(invoiceData, "subscribeToInvoice(): Subscribed to invoice payment");
100
- this.activeSubscriptions.set(hash, abortController);
101
- }
102
-
103
- /**
104
- *
105
- * @param swap
106
- * @protected
107
- * @returns {Promise<boolean>} Whether the invoice should be cancelled
108
- */
109
- protected async processPastSwap(swap: FromBtcLnTrustedSwap): Promise<boolean> {
110
- if(swap.state===FromBtcLnTrustedSwapState.CANCELED) return true;
111
- if(swap.state===FromBtcLnTrustedSwapState.REFUNDED) return true;
112
-
113
- const parsedPR = await this.lightning.parsePaymentRequest(swap.pr);
114
- const invoice = await this.lightning.getInvoice(parsedPR.id);
115
-
116
- switch (invoice.status) {
117
- case "held":
118
- try {
119
- await this.htlcReceived(swap, invoice);
120
- //Result is either FromBtcLnTrustedSwapState.RECEIVED or FromBtcLnTrustedSwapState.CANCELED
121
- } catch (e) {
122
- this.swapLogger.error(swap, "processPastSwap(): Error calling htlcReceived(): ", e);
123
- }
124
- return false;
125
- case "confirmed":
126
- return false;
127
- default:
128
- const isInvoiceExpired = parsedPR.expiryEpochMillis<Date.now();
129
- if(isInvoiceExpired) {
130
- await swap.setState(FromBtcLnTrustedSwapState.CANCELED);
131
- return true;
132
- }
133
- this.subscribeToInvoice(swap);
134
- return false;
135
- }
136
- }
137
-
138
- protected async cancelInvoices(swaps: FromBtcLnTrustedSwap[]) {
139
- for(let swap of swaps) {
140
- //Cancel invoices
141
- try {
142
- const paymentHash = swap.getIdentifierHash();
143
- await this.lightning.cancelHodlInvoice(paymentHash);
144
- this.unsubscribeInvoice(paymentHash);
145
- this.swapLogger.info(swap, "cancelInvoices(): invoice cancelled!");
146
- await this.removeSwapData(swap);
147
- } catch (e) {
148
- this.swapLogger.error(swap, "cancelInvoices(): cannot cancel hodl invoice id", e);
149
- }
150
- }
151
- }
152
-
153
- /**
154
- * Checks past swaps, refunds and deletes ones that are already expired.
155
- */
156
- protected async processPastSwaps(): Promise<void> {
157
- const cancelInvoices: FromBtcLnTrustedSwap[] = [];
158
-
159
- const queriedData = await this.storageManager.query([
160
- {
161
- key: "state",
162
- value: [
163
- FromBtcLnTrustedSwapState.CREATED,
164
- FromBtcLnTrustedSwapState.RECEIVED,
165
- FromBtcLnTrustedSwapState.SENT,
166
- FromBtcLnTrustedSwapState.CONFIRMED,
167
- FromBtcLnTrustedSwapState.CANCELED,
168
- FromBtcLnTrustedSwapState.REFUNDED,
169
- ]
170
- }
171
- ]);
172
-
173
- for(let {obj: swap} of queriedData) {
174
- if(await this.processPastSwap(swap)) cancelInvoices.push(swap);
175
- }
176
-
177
- await this.cancelInvoices(cancelInvoices);
178
- }
179
-
180
- private async cancelSwapAndInvoice(swap: FromBtcLnTrustedSwap): Promise<void> {
181
- if(swap.state!==FromBtcLnTrustedSwapState.RECEIVED) return;
182
- await swap.setState(FromBtcLnTrustedSwapState.CANCELED);
183
- const paymentHash = swap.getIdentifierHash();
184
- await this.lightning.cancelHodlInvoice(paymentHash);
185
- this.unsubscribeInvoice(paymentHash);
186
- await this.removeSwapData(swap);
187
- this.swapLogger.info(swap, "cancelSwapAndInvoice(): swap removed & invoice cancelled, invoice: ", swap.pr);
188
- }
189
-
190
- /**
191
- * Saves the state of received HTLC of the lightning payment
192
- *
193
- * @param invoiceData
194
- * @param invoice
195
- */
196
- private async htlcReceived(invoiceData: FromBtcLnTrustedSwap, invoice: { id: string }) {
197
-
198
- const { signer, chainInterface} = this.getChain(invoiceData.chainIdentifier);
199
-
200
- //Important to prevent race condition and issuing 2 signed init messages at the same time
201
- if(invoiceData.state===FromBtcLnTrustedSwapState.CREATED) {
202
- try {
203
- this.checkTooManyInflightSwaps();
204
- } catch (e) {
205
- await this.cancelSwapAndInvoice(invoiceData);
206
- throw e;
207
- }
208
-
209
- if(invoiceData.metadata!=null) invoiceData.metadata.times.htlcReceived = Date.now();
210
- await invoiceData.setState(FromBtcLnTrustedSwapState.RECEIVED);
211
- await this.storageManager.saveData(invoice.id, null, invoiceData);
212
- }
213
-
214
- if(invoiceData.state===FromBtcLnTrustedSwapState.RECEIVED) {
215
- const balance: Promise<bigint> = chainInterface.getBalance(signer.getAddress(), invoiceData.token);
216
- try {
217
- await this.checkBalance(invoiceData.output, balance, null);
218
- if(invoiceData.metadata!=null) invoiceData.metadata.times.htlcBalanceChecked = Date.now();
219
- } catch (e) {
220
- await this.cancelSwapAndInvoice(invoiceData);
221
- throw e;
222
- }
223
-
224
- if(invoiceData.state!==FromBtcLnTrustedSwapState.RECEIVED) return;
225
-
226
- const txns = await chainInterface.txsTransfer(signer.getAddress(), invoiceData.token, invoiceData.output, invoiceData.dstAddress);
227
-
228
- let unlock = invoiceData.lock(Infinity);
229
- if(unlock==null) return;
230
-
231
- const pluginCheckResult = await PluginManager.onHandlePreFromBtcExecute(
232
- SwapHandlerType.FROM_BTCLN_TRUSTED,
233
- invoiceData
234
- );
235
- if(isQuoteThrow(pluginCheckResult)) {
236
- await this.cancelSwapAndInvoice(invoiceData);
237
- unlock();
238
- throw {
239
- code: 29999,
240
- msg: pluginCheckResult.message
241
- };
242
- }
243
-
244
- if(invoiceData.state!==FromBtcLnTrustedSwapState.RECEIVED) {
245
- unlock();
246
- return;
247
- }
248
-
249
- const result = await chainInterface.sendAndConfirm(signer, txns, true, null, false, async (txId: string, rawTx: string) => {
250
- invoiceData.txIds = {init: txId};
251
- invoiceData.scRawTx = rawTx;
252
- if(invoiceData.state===FromBtcLnTrustedSwapState.RECEIVED) {
253
- await invoiceData.setState(FromBtcLnTrustedSwapState.SENT);
254
- await this.storageManager.saveData(invoice.id, null, invoiceData);
255
- }
256
- }).catch(e => this.swapLogger.error(invoiceData, "htlcReceived(): Error sending transfer txns", e));
257
-
258
- if(result==null) {
259
- //Cancel invoice
260
- await invoiceData.setState(FromBtcLnTrustedSwapState.REFUNDED);
261
- await this.storageManager.saveData(invoice.id, null, invoiceData);
262
- await this.lightning.cancelHodlInvoice(invoice.id);
263
- this.unsubscribeInvoice(invoice.id);
264
- await this.removeSwapData(invoiceData);
265
- this.swapLogger.info(invoiceData, "htlcReceived(): transaction sending failed, refunding lightning: ", invoiceData.pr);
266
- throw {
267
- code: 20002,
268
- msg: "Transaction sending failed"
269
- };
270
- } else {
271
- //Successfully paid
272
- await invoiceData.setState(FromBtcLnTrustedSwapState.CONFIRMED);
273
- await this.storageManager.saveData(invoice.id, null, invoiceData);
274
- }
275
-
276
- unlock();
277
- unlock = null;
278
- }
279
-
280
- if(invoiceData.state===FromBtcLnTrustedSwapState.SENT) {
281
- if(invoiceData.isLocked()) return;
282
-
283
- const txStatus = await chainInterface.getTxStatus(invoiceData.scRawTx);
284
- if(txStatus==="not_found") {
285
- //Retry
286
- invoiceData.txIds = {init: null};
287
- invoiceData.scRawTx = null;
288
- await invoiceData.setState(FromBtcLnTrustedSwapState.RECEIVED);
289
- await this.storageManager.saveData(invoice.id, null, invoiceData);
290
- }
291
- if(txStatus==="reverted") {
292
- //Cancel invoice
293
- await invoiceData.setState(FromBtcLnTrustedSwapState.REFUNDED);
294
- await this.storageManager.saveData(invoice.id, null, invoiceData);
295
- await this.lightning.cancelHodlInvoice(invoice.id);
296
- this.unsubscribeInvoice(invoice.id);
297
- await this.removeSwapData(invoiceData);
298
- this.swapLogger.info(invoiceData, "htlcReceived(): transaction reverted, refunding lightning: ", invoiceData.pr);
299
- throw {
300
- code: 20002,
301
- msg: "Transaction reverted"
302
- };
303
- }
304
- if(txStatus==="success") {
305
- //Successfully paid
306
- await invoiceData.setState(FromBtcLnTrustedSwapState.CONFIRMED);
307
- await this.storageManager.saveData(invoice.id, null, invoiceData);
308
- }
309
- }
310
-
311
- if(invoiceData.state===FromBtcLnTrustedSwapState.CONFIRMED) {
312
- await this.lightning.settleHodlInvoice(invoiceData.secret);
313
-
314
- if(invoiceData.metadata!=null) invoiceData.metadata.times.htlcSettled = Date.now();
315
-
316
- const paymentHash = invoiceData.getIdentifierHash();
317
- this.processedTxIds.set(paymentHash, invoiceData.txIds.init);
318
- await invoiceData.setState(FromBtcLnTrustedSwapState.SETTLED);
319
-
320
- this.unsubscribeInvoice(paymentHash);
321
- this.swapLogger.info(invoiceData, "htlcReceived(): invoice settled, invoice: "+invoiceData.pr+" scTxId: "+invoiceData.txIds.init);
322
- await this.removeSwapData(invoiceData);
323
- }
324
- }
325
-
326
- /**
327
- *
328
- * Checks if the lightning invoice is in HELD state (htlcs received but yet unclaimed)
329
- *
330
- * @param paymentHash
331
- * @throws {DefinedRuntimeError} Will throw if the lightning invoice is not found, or if it isn't in the HELD state
332
- * @returns the fetched lightning invoice
333
- */
334
- private async checkInvoiceStatus(paymentHash: string): Promise<LightningNetworkInvoice> {
335
- const invoice = await this.lightning.getInvoice(paymentHash);
336
-
337
- const isInvoiceFound = invoice!=null;
338
- if (!isInvoiceFound) throw {
339
- _httpStatus: 200,
340
- code: 10001,
341
- msg: "Invoice expired/canceled"
342
- }
343
-
344
- const arr = invoice.description.split("-");
345
- if(arr.length<3 || arr[1]!=="GAS") throw {
346
- _httpStatus: 200,
347
- code: 10001,
348
- msg: "Invoice expired/canceled"
349
- }
350
- const chainIdentifier = arr[0];
351
- const address = arr[2];
352
-
353
- const { chainInterface} = this.getChain(chainIdentifier);
354
- if(!chainInterface.isValidAddress(address, true)) throw {
355
- _httpStatus: 200,
356
- code: 10001,
357
- msg: "Invoice expired/canceled"
358
- };
359
-
360
- switch(invoice.status) {
361
- case "held":
362
- return invoice;
363
- case "canceled":
364
- throw {
365
- _httpStatus: 200,
366
- code: 10001,
367
- msg: "Invoice expired/canceled"
368
- };
369
- case "confirmed":
370
- throw {
371
- _httpStatus: 200,
372
- code: 10000,
373
- msg: "Invoice already paid",
374
- data: {
375
- txId: this.processedTxIds.get(paymentHash)
376
- }
377
- };
378
- case "unpaid":
379
- throw {
380
- _httpStatus: 200,
381
- code: 10010,
382
- msg: "Invoice yet unpaid"
383
- };
384
- default:
385
- throw new Error("Lightning invoice invalid state!");
386
- }
387
- }
388
-
389
- startRestServer(restServer: Express) {
390
-
391
- const createInvoice = expressHandlerWrapper(async (req: Request & {paramReader: IParamReader}, res: Response & {responseStream: ServerParamEncoder}) => {
392
- const metadata: {
393
- request: any,
394
- invoiceRequest?: any,
395
- invoiceResponse?: any,
396
- times: {[key: string]: number}
397
- } = {request: {}, times: {}};
398
-
399
- const chainIdentifier = req.query.chain as string;
400
- const {signer, chainInterface} = this.getChain(chainIdentifier);
401
-
402
- metadata.times.requestReceived = Date.now();
403
- /**
404
- * address: string solana address of the recipient
405
- * amount: string amount (in lamports/smart chain base units) of the invoice
406
- */
407
-
408
- req.query.token ??= chainInterface.getNativeCurrencyAddress();
409
-
410
- const parsedBody: FromBtcLnTrustedRequestType = verifySchema(req.query,{
411
- address: (val: string) => val!=null &&
412
- typeof(val)==="string" &&
413
- chainInterface.isValidAddress(val, true) ? val : null,
414
- token: (val: string) => val!=null &&
415
- typeof(val)==="string" &&
416
- this.isTokenSupported(chainIdentifier, val) ? val : null,
417
- amount: FieldTypeEnum.BigInt,
418
- exactIn: (val: string) => val==="true" ? true :
419
- (val==="false" || val===undefined) ? false : null
420
- });
421
- if(parsedBody==null) throw {
422
- code: 20100,
423
- msg: "Invalid request body"
424
- };
425
- metadata.request = parsedBody;
426
-
427
- const requestedAmount = {input: parsedBody.exactIn, amount: parsedBody.amount, token: parsedBody.token};
428
- const request = {
429
- chainIdentifier,
430
- raw: req,
431
- parsed: parsedBody,
432
- metadata
433
- };
434
- const useToken = parsedBody.token;
435
-
436
- this.checkTooManyInflightSwaps();
437
-
438
- //Check request params
439
- const fees = await this.AmountAssertions.preCheckFromBtcAmounts(this.type, request, requestedAmount);
440
- metadata.times.requestChecked = Date.now();
441
-
442
- //Create abortController for parallel prefetches
443
- const responseStream = res.responseStream;
444
- const abortController = getAbortController(responseStream);
445
-
446
- //Pre-fetch data
447
- const pricePrefetchPromise: Promise<bigint> = this.swapPricing.preFetchPrice(useToken, chainIdentifier).catch(e => {
448
- this.logger.error("pricePrefetchPromise(): pricePrefetch error: ", e);
449
- abortController.abort(e);
450
- return null;
451
- });
452
- const balancePrefetch: Promise<bigint> = chainInterface.getBalance(signer.getAddress(), useToken).catch(e => {
453
- this.logger.error("getBalancePrefetch(): balancePrefetch error: ", e);
454
- abortController.abort(e);
455
- return null;
456
- });
457
- const nativeBalancePrefetch: Promise<bigint> = useToken===chainInterface.getNativeCurrencyAddress() ?
458
- balancePrefetch : this.prefetchNativeBalanceIfNeeded(chainIdentifier, abortController);
459
- const channelsPrefetch: Promise<LightningNetworkChannel[]> = this.LightningAssertions.getChannelsPrefetch(abortController);
460
-
461
- await this.checkNativeBalance(chainIdentifier, nativeBalancePrefetch, abortController.signal);
462
-
463
- //Check valid amount specified (min/max)
464
- const {
465
- amountBD,
466
- swapFee,
467
- swapFeeInToken,
468
- totalInToken
469
- } = await this.AmountAssertions.checkFromBtcAmount(this.type, request, {...requestedAmount, pricePrefetch: pricePrefetchPromise}, fees, abortController.signal);
470
- metadata.times.priceCalculated = Date.now();
471
-
472
- //Check if we have enough funds to honor the request
473
- await this.checkBalance(totalInToken, balancePrefetch, abortController.signal)
474
- await this.LightningAssertions.checkInboundLiquidity(amountBD, channelsPrefetch, abortController.signal);
475
- metadata.times.balanceChecked = Date.now();
476
-
477
- const secret = randomBytes(32);
478
- const hash = createHash("sha256").update(secret).digest();
479
-
480
- const hodlInvoiceObj: HodlInvoiceInit = {
481
- description: chainIdentifier+"-GAS-"+parsedBody.address,
482
- cltvDelta: Number(this.config.minCltv) + 5,
483
- expiresAt: Date.now()+(this.config.invoiceTimeoutSeconds*1000),
484
- id: hash.toString("hex"),
485
- mtokens: amountBD * 1000n
486
- };
487
- metadata.invoiceRequest = hodlInvoiceObj;
488
-
489
- const hodlInvoice = await this.lightning.createHodlInvoice(hodlInvoiceObj);
490
- abortController.signal.throwIfAborted();
491
- metadata.times.invoiceCreated = Date.now();
492
- metadata.invoiceResponse = {...hodlInvoice};
493
-
494
- const createdSwap = new FromBtcLnTrustedSwap(
495
- chainIdentifier,
496
- hodlInvoice.request,
497
- hodlInvoice.mtokens,
498
- swapFee,
499
- swapFeeInToken,
500
- totalInToken,
501
- secret.toString("hex"),
502
- parsedBody.address,
503
- useToken
504
- );
505
- metadata.times.swapCreated = Date.now();
506
- createdSwap.metadata = metadata;
507
-
508
- await PluginManager.swapCreate(createdSwap);
509
- await this.storageManager.saveData(hash.toString("hex"), null, createdSwap);
510
- this.subscribeToInvoice(createdSwap);
511
-
512
- this.swapLogger.info(createdSwap, "REST: /createInvoice: Created swap invoice: "+hodlInvoice.request+" amount: "+amountBD.toString(10));
513
-
514
- res.status(200).json({
515
- msg: "Success",
516
- code: 10000,
517
- data: {
518
- pr: hodlInvoice.request,
519
- amountSats: amountBD.toString(10),
520
- swapFeeSats: swapFee.toString(10),
521
- swapFee: swapFeeInToken.toString(10),
522
- total: totalInToken.toString(10),
523
- intermediaryKey: signer.getAddress()
524
- }
525
- });
526
- });
527
-
528
- restServer.get(this.path+"/createInvoice", createInvoice);
529
-
530
- const getInvoiceStatus = expressHandlerWrapper(async (req, res) => {
531
- /**
532
- * paymentHash: string payment hash of the invoice
533
- */
534
- const parsedBody = verifySchema({...req.body, ...req.query}, {
535
- paymentHash: (val: string) => val!=null &&
536
- typeof(val)==="string" &&
537
- val.length===64 &&
538
- HEX_REGEX.test(val) ? val: null,
539
- });
540
-
541
- await this.checkInvoiceStatus(parsedBody.paymentHash);
542
-
543
- const invoiceData: FromBtcLnTrustedSwap = await this.storageManager.getData(parsedBody.paymentHash, null);
544
- if (invoiceData==null) throw {
545
- _httpStatus: 200,
546
- code: 10001,
547
- msg: "Invoice expired/canceled"
548
- };
549
-
550
- if (
551
- invoiceData.state === FromBtcLnTrustedSwapState.CANCELED ||
552
- invoiceData.state === FromBtcLnTrustedSwapState.REFUNDED
553
- ) throw {
554
- _httpStatus: 200,
555
- code: 10001,
556
- msg: "Invoice expired/canceled"
557
- };
558
-
559
- if (invoiceData.state === FromBtcLnTrustedSwapState.CREATED) throw {
560
- _httpStatus: 200,
561
- code: 10010,
562
- msg: "Invoice yet unpaid"
563
- };
564
-
565
- if (invoiceData.state === FromBtcLnTrustedSwapState.RECEIVED) throw {
566
- _httpStatus: 200,
567
- code: 10011,
568
- msg: "Invoice received, payment processing"
569
- };
570
-
571
- if (invoiceData.state === FromBtcLnTrustedSwapState.SENT) throw {
572
- _httpStatus: 200,
573
- code: 10012,
574
- msg: "Tx sent",
575
- data: {
576
- txId: invoiceData.txIds.init
577
- }
578
- };
579
-
580
- if (invoiceData.state === FromBtcLnTrustedSwapState.CONFIRMED) throw {
581
- _httpStatus: 200,
582
- code: 10000,
583
- msg: "Success, tx confirmed",
584
- data: {
585
- txId: invoiceData.txIds.init
586
- }
587
- };
588
-
589
- if (invoiceData.state === FromBtcLnTrustedSwapState.SETTLED) throw {
590
- _httpStatus: 200,
591
- code: 10000,
592
- msg: "Success, tx confirmed - invoice settled",
593
- data: {
594
- txId: invoiceData.txIds.init
595
- }
596
- };
597
- });
598
- restServer.get(this.path+"/getInvoiceStatus", getInvoiceStatus);
599
-
600
- this.logger.info("started at path: ", this.path);
601
- }
602
-
603
- async init() {
604
- await this.storageManager.loadData(FromBtcLnTrustedSwap);
605
- //Check if all swaps contain a valid amount
606
- for(let {obj: swap} of await this.storageManager.query([])) {
607
- if(swap.amount==null) {
608
- const parsedPR = await this.lightning.parsePaymentRequest(swap.pr);
609
- swap.amount = (parsedPR.mtokens + 999n) / 1000n;
610
- }
611
- }
612
- await PluginManager.serviceInitialize(this);
613
- }
614
-
615
- getInfoData(): any {
616
- return {
617
- minCltv: Number(this.config.minCltv)
618
- };
619
- }
620
-
621
- }
622
-
1
+ import {Express, Request, Response} from "express";
2
+ import {createHash, randomBytes} from "crypto";
3
+ import {FromBtcLnTrustedSwap, FromBtcLnTrustedSwapState} from "./FromBtcLnTrustedSwap";
4
+ import {ISwapPrice} from "../../../prices/ISwapPrice";
5
+ import {MultichainData, SwapBaseConfig, SwapHandler, SwapHandlerType} from "../../SwapHandler";
6
+ import {IIntermediaryStorage} from "../../../storage/IIntermediaryStorage";
7
+ import {expressHandlerWrapper, getAbortController, HEX_REGEX} from "../../../utils/Utils";
8
+ import {IParamReader} from "../../../utils/paramcoders/IParamReader";
9
+ import {ServerParamEncoder} from "../../../utils/paramcoders/server/ServerParamEncoder";
10
+ import {FieldTypeEnum, verifySchema} from "../../../utils/paramcoders/SchemaVerifier";
11
+ import {PluginManager} from "../../../plugins/PluginManager";
12
+ import {
13
+ HodlInvoiceInit,
14
+ ILightningWallet,
15
+ LightningNetworkChannel,
16
+ LightningNetworkInvoice
17
+ } from "../../../wallets/ILightningWallet";
18
+ import {FromBtcAmountAssertions} from "../../assertions/FromBtcAmountAssertions";
19
+ import {LightningAssertions} from "../../assertions/LightningAssertions";
20
+ import {isQuoteThrow} from "../../../plugins/IPlugin";
21
+
22
+ export type SwapForGasServerConfig = SwapBaseConfig & {
23
+ minCltv: bigint,
24
+
25
+ invoiceTimeoutSeconds?: number
26
+ }
27
+
28
+ export type FromBtcLnTrustedRequestType = {
29
+ address: string,
30
+ amount: bigint,
31
+ exactIn?: boolean,
32
+ token?: string
33
+ };
34
+
35
+ /**
36
+ * Swap handler handling from BTCLN swaps using submarine swaps
37
+ */
38
+ export class FromBtcLnTrusted extends SwapHandler<FromBtcLnTrustedSwap, FromBtcLnTrustedSwapState> {
39
+ readonly type = SwapHandlerType.FROM_BTCLN_TRUSTED;
40
+ readonly inflightSwapStates = new Set([FromBtcLnTrustedSwapState.RECEIVED, FromBtcLnTrustedSwapState.SENT, FromBtcLnTrustedSwapState.CONFIRMED]);
41
+
42
+ activeSubscriptions: Map<string, AbortController> = new Map<string, AbortController>();
43
+ processedTxIds: Map<string, string> = new Map<string, string>();
44
+
45
+ readonly config: SwapForGasServerConfig;
46
+ readonly lightning: ILightningWallet;
47
+ readonly LightningAssertions: LightningAssertions;
48
+ readonly AmountAssertions: FromBtcAmountAssertions;
49
+
50
+ constructor(
51
+ storageDirectory: IIntermediaryStorage<FromBtcLnTrustedSwap>,
52
+ path: string,
53
+ chains: MultichainData,
54
+ lightning: ILightningWallet,
55
+ swapPricing: ISwapPrice,
56
+ config: SwapForGasServerConfig
57
+ ) {
58
+ super(storageDirectory, path, chains, swapPricing);
59
+ this.lightning = lightning;
60
+ this.LightningAssertions = new LightningAssertions(this.logger, lightning);
61
+ this.AmountAssertions = new FromBtcAmountAssertions(config, swapPricing);
62
+ this.config = config;
63
+ this.config.invoiceTimeoutSeconds = this.config.invoiceTimeoutSeconds || 90;
64
+ }
65
+
66
+ /**
67
+ * Unsubscribe from the pending lightning network invoice
68
+ *
69
+ * @param paymentHash
70
+ * @private
71
+ */
72
+ private unsubscribeInvoice(paymentHash: string): boolean {
73
+ const controller = this.activeSubscriptions.get(paymentHash);
74
+ if(controller==null) return false;
75
+ controller.abort("Unsubscribed");
76
+ this.activeSubscriptions.delete(paymentHash);
77
+ return true;
78
+ }
79
+
80
+ /**
81
+ * Subscribe to a pending lightning network invoice
82
+ *
83
+ * @param invoiceData
84
+ */
85
+ private subscribeToInvoice(invoiceData: FromBtcLnTrustedSwap) {
86
+ const hash = invoiceData.getIdentifierHash();
87
+
88
+ //Already subscribed
89
+ if(this.activeSubscriptions.has(hash)) return;
90
+
91
+ const abortController = new AbortController();
92
+ this.lightning.waitForInvoice(hash, abortController.signal).then(invoice => {
93
+ this.swapLogger.debug(invoiceData, "subscribeToInvoice(): invoice_updated: ", invoice);
94
+ if(invoice.status!=="held") return;
95
+ this.htlcReceived(invoiceData, invoice).catch(e => this.swapLogger.error(invoiceData, "subscribeToInvoice(): Error calling htlcReceived(): ", e));
96
+ this.activeSubscriptions.delete(hash);
97
+ });
98
+
99
+ this.swapLogger.debug(invoiceData, "subscribeToInvoice(): Subscribed to invoice payment");
100
+ this.activeSubscriptions.set(hash, abortController);
101
+ }
102
+
103
+ /**
104
+ *
105
+ * @param swap
106
+ * @protected
107
+ * @returns {Promise<boolean>} Whether the invoice should be cancelled
108
+ */
109
+ protected async processPastSwap(swap: FromBtcLnTrustedSwap): Promise<boolean> {
110
+ if(swap.state===FromBtcLnTrustedSwapState.CANCELED) return true;
111
+ if(swap.state===FromBtcLnTrustedSwapState.REFUNDED) return true;
112
+
113
+ const parsedPR = await this.lightning.parsePaymentRequest(swap.pr);
114
+ const invoice = await this.lightning.getInvoice(parsedPR.id);
115
+
116
+ switch (invoice.status) {
117
+ case "held":
118
+ try {
119
+ await this.htlcReceived(swap, invoice);
120
+ //Result is either FromBtcLnTrustedSwapState.RECEIVED or FromBtcLnTrustedSwapState.CANCELED
121
+ } catch (e) {
122
+ this.swapLogger.error(swap, "processPastSwap(): Error calling htlcReceived(): ", e);
123
+ }
124
+ return false;
125
+ case "confirmed":
126
+ return false;
127
+ default:
128
+ const isInvoiceExpired = parsedPR.expiryEpochMillis<Date.now();
129
+ if(isInvoiceExpired) {
130
+ await swap.setState(FromBtcLnTrustedSwapState.CANCELED);
131
+ return true;
132
+ }
133
+ this.subscribeToInvoice(swap);
134
+ return false;
135
+ }
136
+ }
137
+
138
+ protected async cancelInvoices(swaps: FromBtcLnTrustedSwap[]) {
139
+ for(let swap of swaps) {
140
+ //Cancel invoices
141
+ try {
142
+ const paymentHash = swap.getIdentifierHash();
143
+ await this.lightning.cancelHodlInvoice(paymentHash);
144
+ this.unsubscribeInvoice(paymentHash);
145
+ this.swapLogger.info(swap, "cancelInvoices(): invoice cancelled!");
146
+ await this.removeSwapData(swap);
147
+ } catch (e) {
148
+ this.swapLogger.error(swap, "cancelInvoices(): cannot cancel hodl invoice id", e);
149
+ }
150
+ }
151
+ }
152
+
153
+ /**
154
+ * Checks past swaps, refunds and deletes ones that are already expired.
155
+ */
156
+ protected async processPastSwaps(): Promise<void> {
157
+ const cancelInvoices: FromBtcLnTrustedSwap[] = [];
158
+
159
+ const queriedData = await this.storageManager.query([
160
+ {
161
+ key: "state",
162
+ value: [
163
+ FromBtcLnTrustedSwapState.CREATED,
164
+ FromBtcLnTrustedSwapState.RECEIVED,
165
+ FromBtcLnTrustedSwapState.SENT,
166
+ FromBtcLnTrustedSwapState.CONFIRMED,
167
+ FromBtcLnTrustedSwapState.CANCELED,
168
+ FromBtcLnTrustedSwapState.REFUNDED,
169
+ ]
170
+ }
171
+ ]);
172
+
173
+ for(let {obj: swap} of queriedData) {
174
+ if(await this.processPastSwap(swap)) cancelInvoices.push(swap);
175
+ }
176
+
177
+ await this.cancelInvoices(cancelInvoices);
178
+ }
179
+
180
+ private async cancelSwapAndInvoice(swap: FromBtcLnTrustedSwap): Promise<void> {
181
+ if(swap.state!==FromBtcLnTrustedSwapState.RECEIVED) return;
182
+ await swap.setState(FromBtcLnTrustedSwapState.CANCELED);
183
+ const paymentHash = swap.getIdentifierHash();
184
+ await this.lightning.cancelHodlInvoice(paymentHash);
185
+ this.unsubscribeInvoice(paymentHash);
186
+ await this.removeSwapData(swap);
187
+ this.swapLogger.info(swap, "cancelSwapAndInvoice(): swap removed & invoice cancelled, invoice: ", swap.pr);
188
+ }
189
+
190
+ /**
191
+ * Saves the state of received HTLC of the lightning payment
192
+ *
193
+ * @param invoiceData
194
+ * @param invoice
195
+ */
196
+ private async htlcReceived(invoiceData: FromBtcLnTrustedSwap, invoice: { id: string }) {
197
+
198
+ const { signer, chainInterface} = this.getChain(invoiceData.chainIdentifier);
199
+
200
+ //Important to prevent race condition and issuing 2 signed init messages at the same time
201
+ if(invoiceData.state===FromBtcLnTrustedSwapState.CREATED) {
202
+ try {
203
+ this.checkTooManyInflightSwaps();
204
+ } catch (e) {
205
+ await this.cancelSwapAndInvoice(invoiceData);
206
+ throw e;
207
+ }
208
+
209
+ if(invoiceData.metadata!=null) invoiceData.metadata.times.htlcReceived = Date.now();
210
+ await invoiceData.setState(FromBtcLnTrustedSwapState.RECEIVED);
211
+ await this.storageManager.saveData(invoice.id, null, invoiceData);
212
+ }
213
+
214
+ if(invoiceData.state===FromBtcLnTrustedSwapState.RECEIVED) {
215
+ const balance: Promise<bigint> = chainInterface.getBalance(signer.getAddress(), invoiceData.token);
216
+ try {
217
+ await this.checkBalance(invoiceData.output, balance, null);
218
+ if(invoiceData.metadata!=null) invoiceData.metadata.times.htlcBalanceChecked = Date.now();
219
+ } catch (e) {
220
+ await this.cancelSwapAndInvoice(invoiceData);
221
+ throw e;
222
+ }
223
+
224
+ if(invoiceData.state!==FromBtcLnTrustedSwapState.RECEIVED) return;
225
+
226
+ const txns = await chainInterface.txsTransfer(signer.getAddress(), invoiceData.token, invoiceData.output, invoiceData.dstAddress);
227
+
228
+ let unlock = invoiceData.lock(Infinity);
229
+ if(unlock==null) return;
230
+
231
+ const pluginCheckResult = await PluginManager.onHandlePreFromBtcExecute(
232
+ SwapHandlerType.FROM_BTCLN_TRUSTED,
233
+ invoiceData
234
+ );
235
+ if(isQuoteThrow(pluginCheckResult)) {
236
+ await this.cancelSwapAndInvoice(invoiceData);
237
+ unlock();
238
+ throw {
239
+ code: 29999,
240
+ msg: pluginCheckResult.message
241
+ };
242
+ }
243
+
244
+ if(invoiceData.state!==FromBtcLnTrustedSwapState.RECEIVED) {
245
+ unlock();
246
+ return;
247
+ }
248
+
249
+ const result = await chainInterface.sendAndConfirm(signer, txns, true, null, false, async (txId: string, rawTx: string) => {
250
+ invoiceData.txIds = {init: txId};
251
+ invoiceData.scRawTx = rawTx;
252
+ if(invoiceData.state===FromBtcLnTrustedSwapState.RECEIVED) {
253
+ await invoiceData.setState(FromBtcLnTrustedSwapState.SENT);
254
+ await this.storageManager.saveData(invoice.id, null, invoiceData);
255
+ }
256
+ }).catch(e => this.swapLogger.error(invoiceData, "htlcReceived(): Error sending transfer txns", e));
257
+
258
+ if(result==null) {
259
+ //Cancel invoice
260
+ await invoiceData.setState(FromBtcLnTrustedSwapState.REFUNDED);
261
+ await this.storageManager.saveData(invoice.id, null, invoiceData);
262
+ await this.lightning.cancelHodlInvoice(invoice.id);
263
+ this.unsubscribeInvoice(invoice.id);
264
+ await this.removeSwapData(invoiceData);
265
+ this.swapLogger.info(invoiceData, "htlcReceived(): transaction sending failed, refunding lightning: ", invoiceData.pr);
266
+ throw {
267
+ code: 20002,
268
+ msg: "Transaction sending failed"
269
+ };
270
+ } else {
271
+ //Successfully paid
272
+ await invoiceData.setState(FromBtcLnTrustedSwapState.CONFIRMED);
273
+ await this.storageManager.saveData(invoice.id, null, invoiceData);
274
+ }
275
+
276
+ unlock();
277
+ unlock = null;
278
+ }
279
+
280
+ if(invoiceData.state===FromBtcLnTrustedSwapState.SENT) {
281
+ if(invoiceData.isLocked()) return;
282
+
283
+ const txStatus = await chainInterface.getTxStatus(invoiceData.scRawTx);
284
+ if(txStatus==="not_found") {
285
+ //Retry
286
+ invoiceData.txIds = {init: null};
287
+ invoiceData.scRawTx = null;
288
+ await invoiceData.setState(FromBtcLnTrustedSwapState.RECEIVED);
289
+ await this.storageManager.saveData(invoice.id, null, invoiceData);
290
+ }
291
+ if(txStatus==="reverted") {
292
+ //Cancel invoice
293
+ await invoiceData.setState(FromBtcLnTrustedSwapState.REFUNDED);
294
+ await this.storageManager.saveData(invoice.id, null, invoiceData);
295
+ await this.lightning.cancelHodlInvoice(invoice.id);
296
+ this.unsubscribeInvoice(invoice.id);
297
+ await this.removeSwapData(invoiceData);
298
+ this.swapLogger.info(invoiceData, "htlcReceived(): transaction reverted, refunding lightning: ", invoiceData.pr);
299
+ throw {
300
+ code: 20002,
301
+ msg: "Transaction reverted"
302
+ };
303
+ }
304
+ if(txStatus==="success") {
305
+ //Successfully paid
306
+ await invoiceData.setState(FromBtcLnTrustedSwapState.CONFIRMED);
307
+ await this.storageManager.saveData(invoice.id, null, invoiceData);
308
+ }
309
+ }
310
+
311
+ if(invoiceData.state===FromBtcLnTrustedSwapState.CONFIRMED) {
312
+ await this.lightning.settleHodlInvoice(invoiceData.secret);
313
+
314
+ if(invoiceData.metadata!=null) invoiceData.metadata.times.htlcSettled = Date.now();
315
+
316
+ const paymentHash = invoiceData.getIdentifierHash();
317
+ this.processedTxIds.set(paymentHash, invoiceData.txIds.init);
318
+ await invoiceData.setState(FromBtcLnTrustedSwapState.SETTLED);
319
+
320
+ this.unsubscribeInvoice(paymentHash);
321
+ this.swapLogger.info(invoiceData, "htlcReceived(): invoice settled, invoice: "+invoiceData.pr+" scTxId: "+invoiceData.txIds.init);
322
+ await this.removeSwapData(invoiceData);
323
+ }
324
+ }
325
+
326
+ /**
327
+ *
328
+ * Checks if the lightning invoice is in HELD state (htlcs received but yet unclaimed)
329
+ *
330
+ * @param paymentHash
331
+ * @throws {DefinedRuntimeError} Will throw if the lightning invoice is not found, or if it isn't in the HELD state
332
+ * @returns the fetched lightning invoice
333
+ */
334
+ private async checkInvoiceStatus(paymentHash: string): Promise<LightningNetworkInvoice> {
335
+ const invoice = await this.lightning.getInvoice(paymentHash);
336
+
337
+ const isInvoiceFound = invoice!=null;
338
+ if (!isInvoiceFound) throw {
339
+ _httpStatus: 200,
340
+ code: 10001,
341
+ msg: "Invoice expired/canceled"
342
+ }
343
+
344
+ const arr = invoice.description.split("-");
345
+ if(arr.length<3 || arr[1]!=="GAS") throw {
346
+ _httpStatus: 200,
347
+ code: 10001,
348
+ msg: "Invoice expired/canceled"
349
+ }
350
+ const chainIdentifier = arr[0];
351
+ const address = arr[2];
352
+
353
+ const { chainInterface} = this.getChain(chainIdentifier);
354
+ if(!chainInterface.isValidAddress(address, true)) throw {
355
+ _httpStatus: 200,
356
+ code: 10001,
357
+ msg: "Invoice expired/canceled"
358
+ };
359
+
360
+ switch(invoice.status) {
361
+ case "held":
362
+ return invoice;
363
+ case "canceled":
364
+ throw {
365
+ _httpStatus: 200,
366
+ code: 10001,
367
+ msg: "Invoice expired/canceled"
368
+ };
369
+ case "confirmed":
370
+ throw {
371
+ _httpStatus: 200,
372
+ code: 10000,
373
+ msg: "Invoice already paid",
374
+ data: {
375
+ txId: this.processedTxIds.get(paymentHash)
376
+ }
377
+ };
378
+ case "unpaid":
379
+ throw {
380
+ _httpStatus: 200,
381
+ code: 10010,
382
+ msg: "Invoice yet unpaid"
383
+ };
384
+ default:
385
+ throw new Error("Lightning invoice invalid state!");
386
+ }
387
+ }
388
+
389
+ startRestServer(restServer: Express) {
390
+
391
+ const createInvoice = expressHandlerWrapper(async (req: Request & {paramReader: IParamReader}, res: Response & {responseStream: ServerParamEncoder}) => {
392
+ const metadata: {
393
+ request: any,
394
+ invoiceRequest?: any,
395
+ invoiceResponse?: any,
396
+ times: {[key: string]: number}
397
+ } = {request: {}, times: {}};
398
+
399
+ const chainIdentifier = req.query.chain as string;
400
+ const {signer, chainInterface} = this.getChain(chainIdentifier);
401
+
402
+ metadata.times.requestReceived = Date.now();
403
+ /**
404
+ * address: string solana address of the recipient
405
+ * amount: string amount (in lamports/smart chain base units) of the invoice
406
+ */
407
+
408
+ req.query.token ??= chainInterface.getNativeCurrencyAddress();
409
+
410
+ const parsedBody: FromBtcLnTrustedRequestType = verifySchema(req.query,{
411
+ address: (val: string) => val!=null &&
412
+ typeof(val)==="string" &&
413
+ chainInterface.isValidAddress(val, true) ? val : null,
414
+ token: (val: string) => val!=null &&
415
+ typeof(val)==="string" &&
416
+ this.isTokenSupported(chainIdentifier, val) ? val : null,
417
+ amount: FieldTypeEnum.BigInt,
418
+ exactIn: (val: string) => val==="true" ? true :
419
+ (val==="false" || val===undefined) ? false : null
420
+ });
421
+ if(parsedBody==null) throw {
422
+ code: 20100,
423
+ msg: "Invalid request body"
424
+ };
425
+ metadata.request = parsedBody;
426
+
427
+ const requestedAmount = {input: parsedBody.exactIn, amount: parsedBody.amount, token: parsedBody.token};
428
+ const request = {
429
+ chainIdentifier,
430
+ raw: req,
431
+ parsed: parsedBody,
432
+ metadata
433
+ };
434
+ const useToken = parsedBody.token;
435
+
436
+ this.checkTooManyInflightSwaps();
437
+
438
+ //Check request params
439
+ const fees = await this.AmountAssertions.preCheckFromBtcAmounts(this.type, request, requestedAmount);
440
+ metadata.times.requestChecked = Date.now();
441
+
442
+ //Create abortController for parallel prefetches
443
+ const responseStream = res.responseStream;
444
+ const abortController = getAbortController(responseStream);
445
+
446
+ //Pre-fetch data
447
+ const pricePrefetchPromise: Promise<bigint> = this.swapPricing.preFetchPrice(useToken, chainIdentifier).catch(e => {
448
+ this.logger.error("pricePrefetchPromise(): pricePrefetch error: ", e);
449
+ abortController.abort(e);
450
+ return null;
451
+ });
452
+ const balancePrefetch: Promise<bigint> = chainInterface.getBalance(signer.getAddress(), useToken).catch(e => {
453
+ this.logger.error("getBalancePrefetch(): balancePrefetch error: ", e);
454
+ abortController.abort(e);
455
+ return null;
456
+ });
457
+ const nativeBalancePrefetch: Promise<bigint> = useToken===chainInterface.getNativeCurrencyAddress() ?
458
+ balancePrefetch : this.prefetchNativeBalanceIfNeeded(chainIdentifier, abortController);
459
+ const channelsPrefetch: Promise<LightningNetworkChannel[]> = this.LightningAssertions.getChannelsPrefetch(abortController);
460
+
461
+ await this.checkNativeBalance(chainIdentifier, nativeBalancePrefetch, abortController.signal);
462
+
463
+ //Check valid amount specified (min/max)
464
+ const {
465
+ amountBD,
466
+ swapFee,
467
+ swapFeeInToken,
468
+ totalInToken
469
+ } = await this.AmountAssertions.checkFromBtcAmount(this.type, request, {...requestedAmount, pricePrefetch: pricePrefetchPromise}, fees, abortController.signal);
470
+ metadata.times.priceCalculated = Date.now();
471
+
472
+ //Check if we have enough funds to honor the request
473
+ await this.checkBalance(totalInToken, balancePrefetch, abortController.signal)
474
+ await this.LightningAssertions.checkInboundLiquidity(amountBD, channelsPrefetch, abortController.signal);
475
+ metadata.times.balanceChecked = Date.now();
476
+
477
+ const secret = randomBytes(32);
478
+ const hash = createHash("sha256").update(secret).digest();
479
+
480
+ const hodlInvoiceObj: HodlInvoiceInit = {
481
+ description: chainIdentifier+"-GAS-"+parsedBody.address,
482
+ cltvDelta: Number(this.config.minCltv) + 5,
483
+ expiresAt: Date.now()+(this.config.invoiceTimeoutSeconds*1000),
484
+ id: hash.toString("hex"),
485
+ mtokens: amountBD * 1000n
486
+ };
487
+ metadata.invoiceRequest = hodlInvoiceObj;
488
+
489
+ const hodlInvoice = await this.lightning.createHodlInvoice(hodlInvoiceObj);
490
+ abortController.signal.throwIfAborted();
491
+ metadata.times.invoiceCreated = Date.now();
492
+ metadata.invoiceResponse = {...hodlInvoice};
493
+
494
+ const createdSwap = new FromBtcLnTrustedSwap(
495
+ chainIdentifier,
496
+ hodlInvoice.request,
497
+ hodlInvoice.mtokens,
498
+ swapFee,
499
+ swapFeeInToken,
500
+ totalInToken,
501
+ secret.toString("hex"),
502
+ parsedBody.address,
503
+ useToken
504
+ );
505
+ metadata.times.swapCreated = Date.now();
506
+ createdSwap.metadata = metadata;
507
+
508
+ await PluginManager.swapCreate(createdSwap);
509
+ await this.storageManager.saveData(hash.toString("hex"), null, createdSwap);
510
+ this.subscribeToInvoice(createdSwap);
511
+
512
+ this.swapLogger.info(createdSwap, "REST: /createInvoice: Created swap invoice: "+hodlInvoice.request+" amount: "+amountBD.toString(10));
513
+
514
+ res.status(200).json({
515
+ msg: "Success",
516
+ code: 10000,
517
+ data: {
518
+ pr: hodlInvoice.request,
519
+ amountSats: amountBD.toString(10),
520
+ swapFeeSats: swapFee.toString(10),
521
+ swapFee: swapFeeInToken.toString(10),
522
+ total: totalInToken.toString(10),
523
+ intermediaryKey: signer.getAddress()
524
+ }
525
+ });
526
+ });
527
+
528
+ restServer.get(this.path+"/createInvoice", createInvoice);
529
+
530
+ const getInvoiceStatus = expressHandlerWrapper(async (req, res) => {
531
+ /**
532
+ * paymentHash: string payment hash of the invoice
533
+ */
534
+ const parsedBody = verifySchema({...req.body, ...req.query}, {
535
+ paymentHash: (val: string) => val!=null &&
536
+ typeof(val)==="string" &&
537
+ val.length===64 &&
538
+ HEX_REGEX.test(val) ? val: null,
539
+ });
540
+
541
+ await this.checkInvoiceStatus(parsedBody.paymentHash);
542
+
543
+ const invoiceData: FromBtcLnTrustedSwap = await this.storageManager.getData(parsedBody.paymentHash, null);
544
+ if (invoiceData==null) throw {
545
+ _httpStatus: 200,
546
+ code: 10001,
547
+ msg: "Invoice expired/canceled"
548
+ };
549
+
550
+ if (
551
+ invoiceData.state === FromBtcLnTrustedSwapState.CANCELED ||
552
+ invoiceData.state === FromBtcLnTrustedSwapState.REFUNDED
553
+ ) throw {
554
+ _httpStatus: 200,
555
+ code: 10001,
556
+ msg: "Invoice expired/canceled"
557
+ };
558
+
559
+ if (invoiceData.state === FromBtcLnTrustedSwapState.CREATED) throw {
560
+ _httpStatus: 200,
561
+ code: 10010,
562
+ msg: "Invoice yet unpaid"
563
+ };
564
+
565
+ if (invoiceData.state === FromBtcLnTrustedSwapState.RECEIVED) throw {
566
+ _httpStatus: 200,
567
+ code: 10011,
568
+ msg: "Invoice received, payment processing"
569
+ };
570
+
571
+ if (invoiceData.state === FromBtcLnTrustedSwapState.SENT) throw {
572
+ _httpStatus: 200,
573
+ code: 10012,
574
+ msg: "Tx sent",
575
+ data: {
576
+ txId: invoiceData.txIds.init
577
+ }
578
+ };
579
+
580
+ if (invoiceData.state === FromBtcLnTrustedSwapState.CONFIRMED) throw {
581
+ _httpStatus: 200,
582
+ code: 10000,
583
+ msg: "Success, tx confirmed",
584
+ data: {
585
+ txId: invoiceData.txIds.init
586
+ }
587
+ };
588
+
589
+ if (invoiceData.state === FromBtcLnTrustedSwapState.SETTLED) throw {
590
+ _httpStatus: 200,
591
+ code: 10000,
592
+ msg: "Success, tx confirmed - invoice settled",
593
+ data: {
594
+ txId: invoiceData.txIds.init
595
+ }
596
+ };
597
+ });
598
+ restServer.get(this.path+"/getInvoiceStatus", getInvoiceStatus);
599
+
600
+ this.logger.info("started at path: ", this.path);
601
+ }
602
+
603
+ async init() {
604
+ await this.storageManager.loadData(FromBtcLnTrustedSwap);
605
+ //Check if all swaps contain a valid amount
606
+ for(let {obj: swap} of await this.storageManager.query([])) {
607
+ if(swap.amount==null) {
608
+ const parsedPR = await this.lightning.parsePaymentRequest(swap.pr);
609
+ swap.amount = (parsedPR.mtokens + 999n) / 1000n;
610
+ }
611
+ }
612
+ await PluginManager.serviceInitialize(this);
613
+ }
614
+
615
+ getInfoData(): any {
616
+ return {
617
+ minCltv: Number(this.config.minCltv)
618
+ };
619
+ }
620
+
621
+ }
622
+