@atomiqlabs/lp-lib 10.3.11

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (138) hide show
  1. package/LICENSE +201 -0
  2. package/dist/fees/IBtcFeeEstimator.d.ts +3 -0
  3. package/dist/fees/IBtcFeeEstimator.js +2 -0
  4. package/dist/fees/OneDollarFeeEstimator.d.ts +16 -0
  5. package/dist/fees/OneDollarFeeEstimator.js +71 -0
  6. package/dist/index.d.ts +33 -0
  7. package/dist/index.js +52 -0
  8. package/dist/info/InfoHandler.d.ts +17 -0
  9. package/dist/info/InfoHandler.js +70 -0
  10. package/dist/plugins/IPlugin.d.ts +118 -0
  11. package/dist/plugins/IPlugin.js +33 -0
  12. package/dist/plugins/PluginManager.d.ts +89 -0
  13. package/dist/plugins/PluginManager.js +263 -0
  14. package/dist/prices/BinanceSwapPrice.d.ts +27 -0
  15. package/dist/prices/BinanceSwapPrice.js +106 -0
  16. package/dist/prices/CoinGeckoSwapPrice.d.ts +31 -0
  17. package/dist/prices/CoinGeckoSwapPrice.js +76 -0
  18. package/dist/storage/IIntermediaryStorage.d.ts +15 -0
  19. package/dist/storage/IIntermediaryStorage.js +2 -0
  20. package/dist/storagemanager/IntermediaryStorageManager.d.ts +15 -0
  21. package/dist/storagemanager/IntermediaryStorageManager.js +113 -0
  22. package/dist/storagemanager/StorageManager.d.ts +12 -0
  23. package/dist/storagemanager/StorageManager.js +74 -0
  24. package/dist/swaps/FromBtcBaseSwap.d.ts +12 -0
  25. package/dist/swaps/FromBtcBaseSwap.js +16 -0
  26. package/dist/swaps/FromBtcBaseSwapHandler.d.ts +118 -0
  27. package/dist/swaps/FromBtcBaseSwapHandler.js +294 -0
  28. package/dist/swaps/FromBtcLnBaseSwapHandler.d.ts +25 -0
  29. package/dist/swaps/FromBtcLnBaseSwapHandler.js +55 -0
  30. package/dist/swaps/ISwapPrice.d.ts +44 -0
  31. package/dist/swaps/ISwapPrice.js +73 -0
  32. package/dist/swaps/SwapHandler.d.ts +186 -0
  33. package/dist/swaps/SwapHandler.js +292 -0
  34. package/dist/swaps/SwapHandlerSwap.d.ts +75 -0
  35. package/dist/swaps/SwapHandlerSwap.js +72 -0
  36. package/dist/swaps/ToBtcBaseSwap.d.ts +35 -0
  37. package/dist/swaps/ToBtcBaseSwap.js +61 -0
  38. package/dist/swaps/ToBtcBaseSwapHandler.d.ts +94 -0
  39. package/dist/swaps/ToBtcBaseSwapHandler.js +233 -0
  40. package/dist/swaps/frombtc_abstract/FromBtcAbs.d.ts +92 -0
  41. package/dist/swaps/frombtc_abstract/FromBtcAbs.js +386 -0
  42. package/dist/swaps/frombtc_abstract/FromBtcSwapAbs.d.ts +26 -0
  43. package/dist/swaps/frombtc_abstract/FromBtcSwapAbs.js +63 -0
  44. package/dist/swaps/frombtc_trusted/FromBtcTrusted.d.ts +55 -0
  45. package/dist/swaps/frombtc_trusted/FromBtcTrusted.js +586 -0
  46. package/dist/swaps/frombtc_trusted/FromBtcTrustedSwap.d.ts +43 -0
  47. package/dist/swaps/frombtc_trusted/FromBtcTrustedSwap.js +99 -0
  48. package/dist/swaps/frombtcln_abstract/FromBtcLnAbs.d.ts +105 -0
  49. package/dist/swaps/frombtcln_abstract/FromBtcLnAbs.js +731 -0
  50. package/dist/swaps/frombtcln_abstract/FromBtcLnSwapAbs.d.ts +29 -0
  51. package/dist/swaps/frombtcln_abstract/FromBtcLnSwapAbs.js +64 -0
  52. package/dist/swaps/frombtcln_trusted/FromBtcLnTrusted.d.ts +79 -0
  53. package/dist/swaps/frombtcln_trusted/FromBtcLnTrusted.js +514 -0
  54. package/dist/swaps/frombtcln_trusted/FromBtcLnTrustedSwap.d.ts +28 -0
  55. package/dist/swaps/frombtcln_trusted/FromBtcLnTrustedSwap.js +66 -0
  56. package/dist/swaps/tobtc_abstract/ToBtcAbs.d.ts +290 -0
  57. package/dist/swaps/tobtc_abstract/ToBtcAbs.js +1056 -0
  58. package/dist/swaps/tobtc_abstract/ToBtcSwapAbs.d.ts +29 -0
  59. package/dist/swaps/tobtc_abstract/ToBtcSwapAbs.js +70 -0
  60. package/dist/swaps/tobtcln_abstract/ToBtcLnAbs.d.ts +246 -0
  61. package/dist/swaps/tobtcln_abstract/ToBtcLnAbs.js +1169 -0
  62. package/dist/swaps/tobtcln_abstract/ToBtcLnSwapAbs.d.ts +27 -0
  63. package/dist/swaps/tobtcln_abstract/ToBtcLnSwapAbs.js +65 -0
  64. package/dist/utils/Utils.d.ts +32 -0
  65. package/dist/utils/Utils.js +109 -0
  66. package/dist/utils/coinselect2/accumulative.d.ts +6 -0
  67. package/dist/utils/coinselect2/accumulative.js +44 -0
  68. package/dist/utils/coinselect2/blackjack.d.ts +6 -0
  69. package/dist/utils/coinselect2/blackjack.js +41 -0
  70. package/dist/utils/coinselect2/index.d.ts +16 -0
  71. package/dist/utils/coinselect2/index.js +40 -0
  72. package/dist/utils/coinselect2/utils.d.ts +64 -0
  73. package/dist/utils/coinselect2/utils.js +121 -0
  74. package/dist/utils/paramcoders/IParamReader.d.ts +5 -0
  75. package/dist/utils/paramcoders/IParamReader.js +2 -0
  76. package/dist/utils/paramcoders/IParamWriter.d.ts +4 -0
  77. package/dist/utils/paramcoders/IParamWriter.js +2 -0
  78. package/dist/utils/paramcoders/LegacyParamEncoder.d.ts +10 -0
  79. package/dist/utils/paramcoders/LegacyParamEncoder.js +33 -0
  80. package/dist/utils/paramcoders/ParamDecoder.d.ts +25 -0
  81. package/dist/utils/paramcoders/ParamDecoder.js +234 -0
  82. package/dist/utils/paramcoders/ParamEncoder.d.ts +9 -0
  83. package/dist/utils/paramcoders/ParamEncoder.js +22 -0
  84. package/dist/utils/paramcoders/SchemaVerifier.d.ts +22 -0
  85. package/dist/utils/paramcoders/SchemaVerifier.js +85 -0
  86. package/dist/utils/paramcoders/server/ServerParamDecoder.d.ts +8 -0
  87. package/dist/utils/paramcoders/server/ServerParamDecoder.js +105 -0
  88. package/dist/utils/paramcoders/server/ServerParamEncoder.d.ts +11 -0
  89. package/dist/utils/paramcoders/server/ServerParamEncoder.js +76 -0
  90. package/package.json +43 -0
  91. package/src/fees/IBtcFeeEstimator.ts +7 -0
  92. package/src/fees/OneDollarFeeEstimator.ts +95 -0
  93. package/src/index.ts +46 -0
  94. package/src/info/InfoHandler.ts +106 -0
  95. package/src/plugins/IPlugin.ts +155 -0
  96. package/src/plugins/PluginManager.ts +310 -0
  97. package/src/prices/BinanceSwapPrice.ts +114 -0
  98. package/src/prices/CoinGeckoSwapPrice.ts +88 -0
  99. package/src/storage/IIntermediaryStorage.ts +21 -0
  100. package/src/storagemanager/IntermediaryStorageManager.ts +101 -0
  101. package/src/storagemanager/StorageManager.ts +68 -0
  102. package/src/swaps/FromBtcBaseSwap.ts +21 -0
  103. package/src/swaps/FromBtcBaseSwapHandler.ts +375 -0
  104. package/src/swaps/FromBtcLnBaseSwapHandler.ts +48 -0
  105. package/src/swaps/ISwapPrice.ts +94 -0
  106. package/src/swaps/SwapHandler.ts +404 -0
  107. package/src/swaps/SwapHandlerSwap.ts +133 -0
  108. package/src/swaps/ToBtcBaseSwap.ts +76 -0
  109. package/src/swaps/ToBtcBaseSwapHandler.ts +309 -0
  110. package/src/swaps/frombtc_abstract/FromBtcAbs.ts +484 -0
  111. package/src/swaps/frombtc_abstract/FromBtcSwapAbs.ts +77 -0
  112. package/src/swaps/frombtc_trusted/FromBtcTrusted.ts +661 -0
  113. package/src/swaps/frombtc_trusted/FromBtcTrustedSwap.ts +158 -0
  114. package/src/swaps/frombtcln_abstract/FromBtcLnAbs.ts +864 -0
  115. package/src/swaps/frombtcln_abstract/FromBtcLnSwapAbs.ts +82 -0
  116. package/src/swaps/frombtcln_trusted/FromBtcLnTrusted.ts +592 -0
  117. package/src/swaps/frombtcln_trusted/FromBtcLnTrustedSwap.ts +90 -0
  118. package/src/swaps/tobtc_abstract/ToBtcAbs.ts +1249 -0
  119. package/src/swaps/tobtc_abstract/ToBtcSwapAbs.ts +112 -0
  120. package/src/swaps/tobtcln_abstract/ToBtcLnAbs.ts +1422 -0
  121. package/src/swaps/tobtcln_abstract/ToBtcLnSwapAbs.ts +87 -0
  122. package/src/utils/Utils.ts +108 -0
  123. package/src/utils/coinselect2/accumulative.js +32 -0
  124. package/src/utils/coinselect2/accumulative.ts +58 -0
  125. package/src/utils/coinselect2/blackjack.js +29 -0
  126. package/src/utils/coinselect2/blackjack.ts +54 -0
  127. package/src/utils/coinselect2/index.js +16 -0
  128. package/src/utils/coinselect2/index.ts +50 -0
  129. package/src/utils/coinselect2/utils.js +110 -0
  130. package/src/utils/coinselect2/utils.ts +183 -0
  131. package/src/utils/paramcoders/IParamReader.ts +8 -0
  132. package/src/utils/paramcoders/IParamWriter.ts +8 -0
  133. package/src/utils/paramcoders/LegacyParamEncoder.ts +28 -0
  134. package/src/utils/paramcoders/ParamDecoder.ts +219 -0
  135. package/src/utils/paramcoders/ParamEncoder.ts +30 -0
  136. package/src/utils/paramcoders/SchemaVerifier.ts +97 -0
  137. package/src/utils/paramcoders/server/ServerParamDecoder.ts +115 -0
  138. package/src/utils/paramcoders/server/ServerParamEncoder.ts +76 -0
