@atomiqlabs/lp-lib 15.0.14 → 16.0.0

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