@@ -0,0 +1,661 @@
1
+ import {FromBtcBaseConfig, FromBtcBaseSwapHandler} from "../FromBtcBaseSwapHandler";
2
+ import {FromBtcTrustedSwap, FromBtcTrustedSwapState} from "./FromBtcTrustedSwap";
3
+ import {BitcoinRpc, BtcBlock, BtcTx, ClaimEvent, InitializeEvent, RefundEvent, SwapData} from "@atomiqlabs/base";
4
+ import {Express, Request, Response} from "express";
5
+ import {MultichainData, SwapHandlerType} from "../SwapHandler";
6
+ import * as BN from "bn.js";
7
+ import {IIntermediaryStorage} from "../../storage/IIntermediaryStorage";
8
+ import {
9
+ AuthenticatedLnd,
10
+ broadcastChainTransaction,
11
+ ChainTransaction, createChainAddress, createHodlInvoice,
12
+ getChainTransactions, getHeight,
13
+ signPsbt,
14
+ subscribeToTransactions, SubscribeToTransactionsChainTransactionEvent
15
+ } from "lightning";
16
+ import {ISwapPrice} from "../ISwapPrice";
17
+ import {PluginManager} from "../../plugins/PluginManager";
18
+ import {address, networks, Psbt, Transaction, TxOutput} from "bitcoinjs-lib";
19
+ import {IBtcFeeEstimator} from "../../fees/IBtcFeeEstimator";
20
+ import {utils} from "../../utils/coinselect2/utils";
21
+ import {expressHandlerWrapper, HEX_REGEX} from "../../utils/Utils";
22
+ import {IParamReader} from "../../utils/paramcoders/IParamReader";
23
+ import {ServerParamEncoder} from "../../utils/paramcoders/server/ServerParamEncoder";
24
+ import {FieldTypeEnum, verifySchema} from "../../utils/paramcoders/SchemaVerifier";
25
+ import * as bitcoin from "bitcoinjs-lib";
26
+ import {serverParamDecoder} from "../../utils/paramcoders/server/ServerParamDecoder";
27
+
28
+ export type FromBtcTrustedConfig = FromBtcBaseConfig & {
29
+ bitcoinNetwork: networks.Network,
30
+ feeEstimator: IBtcFeeEstimator,
31
+ doubleSpendCheckInterval: number,
32
+ swapAddressExpiry: number,
33
+ recommendFeeMultiplier?: number,
34
+ }
35
+
36
+ export type FromBtcTrustedRequestType = {
37
+ address: string,
38
+ refundAddress: string,
39
+ amount: BN,
40
+ exactOut?: boolean
41
+ };
42
+
43
+ export class FromBtcTrusted extends FromBtcBaseSwapHandler<FromBtcTrustedSwap, FromBtcTrustedSwapState> {
44
+ readonly type: SwapHandlerType = SwapHandlerType.FROM_BTC_TRUSTED;
45
+
46
+ readonly config: FromBtcTrustedConfig;
47
+ readonly bitcoinRpc: BitcoinRpc<BtcBlock>;
48
+
49
+ readonly subscriptions: Map<string, FromBtcTrustedSwap> = new Map<string, FromBtcTrustedSwap>();
50
+ readonly doubleSpendWatchdogSwaps: Set<FromBtcTrustedSwap> = new Set<FromBtcTrustedSwap>();
51
+
52
+ readonly refundedSwaps: Map<string, string> = new Map();
53
+ readonly doubleSpentSwaps: Map<string, string> = new Map();
54
+ readonly processedTxIds: Map<string, { txId: string, adjustedAmount: BN, adjustedTotal: BN }> = new Map();
55
+
56
+ constructor(
57
+ storageDirectory: IIntermediaryStorage<FromBtcTrustedSwap>,
58
+ path: string,
59
+ chains: MultichainData,
60
+ lnd: AuthenticatedLnd,
61
+ swapPricing: ISwapPrice,
62
+ bitcoinRpc: BitcoinRpc<BtcBlock>,
63
+ config: FromBtcTrustedConfig
64
+ ) {
65
+ super(storageDirectory, path, chains, lnd, swapPricing);
66
+ this.config = config;
67
+ this.config.recommendFeeMultiplier ??= 1.25;
68
+ this.bitcoinRpc = bitcoinRpc;
69
+ for(let chainId in chains.chains) {
70
+ this.allowedTokens[chainId] = new Set<string>([chains.chains[chainId].swapContract.getNativeCurrencyAddress()]);
71
+ }
72
+ }
73
+
74
+ private getAllAncestors(tx: ChainTransaction): Promise<{tx: BtcTx, vout: number}[]> {
75
+ return Promise.all(tx.inputs.map(input => this.bitcoinRpc.getTransaction(input.transaction_id).then(tx => {
76
+ return {tx, vout: input.transaction_vout}
77
+ })));
78
+ }
79
+
80
+ private async refundSwap(swap: FromBtcTrustedSwap) {
81
+ let unlock = swap.lock(30*1000);
82
+ if(unlock==null) return;
83
+
84
+ const feeRate = await this.config.feeEstimator.estimateFee();
85
+
86
+ const initialTx = Transaction.fromHex(swap.rawTx);
87
+ const ourOutput = initialTx.outs[swap.vout];
88
+
89
+ //Construct PSBT
90
+ const refundOutputScript = address.toOutputScript(swap.refundAddress, this.config.bitcoinNetwork);
91
+ const txBytes = utils.transactionBytes([{type: "p2wpkh"}], [{script: refundOutputScript}], "p2wpkh");
92
+ const txFee = txBytes*feeRate;
93
+ const adjustedOutput = ourOutput.value-txFee;
94
+ if(adjustedOutput<546) {
95
+ this.swapLogger.error(swap, "refundSwap(): cannot refund swap because of dust limit, txId: "+swap.txId);
96
+ unlock();
97
+ return;
98
+ }
99
+
100
+ //Construct PSBT
101
+ const _psbt = new Psbt({network: this.config.bitcoinNetwork});
102
+ _psbt.addInput({
103
+ hash: initialTx.getHash(),
104
+ index: swap.vout,
105
+ witnessUtxo: ourOutput,
106
+ sighashType: 0x01,
107
+ sequence: 0xfffffffd
108
+ });
109
+ _psbt.addOutput({
110
+ script: refundOutputScript,
111
+ value: adjustedOutput
112
+ });
113
+
114
+ //Sign
115
+ const {psbt, transaction} = await signPsbt({
116
+ lnd: this.LND,
117
+ psbt: _psbt.toHex()
118
+ });
119
+ if(swap.metadata!=null) swap.metadata.times.refundSignPSBT = Date.now();
120
+ this.swapLogger.debug(swap, "refundSwap(): signed raw transaction: "+transaction);
121
+
122
+ const signedTx = Transaction.fromHex(transaction);
123
+ const refundTxId = signedTx.getId();
124
+ swap.refundTxId = refundTxId;
125
+
126
+ //Send the refund TX
127
+ await broadcastChainTransaction({transaction, lnd: this.LND});
128
+
129
+ this.swapLogger.debug(swap, "refundSwap(): sent refund transaction: "+refundTxId);
130
+
131
+ this.refundedSwaps.set(swap.getHash(), refundTxId);
132
+ await this.removeSwapData(swap, FromBtcTrustedSwapState.REFUNDED);
133
+ unlock();
134
+ }
135
+
136
+ private async burn(swap: FromBtcTrustedSwap) {
137
+ const initialTx = Transaction.fromHex(swap.rawTx);
138
+ const ourOutput = initialTx.outs[swap.vout];
139
+
140
+ //Construct PSBT
141
+ const _psbt = new Psbt({network: this.config.bitcoinNetwork});
142
+ _psbt.addInput({
143
+ hash: initialTx.getHash(),
144
+ index: swap.vout,
145
+ witnessUtxo: ourOutput,
146
+ sighashType: 0x01,
147
+ sequence: 0xfffffffd
148
+ });
149
+ _psbt.addOutput({
150
+ script: Buffer.concat([Buffer.from([0x6a, 20]), Buffer.from("BURN, BABY, BURN! AQ", "ascii")]),
151
+ value: 0
152
+ });
153
+
154
+ //Sign
155
+ const {psbt, transaction} = await signPsbt({
156
+ lnd: this.LND,
157
+ psbt: _psbt.toHex()
158
+ });
159
+ if(swap.metadata!=null) swap.metadata.times.burnSignPSBT = Date.now();
160
+ this.swapLogger.debug(swap, "burn(): signed raw transaction: "+transaction);
161
+
162
+ const signedTx = Transaction.fromHex(transaction);
163
+ const burnTxId = signedTx.getId();
164
+ swap.burnTxId = burnTxId;
165
+
166
+ //Send the original TX + our burn TX as a package
167
+ const sendTxns = [swap.rawTx, transaction];
168
+ await this.bitcoinRpc.sendRawPackage(sendTxns);
169
+
170
+ this.swapLogger.debug(swap, "burn(): sent burn transaction: "+burnTxId);
171
+ this.doubleSpentSwaps.set(swap.getHash(), burnTxId);
172
+ await this.removeSwapData(swap, FromBtcTrustedSwapState.DOUBLE_SPENT);
173
+ }
174
+
175
+ protected async processPastSwap(swap: FromBtcTrustedSwap, tx: ChainTransaction | null): Promise<void> {
176
+ let parsedTx: Transaction = null;
177
+ let foundVout: TxOutput = null;
178
+ let vout: number = -1;
179
+ if(tx!=null) {
180
+ parsedTx = Transaction.fromHex(tx.transaction);
181
+ const requiredOutputScript = address.toOutputScript(swap.btcAddress, this.config.bitcoinNetwork);
182
+ vout = parsedTx.outs.findIndex(vout => vout.script.equals(requiredOutputScript));
183
+ if(vout!==-1) foundVout = parsedTx.outs[vout];
184
+ }
185
+
186
+ const {swapContract, signer} = this.getChain(swap.chainIdentifier);
187
+
188
+ if(swap.state===FromBtcTrustedSwapState.CREATED) {
189
+ this.subscriptions.set(swap.btcAddress, swap);
190
+ if(foundVout==null) {
191
+ //Check expiry
192
+ if(swap.expiresAt<Date.now()) {
193
+ this.subscriptions.delete(swap.btcAddress);
194
+ await this.removeSwapData(swap, FromBtcTrustedSwapState.EXPIRED);
195
+ return;
196
+ }
197
+ return;
198
+ }
199
+ const sentSats = new BN(foundVout.value);
200
+ if(sentSats.eq(swap.inputSats)) {
201
+ swap.adjustedInput = swap.inputSats;
202
+ swap.adjustedOutput = swap.outputTokens;
203
+ } else {
204
+ //If lower than minimum then ignore
205
+ if(sentSats.lt(this.config.min)) return;
206
+ if(sentSats.gt(this.config.max)) {
207
+ swap.rawTx = tx.transaction;
208
+ swap.txId = tx.id;
209
+ swap.vout = vout;
210
+ this.subscriptions.delete(swap.btcAddress);
211
+ await this.refundSwap(swap);
212
+ return;
213
+ }
214
+ //Adjust the amount
215
+ swap.adjustedInput = sentSats;
216
+ swap.adjustedOutput = swap.outputTokens.mul(sentSats).div(swap.inputSats);
217
+ }
218
+ swap.rawTx = tx.transaction;
219
+ swap.txId = tx.id;
220
+ swap.vout = vout;
221
+ this.subscriptions.delete(swap.btcAddress);
222
+ await swap.setState(FromBtcTrustedSwapState.RECEIVED);
223
+ await this.storageManager.saveData(swap.getHash(), null, swap);
224
+ }
225
+
226
+ if(swap.state===FromBtcTrustedSwapState.RECEIVED) {
227
+ //Check if transaction still exists
228
+ if(tx==null || foundVout==null || tx.id!==swap.txId) {
229
+ await swap.setState(FromBtcTrustedSwapState.CREATED);
230
+ await this.storageManager.saveData(swap.getHash(), null, swap);
231
+ return;
232
+ }
233
+ //Check if it is confirmed
234
+ if(tx.confirmation_count>0) {
235
+ await swap.setState(FromBtcTrustedSwapState.BTC_CONFIRMED);
236
+ await this.storageManager.saveData(swap.getHash(), null, swap);
237
+ } else {
238
+ //Check if it pays high enough fee AND has confirmed ancestors
239
+ const ancestors = await this.getAllAncestors(tx);
240
+ const allAncestorsConfirmed = ancestors.reduce((prev, curr) => prev && curr.tx.confirmations>0, true);
241
+ const totalInput = ancestors.reduce((prev, curr) => prev + curr.tx.outs[curr.vout].value, 0);
242
+ const totalOutput = parsedTx.outs.reduce((prev, curr) => prev + curr.value, 0);
243
+ const fee = totalInput-totalOutput;
244
+ const feePerVbyte = Math.ceil(fee/parsedTx.virtualSize());
245
+ if(
246
+ allAncestorsConfirmed &&
247
+ (feePerVbyte>=swap.recommendedFee || feePerVbyte>=await this.config.feeEstimator.estimateFee())
248
+ ) {
249
+ if(swap.state!==FromBtcTrustedSwapState.RECEIVED) return;
250
+ await swap.setState(FromBtcTrustedSwapState.BTC_CONFIRMED);
251
+ await this.storageManager.saveData(swap.getHash(), null, swap);
252
+ } else {
253
+ return;
254
+ }
255
+ }
256
+ }
257
+
258
+ if(swap.doubleSpent || tx==null || foundVout==null || tx.id!==swap.txId) {
259
+ if(!swap.doubleSpent) {
260
+ swap.doubleSpent = true;
261
+ try {
262
+ await this.burn(swap);
263
+ this.doubleSpendWatchdogSwaps.delete(swap);
264
+ } catch (e) {
265
+ this.swapLogger.error(swap, "processPastSwap(): Error burning swap: ", e);
266
+ swap.doubleSpent = false;
267
+ }
268
+ }
269
+ return;
270
+ } else {
271
+ if(!this.doubleSpendWatchdogSwaps.has(swap)) {
272
+ this.swapLogger.debug(swap, "processPastSwap(): Adding swap transaction to double spend watchdog list: ", swap.txId);
273
+ this.doubleSpendWatchdogSwaps.add(swap);
274
+ }
275
+ }
276
+ if(tx.confirmation_count > 0) {
277
+ this.swapLogger.debug(swap, "processPastSwap(): Removing confirmed swap transaction from double spend watchdog list: ", swap.txId);
278
+ this.doubleSpendWatchdogSwaps.delete(swap);
279
+ }
280
+
281
+ if(swap.state===FromBtcTrustedSwapState.BTC_CONFIRMED) {
282
+ //Send gas token
283
+ const balance: Promise<BN> = swapContract.getBalance(signer.getAddress(), swapContract.getNativeCurrencyAddress(), false);
284
+ try {
285
+ await this.checkBalance(swap.adjustedOutput, balance, null);
286
+ if(swap.metadata!=null) swap.metadata.times.receivedBalanceChecked = Date.now();
287
+ } catch (e) {
288
+ this.swapLogger.error(swap, "processPastSwap(): Error not enough balance: ", e);
289
+ await this.refundSwap(swap);
290
+ return;
291
+ }
292
+
293
+ if(swap.state!==FromBtcTrustedSwapState.BTC_CONFIRMED) return;
294
+
295
+ let unlock = swap.lock(30*1000);
296
+ if(unlock==null) return;
297
+
298
+ const txns = await swapContract.txsTransfer(signer.getAddress(), swapContract.getNativeCurrencyAddress(), swap.adjustedOutput, swap.dstAddress);
299
+ await swapContract.sendAndConfirm(signer, txns, true, null, false, async (txId: string, rawTx: string) => {
300
+ swap.txIds = {init: txId};
301
+ swap.scRawTx = rawTx;
302
+ if(swap.state===FromBtcTrustedSwapState.BTC_CONFIRMED) {
303
+ await swap.setState(FromBtcTrustedSwapState.SENT);
304
+ await this.storageManager.saveData(swap.getHash(), null, swap);
305
+ }
306
+ if(unlock!=null) unlock();
307
+ unlock = null;
308
+ });
309
+ }
310
+
311
+ if(swap.state===FromBtcTrustedSwapState.SENT) {
312
+ const txStatus = await swapContract.getTxStatus(swap.scRawTx);
313
+ switch(txStatus) {
314
+ case "not_found":
315
+ //Retry
316
+ swap.txIds = {init: null};
317
+ swap.scRawTx = null;
318
+ await swap.setState(FromBtcTrustedSwapState.RECEIVED);
319
+ await this.storageManager.saveData(swap.getHash(), null, swap);
320
+ break;
321
+ case "reverted":
322
+ //Cancel invoice
323
+ await this.refundSwap(swap);
324
+ this.swapLogger.info(swap, "processPastSwap(): transaction reverted, refunding btc on-chain: ", swap.btcAddress);
325
+ break;
326
+ case "success":
327
+ await swap.setState(FromBtcTrustedSwapState.CONFIRMED);
328
+ await this.storageManager.saveData(swap.getHash(), null, swap);
329
+ break;
330
+ }
331
+ }
332
+
333
+ if(swap.state===FromBtcTrustedSwapState.CONFIRMED) {
334
+ this.processedTxIds.set(swap.getHash(), {
335
+ txId: swap.txIds.init,
336
+ adjustedAmount: swap.adjustedInput,
337
+ adjustedTotal: swap.adjustedOutput
338
+ });
339
+ if(tx.confirmation_count>0) await this.removeSwapData(swap, FromBtcTrustedSwapState.FINISHED);
340
+ }
341
+ }
342
+
343
+ protected async processPastSwaps(): Promise<void> {
344
+ const queriedData = await this.storageManager.query([
345
+ {
346
+ key: "state",
347
+ value: [
348
+ FromBtcTrustedSwapState.CREATED,
349
+ FromBtcTrustedSwapState.RECEIVED,
350
+ FromBtcTrustedSwapState.BTC_CONFIRMED,
351
+ FromBtcTrustedSwapState.SENT,
352
+ FromBtcTrustedSwapState.CONFIRMED
353
+ ]
354
+ }
355
+ ]);
356
+
357
+ const startingBlockheight = queriedData.reduce((prev, swap) => Math.min(prev, swap.createdHeight), Infinity);
358
+ if(startingBlockheight===Infinity) return;
359
+ const {transactions} = await getChainTransactions({lnd: this.LND, after: startingBlockheight});
360
+
361
+ for(let swap of queriedData) {
362
+ const tx = transactions.find(tx => tx.output_addresses.includes(swap.btcAddress));
363
+ await this.processPastSwap(swap, tx);
364
+ }
365
+ }
366
+
367
+ private isValidBitcoinAddress(address: string) {
368
+ try {
369
+ bitcoin.address.toOutputScript(address, this.config.bitcoinNetwork);
370
+ return true;
371
+ } catch (e) {}
372
+ return false;
373
+ }
374
+
375
+ startRestServer(restServer: Express): void {
376
+
377
+ const getAddress = expressHandlerWrapper(async (req: Request & {paramReader: IParamReader}, res: Response & {responseStream: ServerParamEncoder}) => {
378
+ const metadata: {
379
+ request: any,
380
+ invoiceRequest?: any,
381
+ invoiceResponse?: any,
382
+ times: {[key: string]: number}
383
+ } = {request: {}, times: {}};
384
+
385
+ const chainIdentifier = req.query.chain as string ?? this.chains.default;
386
+ const {swapContract, signer} = this.getChain(chainIdentifier);
387
+
388
+ metadata.times.requestReceived = Date.now();
389
+ /**
390
+ * address: string solana address of the recipient
391
+ * refundAddress: string bitcoin address to use in case of refund
392
+ * amount: string amount (in lamports/smart chain base units) of the invoice
393
+ * exactOut: boolean whether to create and exact output swap
394
+ */
395
+
396
+ const parsedBody: FromBtcTrustedRequestType = await req.paramReader.getParams({
397
+ address: (val: string) => val!=null &&
398
+ typeof(val)==="string" &&
399
+ swapContract.isValidAddress(val) ? val : null,
400
+ refundAddress: (val: string) => val!=null &&
401
+ typeof(val)==="string" &&
402
+ this.isValidBitcoinAddress(val) ? val : null,
403
+ amount: FieldTypeEnum.BN,
404
+ exactOut: FieldTypeEnum.BooleanOptional
405
+ });
406
+ if(parsedBody==null) throw {
407
+ code: 20100,
408
+ msg: "Invalid request body"
409
+ };
410
+ metadata.request = parsedBody;
411
+
412
+ const requestedAmount = {input: !parsedBody.exactOut, amount: parsedBody.amount};
413
+ const request = {
414
+ chainIdentifier,
415
+ raw: req,
416
+ parsed: parsedBody,
417
+ metadata
418
+ };
419
+ const useToken = swapContract.getNativeCurrencyAddress();
420
+
421
+ //Check request params
422
+ const fees = await this.preCheckAmounts(request, requestedAmount, useToken);
423
+ metadata.times.requestChecked = Date.now();
424
+
425
+ //Create abortController for parallel prefetches
426
+ const responseStream = res.responseStream;
427
+ const abortController = this.getAbortController(responseStream);
428
+
429
+ //Pre-fetch data
430
+ const {pricePrefetchPromise} = this.getFromBtcPricePrefetches(chainIdentifier, useToken, abortController);
431
+ const balancePrefetch = swapContract.getBalance(signer.getAddress(), useToken, false).catch(e => {
432
+ this.logger.error("getBalancePrefetch(): balancePrefetch error: ", e);
433
+ abortController.abort(e);
434
+ return null;
435
+ });
436
+
437
+ //Check valid amount specified (min/max)
438
+ const {
439
+ amountBD,
440
+ swapFee,
441
+ swapFeeInToken,
442
+ totalInToken
443
+ } = await this.checkFromBtcAmount(request, requestedAmount, fees, useToken, abortController.signal, pricePrefetchPromise);
444
+ metadata.times.priceCalculated = Date.now();
445
+
446
+ //Check if we have enough funds to honor the request
447
+ await this.checkBalance(totalInToken, balancePrefetch, abortController.signal)
448
+ metadata.times.balanceChecked = Date.now();
449
+
450
+ const {address: receiveAddress} = await createChainAddress({
451
+ lnd: this.LND,
452
+ format: "p2wpkh"
453
+ });
454
+ abortController.signal.throwIfAborted();
455
+ metadata.times.addressCreated = Date.now();
456
+
457
+ const {current_block_height} = await getHeight({lnd: this.LND});
458
+ const feeRate = await this.config.feeEstimator.estimateFee();
459
+ const recommendedFee = Math.ceil(feeRate*this.config.recommendFeeMultiplier);
460
+
461
+ const createdSwap = new FromBtcTrustedSwap(
462
+ chainIdentifier,
463
+ swapFee,
464
+ swapFeeInToken,
465
+ receiveAddress,
466
+ amountBD,
467
+ parsedBody.address,
468
+ totalInToken,
469
+ current_block_height,
470
+ Date.now()+(this.config.swapAddressExpiry*1000),
471
+ recommendedFee,
472
+ parsedBody.refundAddress
473
+ );
474
+ metadata.times.swapCreated = Date.now();
475
+ createdSwap.metadata = metadata;
476
+
477
+ await PluginManager.swapCreate(createdSwap);
478
+ await this.storageManager.saveData(createdSwap.getHash(), null, createdSwap);
479
+ this.subscriptions.set(createdSwap.btcAddress, createdSwap);
480
+
481
+ this.swapLogger.info(createdSwap, "REST: /getAddress: Created swap address: "+createdSwap.btcAddress+" amount: "+amountBD.toString(10));
482
+
483
+ await responseStream.writeParamsAndEnd({
484
+ msg: "Success",
485
+ code: 10000,
486
+ data: {
487
+ paymentHash: createdSwap.getHash(),
488
+ btcAddress: receiveAddress,
489
+ amountSats: amountBD.toString(10),
490
+ swapFeeSats: swapFee.toString(10),
491
+ swapFee: swapFeeInToken.toString(10),
492
+ total: totalInToken.toString(10),
493
+ intermediaryKey: signer.getAddress(),
494
+ recommendedFee,
495
+ expiresAt: createdSwap.expiresAt
496
+ }
497
+ });
498
+ });
499
+
500
+ restServer.use(this.path+"/getAddress", serverParamDecoder(10*1000));
501
+ restServer.post(this.path+"/getAddress", getAddress);
502
+
503
+ const getInvoiceStatus = expressHandlerWrapper(async (req, res) => {
504
+ /**
505
+ * paymentHash: string payment hash of the invoice
506
+ */
507
+ const parsedBody = verifySchema(req.query, {
508
+ paymentHash: (val: string) => val!=null &&
509
+ typeof(val)==="string" &&
510
+ val.length===64 &&
511
+ HEX_REGEX.test(val) ? val: null,
512
+ });
513
+
514
+ const processedTxData = this.processedTxIds.get(parsedBody.paymentHash);
515
+ if(processedTxData!=null) throw {
516
+ _httpStatus: 200,
517
+ code: 10000,
518
+ msg: "Success, tx confirmed",
519
+ data: processedTxData
520
+ };
521
+
522
+ const refundTxId = this.refundedSwaps.get(parsedBody.paymentHash);
523
+ if(refundTxId!=null) throw {
524
+ _httpStatus: 200,
525
+ code: 10014,
526
+ msg: "Refunded",
527
+ data: {
528
+ txId: refundTxId
529
+ }
530
+ };
531
+
532
+ const doubleSpendTxId = this.doubleSpentSwaps.get(parsedBody.paymentHash);
533
+ if(doubleSpendTxId!=null) throw {
534
+ _httpStatus: 200,
535
+ code: 10015,
536
+ msg: "Double spend detected, deposit burned",
537
+ data: {
538
+ txId: doubleSpendTxId
539
+ }
540
+ };
541
+
542
+ const invoiceData: FromBtcTrustedSwap = await this.storageManager.getData(parsedBody.paymentHash, null);
543
+ if (invoiceData==null) throw {
544
+ _httpStatus: 200,
545
+ code: 10001,
546
+ msg: "Swap expired/canceled"
547
+ };
548
+
549
+ if (invoiceData.state === FromBtcTrustedSwapState.CREATED) throw {
550
+ _httpStatus: 200,
551
+ code: 10010,
552
+ msg: "Bitcoin yet unpaid"
553
+ };
554
+
555
+ if (invoiceData.state === FromBtcTrustedSwapState.RECEIVED) throw {
556
+ _httpStatus: 200,
557
+ code: 10011,
558
+ msg: "Bitcoin received, payment processing",
559
+ data: {
560
+ adjustedAmount: invoiceData.adjustedInput.toString(10),
561
+ adjustedTotal: invoiceData.adjustedOutput.toString(10)
562
+ }
563
+ };
564
+
565
+ if (invoiceData.state === FromBtcTrustedSwapState.BTC_CONFIRMED) throw {
566
+ _httpStatus: 200,
567
+ code: 10013,
568
+ msg: "Bitcoin accepted, payment processing",
569
+ data: {
570
+ adjustedAmount: invoiceData.adjustedInput.toString(10),
571
+ adjustedTotal: invoiceData.adjustedOutput.toString(10)
572
+ }
573
+ };
574
+
575
+ if (invoiceData.state === FromBtcTrustedSwapState.SENT) throw {
576
+ _httpStatus: 200,
577
+ code: 10012,
578
+ msg: "Tx sent",
579
+ data: {
580
+ adjustedAmount: invoiceData.adjustedInput.toString(10),
581
+ adjustedTotal: invoiceData.adjustedOutput.toString(10),
582
+ txId: invoiceData.txIds.init
583
+ }
584
+ };
585
+
586
+ if (invoiceData.state === FromBtcTrustedSwapState.CONFIRMED || invoiceData.state === FromBtcTrustedSwapState.FINISHED) throw {
587
+ _httpStatus: 200,
588
+ code: 10000,
589
+ msg: "Success, tx confirmed",
590
+ data: {
591
+ adjustedAmount: invoiceData.adjustedInput.toString(10),
592
+ adjustedTotal: invoiceData.adjustedOutput.toString(10),
593
+ txId: invoiceData.txIds.init
594
+ }
595
+ };
596
+ });
597
+
598
+ restServer.get(this.path+"/getAddressStatus", getInvoiceStatus);
599
+
600
+ this.logger.info("started at path: ", this.path);
601
+ }
602
+
603
+ private async checkDoubleSpends(): Promise<void> {
604
+ for(let swap of this.doubleSpendWatchdogSwaps.keys()) {
605
+ const tx = await this.bitcoinRpc.getTransaction(swap.txId);
606
+ if(tx==null) {
607
+ this.swapLogger.debug(swap, "checkDoubleSpends(): Swap was double spent, burning... - original txId: "+swap.txId);
608
+ this.processPastSwap(swap, null);
609
+ }
610
+ }
611
+ }
612
+
613
+ private async startDoubleSpendWatchdog() {
614
+ let rerun: () => Promise<void>;
615
+ rerun = async () => {
616
+ await this.checkDoubleSpends().catch( e => console.error(e));
617
+ setTimeout(rerun, this.config.doubleSpendCheckInterval);
618
+ };
619
+ await rerun();
620
+ }
621
+
622
+ private listenToTxns() {
623
+ const res = subscribeToTransactions({lnd: this.LND});
624
+ res.on("chain_transaction", (tx: SubscribeToTransactionsChainTransactionEvent) => {
625
+ for(let address of tx.output_addresses) {
626
+ const savedSwap = this.subscriptions.get(address);
627
+ if(savedSwap==null) continue;
628
+ this.processPastSwap(savedSwap, tx);
629
+ return;
630
+ }
631
+ });
632
+ }
633
+
634
+ async startWatchdog() {
635
+ await super.startWatchdog();
636
+ await this.startDoubleSpendWatchdog();
637
+ }
638
+
639
+ async init(): Promise<void> {
640
+ await this.storageManager.loadData(FromBtcTrustedSwap);
641
+ this.listenToTxns();
642
+ await PluginManager.serviceInitialize(this);
643
+ }
644
+
645
+ getInfoData(): any {
646
+ return {};
647
+ }
648
+
649
+ protected processClaimEvent(chainIdentifier: string, event: ClaimEvent<SwapData>): Promise<void> {
650
+ return Promise.resolve(undefined);
651
+ }
652
+
653
+ protected processInitializeEvent(chainIdentifier: string, event: InitializeEvent<SwapData>): Promise<void> {
654
+ return Promise.resolve(undefined);
655
+ }
656
+
657
+ protected processRefundEvent(chainIdentifier: string, event: RefundEvent<SwapData>): Promise<void> {
658
+ return Promise.resolve(undefined);
659
+ }
660
+
661
+ }