@atomiqlabs/lp-lib 15.0.13 → 15.0.14

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 (163) 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 +40 -40
  5. package/dist/index.js +56 -56
  6. package/dist/info/InfoHandler.d.ts +17 -17
  7. package/dist/info/InfoHandler.js +58 -61
  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 +18 -18
  23. package/dist/storagemanager/IntermediaryStorageManager.js +104 -104
  24. package/dist/storagemanager/StorageManager.d.ts +12 -12
  25. package/dist/storagemanager/StorageManager.js +57 -57
  26. package/dist/swaps/SwapHandler.d.ts +153 -156
  27. package/dist/swaps/SwapHandler.js +157 -163
  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 +172 -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 +101 -101
  45. package/dist/swaps/escrow/FromBtcBaseSwapHandler.js +207 -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 -675
  56. package/dist/swaps/escrow/frombtcln_abstract/FromBtcLnSwapAbs.d.ts +32 -32
  57. package/dist/swaps/escrow/frombtcln_abstract/FromBtcLnSwapAbs.js +88 -88
  58. package/dist/swaps/escrow/tobtc_abstract/ToBtcAbs.d.ts +171 -171
  59. package/dist/swaps/escrow/tobtc_abstract/ToBtcAbs.js +718 -718
  60. package/dist/swaps/escrow/tobtc_abstract/ToBtcSwapAbs.d.ts +28 -28
  61. package/dist/swaps/escrow/tobtc_abstract/ToBtcSwapAbs.js +64 -64
  62. package/dist/swaps/escrow/tobtcln_abstract/ToBtcLnAbs.d.ts +177 -177
  63. package/dist/swaps/escrow/tobtcln_abstract/ToBtcLnAbs.js +863 -863
  64. package/dist/swaps/escrow/tobtcln_abstract/ToBtcLnSwapAbs.d.ts +24 -24
  65. package/dist/swaps/escrow/tobtcln_abstract/ToBtcLnSwapAbs.js +58 -58
  66. package/dist/swaps/spv_vault_swap/SpvVault.d.ts +45 -45
  67. package/dist/swaps/spv_vault_swap/SpvVault.js +145 -145
  68. package/dist/swaps/spv_vault_swap/SpvVaultSwap.d.ts +68 -68
  69. package/dist/swaps/spv_vault_swap/SpvVaultSwap.js +158 -158
  70. package/dist/swaps/spv_vault_swap/SpvVaultSwapHandler.d.ts +68 -68
  71. package/dist/swaps/spv_vault_swap/SpvVaultSwapHandler.js +528 -528
  72. package/dist/swaps/spv_vault_swap/SpvVaults.d.ts +68 -68
  73. package/dist/swaps/spv_vault_swap/SpvVaults.js +454 -454
  74. package/dist/swaps/trusted/frombtc_trusted/FromBtcTrusted.d.ts +51 -51
  75. package/dist/swaps/trusted/frombtc_trusted/FromBtcTrusted.js +650 -650
  76. package/dist/swaps/trusted/frombtc_trusted/FromBtcTrustedSwap.d.ts +52 -52
  77. package/dist/swaps/trusted/frombtc_trusted/FromBtcTrustedSwap.js +118 -118
  78. package/dist/swaps/trusted/frombtcln_trusted/FromBtcLnTrusted.d.ts +76 -76
  79. package/dist/swaps/trusted/frombtcln_trusted/FromBtcLnTrusted.js +493 -495
  80. package/dist/swaps/trusted/frombtcln_trusted/FromBtcLnTrustedSwap.d.ts +34 -34
  81. package/dist/swaps/trusted/frombtcln_trusted/FromBtcLnTrustedSwap.js +81 -81
  82. package/dist/utils/BitcoinUtils.d.ts +4 -4
  83. package/dist/utils/BitcoinUtils.js +61 -61
  84. package/dist/utils/Utils.d.ts +29 -29
  85. package/dist/utils/Utils.js +88 -88
  86. package/dist/utils/paramcoders/IParamReader.d.ts +5 -5
  87. package/dist/utils/paramcoders/IParamReader.js +2 -2
  88. package/dist/utils/paramcoders/IParamWriter.d.ts +4 -4
  89. package/dist/utils/paramcoders/IParamWriter.js +2 -2
  90. package/dist/utils/paramcoders/LegacyParamEncoder.d.ts +10 -10
  91. package/dist/utils/paramcoders/LegacyParamEncoder.js +22 -22
  92. package/dist/utils/paramcoders/ParamDecoder.d.ts +25 -25
  93. package/dist/utils/paramcoders/ParamDecoder.js +222 -222
  94. package/dist/utils/paramcoders/ParamEncoder.d.ts +9 -9
  95. package/dist/utils/paramcoders/ParamEncoder.js +22 -22
  96. package/dist/utils/paramcoders/SchemaVerifier.d.ts +21 -21
  97. package/dist/utils/paramcoders/SchemaVerifier.js +84 -84
  98. package/dist/utils/paramcoders/server/ServerParamDecoder.d.ts +8 -8
  99. package/dist/utils/paramcoders/server/ServerParamDecoder.js +105 -105
  100. package/dist/utils/paramcoders/server/ServerParamEncoder.d.ts +11 -11
  101. package/dist/utils/paramcoders/server/ServerParamEncoder.js +65 -65
  102. package/dist/wallets/IBitcoinWallet.d.ts +67 -67
  103. package/dist/wallets/IBitcoinWallet.js +2 -2
  104. package/dist/wallets/ILightningWallet.d.ts +117 -117
  105. package/dist/wallets/ILightningWallet.js +37 -37
  106. package/dist/wallets/ISpvVaultSigner.d.ts +7 -7
  107. package/dist/wallets/ISpvVaultSigner.js +2 -2
  108. package/dist/wallets/ISpvVaultWallet.d.ts +42 -42
  109. package/dist/wallets/ISpvVaultWallet.js +2 -2
  110. package/package.json +36 -36
  111. package/src/fees/IBtcFeeEstimator.ts +6 -6
  112. package/src/index.ts +51 -51
  113. package/src/info/InfoHandler.ts +100 -106
  114. package/src/plugins/IPlugin.ts +174 -174
  115. package/src/plugins/PluginManager.ts +354 -354
  116. package/src/prices/BinanceSwapPrice.ts +113 -113
  117. package/src/prices/CoinGeckoSwapPrice.ts +87 -87
  118. package/src/prices/ISwapPrice.ts +88 -88
  119. package/src/prices/OKXSwapPrice.ts +113 -113
  120. package/src/storage/IIntermediaryStorage.ts +19 -19
  121. package/src/storagemanager/IntermediaryStorageManager.ts +109 -109
  122. package/src/storagemanager/StorageManager.ts +68 -68
  123. package/src/swaps/SwapHandler.ts +272 -280
  124. package/src/swaps/SwapHandlerSwap.ts +141 -141
  125. package/src/swaps/assertions/AmountAssertions.ts +77 -77
  126. package/src/swaps/assertions/FromBtcAmountAssertions.ts +238 -238
  127. package/src/swaps/assertions/LightningAssertions.ts +103 -103
  128. package/src/swaps/assertions/ToBtcAmountAssertions.ts +203 -203
  129. package/src/swaps/escrow/EscrowHandler.ts +179 -179
  130. package/src/swaps/escrow/EscrowHandlerSwap.ts +86 -86
  131. package/src/swaps/escrow/FromBtcBaseSwap.ts +38 -38
  132. package/src/swaps/escrow/FromBtcBaseSwapHandler.ts +283 -283
  133. package/src/swaps/escrow/ToBtcBaseSwap.ts +85 -85
  134. package/src/swaps/escrow/ToBtcBaseSwapHandler.ts +129 -129
  135. package/src/swaps/escrow/frombtc_abstract/FromBtcAbs.ts +452 -452
  136. package/src/swaps/escrow/frombtc_abstract/FromBtcSwapAbs.ts +61 -61
  137. package/src/swaps/escrow/frombtcln_abstract/FromBtcLnAbs.ts +855 -856
  138. package/src/swaps/escrow/frombtcln_abstract/FromBtcLnSwapAbs.ts +137 -137
  139. package/src/swaps/escrow/tobtc_abstract/ToBtcAbs.ts +890 -890
  140. package/src/swaps/escrow/tobtc_abstract/ToBtcSwapAbs.ts +108 -108
  141. package/src/swaps/escrow/tobtcln_abstract/ToBtcLnAbs.ts +1112 -1112
  142. package/src/swaps/escrow/tobtcln_abstract/ToBtcLnSwapAbs.ts +80 -80
  143. package/src/swaps/spv_vault_swap/SpvVault.ts +178 -178
  144. package/src/swaps/spv_vault_swap/SpvVaultSwap.ts +228 -228
  145. package/src/swaps/spv_vault_swap/SpvVaultSwapHandler.ts +671 -671
  146. package/src/swaps/spv_vault_swap/SpvVaults.ts +526 -526
  147. package/src/swaps/trusted/frombtc_trusted/FromBtcTrusted.ts +747 -747
  148. package/src/swaps/trusted/frombtc_trusted/FromBtcTrustedSwap.ts +185 -185
  149. package/src/swaps/trusted/frombtcln_trusted/FromBtcLnTrusted.ts +591 -592
  150. package/src/swaps/trusted/frombtcln_trusted/FromBtcLnTrustedSwap.ts +121 -121
  151. package/src/utils/BitcoinUtils.ts +59 -59
  152. package/src/utils/Utils.ts +102 -102
  153. package/src/utils/paramcoders/IParamReader.ts +7 -7
  154. package/src/utils/paramcoders/IParamWriter.ts +8 -8
  155. package/src/utils/paramcoders/LegacyParamEncoder.ts +27 -27
  156. package/src/utils/paramcoders/ParamDecoder.ts +218 -218
  157. package/src/utils/paramcoders/ParamEncoder.ts +29 -29
  158. package/src/utils/paramcoders/SchemaVerifier.ts +96 -96
  159. package/src/utils/paramcoders/server/ServerParamDecoder.ts +115 -115
  160. package/src/utils/paramcoders/server/ServerParamEncoder.ts +75 -75
  161. package/src/wallets/IBitcoinWallet.ts +68 -68
  162. package/src/wallets/ILightningWallet.ts +178 -178
  163. package/src/wallets/ISpvVaultSigner.ts +10 -10
@@ -1,718 +1,718 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.ToBtcAbs = void 0;
4
- const ToBtcSwapAbs_1 = require("./ToBtcSwapAbs");
5
- const SwapHandler_1 = require("../../SwapHandler");
6
- const base_1 = require("@atomiqlabs/base");
7
- const Utils_1 = require("../../../utils/Utils");
8
- const PluginManager_1 = require("../../../plugins/PluginManager");
9
- const crypto_1 = require("crypto");
10
- const SchemaVerifier_1 = require("../../../utils/paramcoders/SchemaVerifier");
11
- const ServerParamDecoder_1 = require("../../../utils/paramcoders/server/ServerParamDecoder");
12
- const ToBtcBaseSwapHandler_1 = require("../ToBtcBaseSwapHandler");
13
- const promise_queue_ts_1 = require("promise-queue-ts");
14
- const BitcoinUtils_1 = require("../../../utils/BitcoinUtils");
15
- const OUTPUT_SCRIPT_MAX_LENGTH = 200;
16
- /**
17
- * Handler for to BTC swaps, utilizing PTLCs (proof-time locked contracts) using btc relay (on-chain bitcoin SPV)
18
- */
19
- class ToBtcAbs extends ToBtcBaseSwapHandler_1.ToBtcBaseSwapHandler {
20
- constructor(storageDirectory, path, chainData, bitcoin, swapPricing, bitcoinRpc, config) {
21
- super(storageDirectory, path, chainData, swapPricing, config);
22
- this.type = SwapHandler_1.SwapHandlerType.TO_BTC;
23
- this.swapType = base_1.ChainSwapType.CHAIN_NONCED;
24
- this.activeSubscriptions = {};
25
- this.sendBtcQueue = new promise_queue_ts_1.PromiseQueue();
26
- this.bitcoinRpc = bitcoinRpc;
27
- this.bitcoin = bitcoin;
28
- this.config = config;
29
- }
30
- /**
31
- * Returns the payment hash of the swap, takes swap nonce into account. Payment hash is chain-specific.
32
- *
33
- * @param chainIdentifier
34
- * @param address
35
- * @param confirmations
36
- * @param nonce
37
- * @param amount
38
- */
39
- getHash(chainIdentifier, address, confirmations, nonce, amount) {
40
- const parsedOutputScript = this.bitcoin.toOutputScript(address);
41
- const { swapContract } = this.getChain(chainIdentifier);
42
- return swapContract.getHashForOnchain(parsedOutputScript, amount, confirmations, nonce);
43
- }
44
- /**
45
- * Tries to claim the swap after our transaction was confirmed
46
- *
47
- * @param tx
48
- * @param swap
49
- * @param vout
50
- */
51
- async tryClaimSwap(tx, swap, vout) {
52
- const { swapContract, signer } = this.getChain(swap.chainIdentifier);
53
- const blockHeader = await this.bitcoinRpc.getBlockHeader(tx.blockhash);
54
- //Set flag that we are sending the transaction already, so we don't end up with race condition
55
- const unlock = swap.lock(swapContract.claimWithTxDataTimeout);
56
- if (unlock == null)
57
- return false;
58
- try {
59
- this.swapLogger.debug(swap, "tryClaimSwap(): initiate claim of swap, height: " + blockHeader.getHeight() + " utxo: " + tx.txid + ":" + vout);
60
- const result = await swapContract.claimWithTxData(signer, swap.data, { ...tx, height: blockHeader.getHeight() }, swap.requiredConfirmations, vout, null, null, false, {
61
- waitForConfirmation: true
62
- });
63
- this.swapLogger.info(swap, "tryClaimSwap(): swap claimed successfully, height: " + blockHeader.getHeight() + " utxo: " + tx.txid + ":" + vout + " address: " + swap.address);
64
- if (swap.metadata != null)
65
- swap.metadata.times.txClaimed = Date.now();
66
- unlock();
67
- return true;
68
- }
69
- catch (e) {
70
- this.swapLogger.error(swap, "tryClaimSwap(): error occurred claiming swap, height: " + blockHeader.getHeight() + " utxo: " + tx.txid + ":" + vout + " address: " + swap.address, e);
71
- return false;
72
- }
73
- }
74
- async processPastSwap(swap) {
75
- const { swapContract, signer } = this.getChain(swap.chainIdentifier);
76
- if (swap.state === ToBtcSwapAbs_1.ToBtcSwapState.SAVED) {
77
- const isSignatureExpired = await swapContract.isInitAuthorizationExpired(swap.data, swap);
78
- if (isSignatureExpired) {
79
- const isCommitted = await swapContract.isCommited(swap.data);
80
- if (!isCommitted) {
81
- this.swapLogger.info(swap, "processPastSwap(state=SAVED): authorization expired & swap not committed, cancelling swap, address: " + swap.address);
82
- await this.removeSwapData(swap, ToBtcSwapAbs_1.ToBtcSwapState.CANCELED);
83
- }
84
- else {
85
- this.swapLogger.info(swap, "processPastSwap(state=SAVED): swap committed (detected from processPastSwap), address: " + swap.address);
86
- await swap.setState(ToBtcSwapAbs_1.ToBtcSwapState.COMMITED);
87
- await this.saveSwapData(swap);
88
- }
89
- return;
90
- }
91
- }
92
- if (swap.state === ToBtcSwapAbs_1.ToBtcSwapState.NON_PAYABLE || swap.state === ToBtcSwapAbs_1.ToBtcSwapState.SAVED) {
93
- if (await swapContract.isExpired(signer.getAddress(), swap.data)) {
94
- this.swapLogger.info(swap, "processPastSwap(state=NON_PAYABLE|SAVED): swap expired, cancelling, address: " + swap.address);
95
- await this.removeSwapData(swap, ToBtcSwapAbs_1.ToBtcSwapState.CANCELED);
96
- return;
97
- }
98
- }
99
- //Sanity check for sent swaps
100
- if (swap.state === ToBtcSwapAbs_1.ToBtcSwapState.BTC_SENT) {
101
- const isCommited = await swapContract.isCommited(swap.data);
102
- if (!isCommited) {
103
- const status = await swapContract.getCommitStatus(signer.getAddress(), swap.data);
104
- if (status.type === base_1.SwapCommitStateType.PAID) {
105
- this.swapLogger.info(swap, "processPastSwap(state=BTC_SENT): swap claimed (detected from processPastSwap), address: " + swap.address);
106
- this.unsubscribePayment(swap);
107
- swap.txIds ?? (swap.txIds = {});
108
- swap.txIds.claim = await status.getClaimTxId();
109
- await this.removeSwapData(swap, ToBtcSwapAbs_1.ToBtcSwapState.CLAIMED);
110
- }
111
- else if (status.type === base_1.SwapCommitStateType.EXPIRED) {
112
- this.swapLogger.warn(swap, "processPastSwap(state=BTC_SENT): swap expired, but bitcoin was probably already sent, txId: " + swap.txId + " address: " + swap.address);
113
- this.unsubscribePayment(swap);
114
- swap.txIds ?? (swap.txIds = {});
115
- swap.txIds.refund = status.getRefundTxId == null ? null : await status.getRefundTxId();
116
- await this.removeSwapData(swap, ToBtcSwapAbs_1.ToBtcSwapState.REFUNDED);
117
- }
118
- return;
119
- }
120
- }
121
- if (swap.state === ToBtcSwapAbs_1.ToBtcSwapState.COMMITED || swap.state === ToBtcSwapAbs_1.ToBtcSwapState.BTC_SENDING || swap.state === ToBtcSwapAbs_1.ToBtcSwapState.BTC_SENT) {
122
- await this.processInitialized(swap);
123
- return;
124
- }
125
- }
126
- /**
127
- * Checks past swaps, deletes ones that are already expired.
128
- */
129
- async processPastSwaps() {
130
- const queriedData = await this.storageManager.query([
131
- {
132
- key: "state",
133
- values: [
134
- ToBtcSwapAbs_1.ToBtcSwapState.SAVED,
135
- ToBtcSwapAbs_1.ToBtcSwapState.NON_PAYABLE,
136
- ToBtcSwapAbs_1.ToBtcSwapState.COMMITED,
137
- ToBtcSwapAbs_1.ToBtcSwapState.BTC_SENDING,
138
- ToBtcSwapAbs_1.ToBtcSwapState.BTC_SENT,
139
- ]
140
- }
141
- ]);
142
- for (let { obj: swap } of queriedData) {
143
- await this.processPastSwap(swap);
144
- }
145
- }
146
- async processBtcTx(swap, tx) {
147
- tx.confirmations = tx.confirmations || 0;
148
- //Check transaction has enough confirmations
149
- const hasEnoughConfirmations = tx.confirmations >= swap.requiredConfirmations;
150
- if (!hasEnoughConfirmations) {
151
- return false;
152
- }
153
- this.swapLogger.debug(swap, "processBtcTx(): address: " + swap.address + " amount: " + swap.amount.toString(10) + " btcTx: " + tx);
154
- //Search for required transaction output (vout)
155
- const outputScript = this.bitcoin.toOutputScript(swap.address);
156
- const vout = tx.outs.find(e => BigInt(e.value) === swap.amount && Buffer.from(e.scriptPubKey.hex, "hex").equals(outputScript));
157
- if (vout == null) {
158
- this.swapLogger.warn(swap, "processBtcTx(): cannot find correct vout," +
159
- " required output script: " + outputScript.toString("hex") +
160
- " required amount: " + swap.amount.toString(10) +
161
- " vouts: ", tx.outs);
162
- return false;
163
- }
164
- if (swap.metadata != null)
165
- swap.metadata.times.payTxConfirmed = Date.now();
166
- const success = await this.tryClaimSwap(tx, swap, vout.n);
167
- return success;
168
- }
169
- /**
170
- * Checks active sent out bitcoin transactions
171
- */
172
- async processBtcTxs() {
173
- const unsubscribeSwaps = [];
174
- for (let txId in this.activeSubscriptions) {
175
- const swap = this.activeSubscriptions[txId];
176
- //TODO: RBF the transaction if it's already taking too long to confirm
177
- try {
178
- let tx = await this.bitcoin.getWalletTransaction(txId);
179
- if (tx == null)
180
- continue;
181
- if (await this.processBtcTx(swap, tx)) {
182
- this.swapLogger.info(swap, "processBtcTxs(): swap claimed successfully, txId: " + tx.txid + " address: " + swap.address);
183
- unsubscribeSwaps.push(swap);
184
- }
185
- }
186
- catch (e) {
187
- this.swapLogger.error(swap, "processBtcTxs(): error processing btc transaction", e);
188
- }
189
- }
190
- unsubscribeSwaps.forEach(swap => {
191
- this.unsubscribePayment(swap);
192
- });
193
- }
194
- /**
195
- * Subscribes to and periodically checks txId used to send out funds for the swap for enough confirmations
196
- *
197
- * @param payment
198
- */
199
- subscribeToPayment(payment) {
200
- this.swapLogger.info(payment, "subscribeToPayment(): subscribing to swap, txId: " + payment.txId + " address: " + payment.address);
201
- this.activeSubscriptions[payment.txId] = payment;
202
- }
203
- unsubscribePayment(payment) {
204
- if (payment.txId != null) {
205
- if (this.activeSubscriptions[payment.txId] != null) {
206
- this.swapLogger.info(payment, "unsubscribePayment(): unsubscribing swap, txId: " + payment.txId + " address: " + payment.address);
207
- delete this.activeSubscriptions[payment.txId];
208
- }
209
- }
210
- }
211
- /**
212
- * Checks if expiry time on the swap leaves us enough room to send a transaction and for the transaction to confirm
213
- *
214
- * @param swap
215
- * @private
216
- * @throws DefinedRuntimeError will throw an error in case there isn't enough time for us to send a BTC payout tx
217
- */
218
- checkExpiresTooSoon(swap) {
219
- const currentTimestamp = BigInt(Math.floor(Date.now() / 1000));
220
- const tsDelta = swap.data.getExpiry() - currentTimestamp;
221
- const minRequiredCLTV = this.getExpiryFromCLTV(swap.preferedConfirmationTarget, swap.requiredConfirmations);
222
- const hasRequiredCLTVDelta = tsDelta >= minRequiredCLTV;
223
- if (!hasRequiredCLTVDelta)
224
- throw {
225
- code: 90001,
226
- msg: "TS delta too low",
227
- data: {
228
- required: minRequiredCLTV.toString(10),
229
- actual: tsDelta.toString(10)
230
- }
231
- };
232
- }
233
- /**
234
- * Checks if the actual fee for the swap is no higher than the quoted estimate
235
- *
236
- * @param quotedSatsPerVbyte
237
- * @param actualSatsPerVbyte
238
- * @private
239
- * @throws DefinedRuntimeError will throw an error in case the actual fee is higher than quoted fee
240
- */
241
- checkCalculatedTxFee(quotedSatsPerVbyte, actualSatsPerVbyte) {
242
- const swapPaysEnoughNetworkFee = quotedSatsPerVbyte >= actualSatsPerVbyte;
243
- if (!swapPaysEnoughNetworkFee)
244
- throw {
245
- code: 90003,
246
- msg: "Fee changed too much!",
247
- data: {
248
- quotedFee: quotedSatsPerVbyte.toString(10),
249
- actualFee: actualSatsPerVbyte.toString(10)
250
- }
251
- };
252
- }
253
- /**
254
- * Sends a bitcoin transaction to payout BTC for a swap
255
- *
256
- * @param swap
257
- * @private
258
- * @throws DefinedRuntimeError will throw an error in case the payment cannot be initiated
259
- */
260
- sendBitcoinPayment(swap) {
261
- //Make sure that bitcoin payouts are processed sequentially to avoid race conditions between multiple payouts,
262
- // e.g. that 2 payouts share the same input and would effectively double-spend each other
263
- return this.sendBtcQueue.enqueue(async () => {
264
- //Run checks
265
- this.checkExpiresTooSoon(swap);
266
- if (swap.metadata != null)
267
- swap.metadata.times.payCLTVChecked = Date.now();
268
- const satsPerVbyte = await this.bitcoin.getFeeRate();
269
- this.checkCalculatedTxFee(swap.satsPerVbyte, BigInt(satsPerVbyte));
270
- if (swap.metadata != null)
271
- swap.metadata.times.payChainFee = Date.now();
272
- const signResult = await this.bitcoin.getSignedTransaction(swap.address, Number(swap.amount), satsPerVbyte, swap.nonce, Number(swap.satsPerVbyte));
273
- if (signResult == null)
274
- throw {
275
- code: 90002,
276
- msg: "Failed to create signed transaction (not enough funds?)"
277
- };
278
- if (swap.metadata != null)
279
- swap.metadata.times.paySignPSBT = Date.now();
280
- try {
281
- this.swapLogger.debug(swap, "sendBitcoinPayment(): signed raw transaction: " + signResult.raw);
282
- swap.txId = signResult.tx.id;
283
- swap.btcRawTx = signResult.raw;
284
- swap.setRealNetworkFee(BigInt(signResult.networkFee));
285
- swap.sending = true;
286
- await swap.setState(ToBtcSwapAbs_1.ToBtcSwapState.BTC_SENDING);
287
- await this.saveSwapData(swap);
288
- await this.bitcoin.sendRawTransaction(signResult.raw);
289
- swap.sending = false;
290
- }
291
- catch (e) {
292
- swap.sending = false;
293
- throw e;
294
- }
295
- if (swap.metadata != null)
296
- swap.metadata.times.payTxSent = Date.now();
297
- this.swapLogger.info(swap, "sendBitcoinPayment(): btc transaction generated, signed & broadcasted, txId: " + swap.txId + " address: " + swap.address);
298
- await swap.setState(ToBtcSwapAbs_1.ToBtcSwapState.BTC_SENT);
299
- await this.saveSwapData(swap);
300
- });
301
- }
302
- /**
303
- * Called after swap was successfully committed, will check if bitcoin tx is already sent, if not tries to send it and subscribes to it
304
- *
305
- * @param swap
306
- */
307
- async processInitialized(swap) {
308
- if (swap.state === ToBtcSwapAbs_1.ToBtcSwapState.BTC_SENDING) {
309
- if (swap.sending)
310
- return;
311
- //Bitcoin transaction was signed (maybe also sent)
312
- const tx = await (0, BitcoinUtils_1.checkTransactionReplaced)(swap.txId, swap.btcRawTx, this.bitcoinRpc);
313
- const isTxSent = tx != null;
314
- if (!isTxSent) {
315
- //Reset the state to COMMITED
316
- this.swapLogger.info(swap, "processInitialized(state=BTC_SENDING): btc transaction not found, resetting to COMMITED state, txId: " + swap.txId + " address: " + swap.address);
317
- await swap.setState(ToBtcSwapAbs_1.ToBtcSwapState.COMMITED);
318
- }
319
- else {
320
- this.swapLogger.info(swap, "processInitialized(state=BTC_SENDING): btc transaction found, advancing to BTC_SENT state, txId: " + swap.txId + " address: " + swap.address);
321
- await swap.setState(ToBtcSwapAbs_1.ToBtcSwapState.BTC_SENT);
322
- await this.saveSwapData(swap);
323
- }
324
- }
325
- if (swap.state === ToBtcSwapAbs_1.ToBtcSwapState.SAVED) {
326
- this.swapLogger.info(swap, "processInitialized(state=SAVED): advancing to COMMITED state, address: " + swap.address);
327
- await swap.setState(ToBtcSwapAbs_1.ToBtcSwapState.COMMITED);
328
- await this.saveSwapData(swap);
329
- }
330
- if (swap.state === ToBtcSwapAbs_1.ToBtcSwapState.COMMITED) {
331
- const unlock = swap.lock(60);
332
- if (unlock == null)
333
- return;
334
- this.swapLogger.debug(swap, "processInitialized(state=COMMITED): sending bitcoin transaction, address: " + swap.address);
335
- try {
336
- await this.sendBitcoinPayment(swap);
337
- this.swapLogger.info(swap, "processInitialized(state=COMMITED): btc transaction sent, address: " + swap.address);
338
- }
339
- catch (e) {
340
- if ((0, Utils_1.isDefinedRuntimeError)(e)) {
341
- this.swapLogger.error(swap, "processInitialized(state=COMMITED): setting state to NON_PAYABLE due to send bitcoin payment error", e);
342
- if (swap.metadata != null)
343
- swap.metadata.payError = e;
344
- await swap.setState(ToBtcSwapAbs_1.ToBtcSwapState.NON_PAYABLE);
345
- await this.saveSwapData(swap);
346
- }
347
- else {
348
- this.swapLogger.error(swap, "processInitialized(state=COMMITED): send bitcoin payment error", e);
349
- throw e;
350
- }
351
- }
352
- unlock();
353
- }
354
- if (swap.state === ToBtcSwapAbs_1.ToBtcSwapState.NON_PAYABLE)
355
- return;
356
- this.subscribeToPayment(swap);
357
- }
358
- async processInitializeEvent(chainIdentifier, swap, event) {
359
- this.swapLogger.info(swap, "SC: InitializeEvent: swap initialized by the client, address: " + swap.address);
360
- await this.processInitialized(swap);
361
- }
362
- async processClaimEvent(chainIdentifier, swap, event) {
363
- this.swapLogger.info(swap, "SC: ClaimEvent: swap successfully claimed to us, address: " + swap.address);
364
- //Also remove transaction from active subscriptions
365
- this.unsubscribePayment(swap);
366
- await this.removeSwapData(swap, ToBtcSwapAbs_1.ToBtcSwapState.CLAIMED);
367
- }
368
- async processRefundEvent(chainIdentifier, swap, event) {
369
- this.swapLogger.info(swap, "SC: RefundEvent: swap successfully refunded by the user, address: " + swap.address);
370
- //Also remove transaction from active subscriptions
371
- this.unsubscribePayment(swap);
372
- await this.removeSwapData(swap, ToBtcSwapAbs_1.ToBtcSwapState.REFUNDED);
373
- }
374
- /**
375
- * Returns required expiry delta for swap params
376
- *
377
- * @param confirmationTarget
378
- * @param confirmations
379
- */
380
- getExpiryFromCLTV(confirmationTarget, confirmations) {
381
- //Blocks = 10 + (confirmations + confirmationTarget)*2
382
- //Time = 3600 + (600*blocks*2)
383
- const cltv = this.config.minChainCltv + (BigInt(confirmations + confirmationTarget) * this.config.sendSafetyFactor);
384
- return this.config.gracePeriod + (this.config.bitcoinBlocktime * cltv * this.config.safetyFactor);
385
- }
386
- /**
387
- * Checks if the requested nonce is valid
388
- *
389
- * @param nonce
390
- * @throws {DefinedRuntimeError} will throw an error if the nonce is invalid
391
- */
392
- checkNonceValid(nonce) {
393
- if (nonce < 0 || nonce >= (2n ** 64n))
394
- throw {
395
- code: 20021,
396
- msg: "Invalid request body (nonce - cannot be parsed)"
397
- };
398
- const firstPart = nonce >> 24n;
399
- const maxAllowedValue = BigInt(Math.floor(Date.now() / 1000) - 600000000);
400
- if (firstPart > maxAllowedValue)
401
- throw {
402
- code: 20022,
403
- msg: "Invalid request body (nonce - too high)"
404
- };
405
- }
406
- /**
407
- * Checks if confirmation target is within configured bounds
408
- *
409
- * @param confirmationTarget
410
- * @throws {DefinedRuntimeError} will throw an error if the confirmationTarget is out of bounds
411
- */
412
- checkConfirmationTarget(confirmationTarget) {
413
- if (confirmationTarget > this.config.maxConfTarget)
414
- throw {
415
- code: 20023,
416
- msg: "Invalid request body (confirmationTarget - too high)"
417
- };
418
- if (confirmationTarget < this.config.minConfTarget)
419
- throw {
420
- code: 20024,
421
- msg: "Invalid request body (confirmationTarget - too low)"
422
- };
423
- }
424
- /**
425
- * Checks if the required confirmations are within configured bounds
426
- *
427
- * @param confirmations
428
- * @throws {DefinedRuntimeError} will throw an error if the confirmations are out of bounds
429
- */
430
- checkRequiredConfirmations(confirmations) {
431
- if (confirmations > this.config.maxConfirmations)
432
- throw {
433
- code: 20025,
434
- msg: "Invalid request body (confirmations - too high)"
435
- };
436
- if (confirmations < this.config.minConfirmations)
437
- throw {
438
- code: 20026,
439
- msg: "Invalid request body (confirmations - too low)"
440
- };
441
- }
442
- /**
443
- * Checks the validity of the provided address, also checks if the resulting output script isn't too large
444
- *
445
- * @param address
446
- * @throws {DefinedRuntimeError} will throw an error if the address is invalid
447
- */
448
- checkAddress(address) {
449
- let parsedOutputScript;
450
- try {
451
- parsedOutputScript = this.bitcoin.toOutputScript(address);
452
- }
453
- catch (e) {
454
- throw {
455
- code: 20031,
456
- msg: "Invalid request body (address - cannot be parsed)"
457
- };
458
- }
459
- if (parsedOutputScript.length > OUTPUT_SCRIPT_MAX_LENGTH)
460
- throw {
461
- code: 20032,
462
- msg: "Invalid request body (address's output script - too long)"
463
- };
464
- }
465
- /**
466
- * Checks if the swap is expired, taking into consideration on-chain time skew
467
- *
468
- * @param swap
469
- * @throws {DefinedRuntimeError} will throw an error if the swap is expired
470
- */
471
- async checkExpired(swap) {
472
- const { swapContract, signer } = this.getChain(swap.chainIdentifier);
473
- const isExpired = await swapContract.isExpired(signer.getAddress(), swap.data);
474
- if (isExpired)
475
- throw {
476
- _httpStatus: 200,
477
- code: 20010,
478
- msg: "Payment expired"
479
- };
480
- }
481
- /**
482
- * Checks & returns the network fee needed for a transaction
483
- *
484
- * @param address
485
- * @param amount
486
- * @throws {DefinedRuntimeError} will throw an error if there are not enough BTC funds
487
- */
488
- async checkAndGetNetworkFee(address, amount) {
489
- let chainFeeResp = await this.bitcoin.estimateFee(address, Number(amount), null, this.config.networkFeeMultiplier);
490
- const hasEnoughFunds = chainFeeResp != null;
491
- if (!hasEnoughFunds)
492
- throw {
493
- code: 20002,
494
- msg: "Not enough liquidity"
495
- };
496
- return {
497
- networkFee: BigInt(chainFeeResp.networkFee),
498
- satsPerVbyte: BigInt(chainFeeResp.satsPerVbyte)
499
- };
500
- }
501
- startRestServer(restServer) {
502
- restServer.use(this.path + "/payInvoice", (0, ServerParamDecoder_1.serverParamDecoder)(10 * 1000));
503
- restServer.post(this.path + "/payInvoice", (0, Utils_1.expressHandlerWrapper)(async (req, res) => {
504
- const metadata = { request: {}, times: {} };
505
- const chainIdentifier = req.query.chain ?? this.chains.default;
506
- const { swapContract, signer, chainInterface } = this.getChain(chainIdentifier);
507
- metadata.times.requestReceived = Date.now();
508
- /**
509
- *Sent initially:
510
- * address: string Bitcoin destination address
511
- * amount: string Amount to send (in satoshis)
512
- * confirmationTarget: number Desired confirmation target for the swap, how big of a fee should be assigned to TX
513
- * confirmations: number Required number of confirmations for us to claim the swap
514
- * nonce: string Nonce for the swap (used for replay protection)
515
- * token: string Desired token to use
516
- * offerer: string Address of the caller
517
- * exactIn: boolean Whether the swap should be an exact in instead of exact out swap
518
- *
519
- *Sent later:
520
- * feeRate: string Fee rate to use for the init signature
521
- */
522
- const parsedBody = await req.paramReader.getParams({
523
- address: SchemaVerifier_1.FieldTypeEnum.String,
524
- amount: SchemaVerifier_1.FieldTypeEnum.BigInt,
525
- confirmationTarget: SchemaVerifier_1.FieldTypeEnum.Number,
526
- confirmations: SchemaVerifier_1.FieldTypeEnum.Number,
527
- nonce: SchemaVerifier_1.FieldTypeEnum.BigInt,
528
- token: (val) => val != null &&
529
- typeof (val) === "string" &&
530
- this.isTokenSupported(chainIdentifier, val) ? val : null,
531
- offerer: (val) => val != null &&
532
- typeof (val) === "string" &&
533
- chainInterface.isValidAddress(val) ? val : null,
534
- exactIn: SchemaVerifier_1.FieldTypeEnum.BooleanOptional
535
- });
536
- if (parsedBody == null)
537
- throw {
538
- code: 20100,
539
- msg: "Invalid request body"
540
- };
541
- metadata.request = parsedBody;
542
- const requestedAmount = { input: !!parsedBody.exactIn, amount: parsedBody.amount, token: parsedBody.token };
543
- const request = {
544
- chainIdentifier,
545
- raw: req,
546
- parsed: parsedBody,
547
- metadata
548
- };
549
- const useToken = parsedBody.token;
550
- const responseStream = res.responseStream;
551
- this.checkNonceValid(parsedBody.nonce);
552
- this.checkConfirmationTarget(parsedBody.confirmationTarget);
553
- this.checkRequiredConfirmations(parsedBody.confirmations);
554
- this.checkAddress(parsedBody.address);
555
- await this.checkVaultInitialized(chainIdentifier, parsedBody.token);
556
- const fees = await this.AmountAssertions.preCheckToBtcAmounts(this.type, request, requestedAmount);
557
- metadata.times.requestChecked = Date.now();
558
- //Initialize abort controller for the parallel async operations
559
- const abortController = (0, Utils_1.getAbortController)(responseStream);
560
- const { pricePrefetchPromise, signDataPrefetchPromise } = this.getToBtcPrefetches(chainIdentifier, useToken, responseStream, abortController);
561
- const { amountBD, networkFeeData, totalInToken, swapFee, swapFeeInToken, networkFeeInToken } = await this.AmountAssertions.checkToBtcAmount(this.type, request, { ...requestedAmount, pricePrefetch: pricePrefetchPromise }, fees, async (amount) => {
562
- metadata.times.amountsChecked = Date.now();
563
- const resp = await this.checkAndGetNetworkFee(parsedBody.address, amount);
564
- this.logger.debug("checkToBtcAmount(): network fee calculated, amount: " + amount.toString(10) + " fee: " + resp.networkFee.toString(10));
565
- metadata.times.chainFeeCalculated = Date.now();
566
- return resp;
567
- }, abortController.signal);
568
- metadata.times.priceCalculated = Date.now();
569
- const paymentHash = this.getHash(chainIdentifier, parsedBody.address, parsedBody.confirmations, parsedBody.nonce, amountBD).toString("hex");
570
- //Add grace period another time, so the user has 1 hour to commit
571
- const expirySeconds = this.getExpiryFromCLTV(parsedBody.confirmationTarget, parsedBody.confirmations) + this.config.gracePeriod;
572
- const currentTimestamp = BigInt(Math.floor(Date.now() / 1000));
573
- const minRequiredExpiry = currentTimestamp + expirySeconds;
574
- const sequence = base_1.BigIntBufferUtils.fromBuffer((0, crypto_1.randomBytes)(8));
575
- const payObject = await swapContract.createSwapData(base_1.ChainSwapType.CHAIN_NONCED, parsedBody.offerer, signer.getAddress(), useToken, totalInToken, paymentHash, sequence, minRequiredExpiry, true, false, 0n, 0n);
576
- abortController.signal.throwIfAborted();
577
- metadata.times.swapCreated = Date.now();
578
- const sigData = await this.getToBtcSignatureData(chainIdentifier, payObject, req, abortController.signal, signDataPrefetchPromise);
579
- metadata.times.swapSigned = Date.now();
580
- const createdSwap = new ToBtcSwapAbs_1.ToBtcSwapAbs(chainIdentifier, parsedBody.address, amountBD, swapFee, swapFeeInToken, networkFeeData.networkFee, networkFeeInToken, networkFeeData.satsPerVbyte, parsedBody.nonce, parsedBody.confirmations, parsedBody.confirmationTarget);
581
- createdSwap.data = payObject;
582
- createdSwap.metadata = metadata;
583
- createdSwap.prefix = sigData.prefix;
584
- createdSwap.timeout = sigData.timeout;
585
- createdSwap.signature = sigData.signature;
586
- createdSwap.feeRate = sigData.feeRate;
587
- await PluginManager_1.PluginManager.swapCreate(createdSwap);
588
- await this.saveSwapData(createdSwap);
589
- this.swapLogger.info(createdSwap, "REST: /payInvoice: created swap address: " + createdSwap.address + " amount: " + amountBD.toString(10));
590
- await responseStream.writeParamsAndEnd({
591
- code: 20000,
592
- msg: "Success",
593
- data: {
594
- amount: amountBD.toString(10),
595
- address: signer.getAddress(),
596
- satsPervByte: networkFeeData.satsPerVbyte.toString(10),
597
- networkFee: networkFeeInToken.toString(10),
598
- swapFee: swapFeeInToken.toString(10),
599
- totalFee: (swapFeeInToken + networkFeeInToken).toString(10),
600
- total: totalInToken.toString(10),
601
- minRequiredExpiry: minRequiredExpiry.toString(10),
602
- data: payObject.serialize(),
603
- prefix: sigData.prefix,
604
- timeout: sigData.timeout,
605
- signature: sigData.signature
606
- }
607
- });
608
- }));
609
- const getRefundAuthorization = (0, Utils_1.expressHandlerWrapper)(async (req, res) => {
610
- /**
611
- * paymentHash: string Payment hash identifier of the swap
612
- * sequence: BN Sequence identifier of the swap
613
- */
614
- const parsedBody = (0, SchemaVerifier_1.verifySchema)({ ...req.body, ...req.query }, {
615
- paymentHash: (val) => val != null &&
616
- typeof (val) === "string" &&
617
- Utils_1.HEX_REGEX.test(val) ? val : null,
618
- sequence: SchemaVerifier_1.FieldTypeEnum.BigInt
619
- });
620
- if (parsedBody == null)
621
- throw {
622
- code: 20100,
623
- msg: "Invalid request body/query (paymentHash/sequence)"
624
- };
625
- this.checkSequence(parsedBody.sequence);
626
- const payment = await this.storageManager.getData(parsedBody.paymentHash, parsedBody.sequence);
627
- if (payment == null || payment.state === ToBtcSwapAbs_1.ToBtcSwapState.SAVED)
628
- throw {
629
- _httpStatus: 200,
630
- code: 20007,
631
- msg: "Payment not found"
632
- };
633
- await this.checkExpired(payment);
634
- if (payment.state === ToBtcSwapAbs_1.ToBtcSwapState.COMMITED)
635
- throw {
636
- _httpStatus: 200,
637
- code: 20008,
638
- msg: "Payment processing"
639
- };
640
- if (payment.state === ToBtcSwapAbs_1.ToBtcSwapState.BTC_SENT || payment.state === ToBtcSwapAbs_1.ToBtcSwapState.BTC_SENDING)
641
- throw {
642
- _httpStatus: 200,
643
- code: 20006,
644
- msg: "Already paid",
645
- data: {
646
- txId: payment.txId
647
- }
648
- };
649
- const { swapContract, signer } = this.getChain(payment.chainIdentifier);
650
- if (payment.state === ToBtcSwapAbs_1.ToBtcSwapState.NON_PAYABLE) {
651
- const isCommited = await swapContract.isCommited(payment.data);
652
- if (!isCommited)
653
- throw {
654
- code: 20005,
655
- msg: "Not committed"
656
- };
657
- const refundResponse = await swapContract.getRefundSignature(signer, payment.data, this.config.refundAuthorizationTimeout);
658
- //Double check the state after promise result
659
- if (payment.state !== ToBtcSwapAbs_1.ToBtcSwapState.NON_PAYABLE)
660
- throw {
661
- code: 20005,
662
- msg: "Not committed"
663
- };
664
- this.swapLogger.info(payment, "REST: /getRefundAuthorization: returning refund authorization, because swap is in NON_PAYABLE state, address: " + payment.address);
665
- res.status(200).json({
666
- code: 20000,
667
- msg: "Success",
668
- data: {
669
- address: signer.getAddress(),
670
- prefix: refundResponse.prefix,
671
- timeout: refundResponse.timeout,
672
- signature: refundResponse.signature
673
- }
674
- });
675
- return;
676
- }
677
- throw {
678
- _httpStatus: 500,
679
- code: 20009,
680
- msg: "Invalid payment status"
681
- };
682
- });
683
- restServer.post(this.path + "/getRefundAuthorization", getRefundAuthorization);
684
- restServer.get(this.path + "/getRefundAuthorization", getRefundAuthorization);
685
- this.logger.info("started at path: ", this.path);
686
- }
687
- /**
688
- * Starts watchdog checking sent bitcoin transactions
689
- */
690
- async startTxTimer() {
691
- let rerun;
692
- rerun = async () => {
693
- await this.processBtcTxs().catch(e => this.logger.error("startTxTimer(): call to processBtcTxs() errored", e));
694
- setTimeout(rerun, this.config.txCheckInterval);
695
- };
696
- await rerun();
697
- }
698
- async startWatchdog() {
699
- await super.startWatchdog();
700
- await this.startTxTimer();
701
- }
702
- async init() {
703
- await this.loadData(ToBtcSwapAbs_1.ToBtcSwapAbs);
704
- this.subscribeToEvents();
705
- await PluginManager_1.PluginManager.serviceInitialize(this);
706
- }
707
- getInfoData() {
708
- return {
709
- minCltv: Number(this.config.minChainCltv),
710
- minConfirmations: this.config.minConfirmations,
711
- maxConfirmations: this.config.maxConfirmations,
712
- minConfTarget: this.config.minConfTarget,
713
- maxConfTarget: this.config.maxConfTarget,
714
- maxOutputScriptLen: OUTPUT_SCRIPT_MAX_LENGTH
715
- };
716
- }
717
- }
718
- exports.ToBtcAbs = ToBtcAbs;
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.ToBtcAbs = void 0;
4
+ const ToBtcSwapAbs_1 = require("./ToBtcSwapAbs");
5
+ const SwapHandler_1 = require("../../SwapHandler");
6
+ const base_1 = require("@atomiqlabs/base");
7
+ const Utils_1 = require("../../../utils/Utils");
8
+ const PluginManager_1 = require("../../../plugins/PluginManager");
9
+ const crypto_1 = require("crypto");
10
+ const SchemaVerifier_1 = require("../../../utils/paramcoders/SchemaVerifier");
11
+ const ServerParamDecoder_1 = require("../../../utils/paramcoders/server/ServerParamDecoder");
12
+ const ToBtcBaseSwapHandler_1 = require("../ToBtcBaseSwapHandler");
13
+ const promise_queue_ts_1 = require("promise-queue-ts");
14
+ const BitcoinUtils_1 = require("../../../utils/BitcoinUtils");
15
+ const OUTPUT_SCRIPT_MAX_LENGTH = 200;
16
+ /**
17
+ * Handler for to BTC swaps, utilizing PTLCs (proof-time locked contracts) using btc relay (on-chain bitcoin SPV)
18
+ */
19
+ class ToBtcAbs extends ToBtcBaseSwapHandler_1.ToBtcBaseSwapHandler {
20
+ constructor(storageDirectory, path, chainData, bitcoin, swapPricing, bitcoinRpc, config) {
21
+ super(storageDirectory, path, chainData, swapPricing, config);
22
+ this.type = SwapHandler_1.SwapHandlerType.TO_BTC;
23
+ this.swapType = base_1.ChainSwapType.CHAIN_NONCED;
24
+ this.activeSubscriptions = {};
25
+ this.sendBtcQueue = new promise_queue_ts_1.PromiseQueue();
26
+ this.bitcoinRpc = bitcoinRpc;
27
+ this.bitcoin = bitcoin;
28
+ this.config = config;
29
+ }
30
+ /**
31
+ * Returns the payment hash of the swap, takes swap nonce into account. Payment hash is chain-specific.
32
+ *
33
+ * @param chainIdentifier
34
+ * @param address
35
+ * @param confirmations
36
+ * @param nonce
37
+ * @param amount
38
+ */
39
+ getHash(chainIdentifier, address, confirmations, nonce, amount) {
40
+ const parsedOutputScript = this.bitcoin.toOutputScript(address);
41
+ const { swapContract } = this.getChain(chainIdentifier);
42
+ return swapContract.getHashForOnchain(parsedOutputScript, amount, confirmations, nonce);
43
+ }
44
+ /**
45
+ * Tries to claim the swap after our transaction was confirmed
46
+ *
47
+ * @param tx
48
+ * @param swap
49
+ * @param vout
50
+ */
51
+ async tryClaimSwap(tx, swap, vout) {
52
+ const { swapContract, signer } = this.getChain(swap.chainIdentifier);
53
+ const blockHeader = await this.bitcoinRpc.getBlockHeader(tx.blockhash);
54
+ //Set flag that we are sending the transaction already, so we don't end up with race condition
55
+ const unlock = swap.lock(swapContract.claimWithTxDataTimeout);
56
+ if (unlock == null)
57
+ return false;
58
+ try {
59
+ this.swapLogger.debug(swap, "tryClaimSwap(): initiate claim of swap, height: " + blockHeader.getHeight() + " utxo: " + tx.txid + ":" + vout);
60
+ const result = await swapContract.claimWithTxData(signer, swap.data, { ...tx, height: blockHeader.getHeight() }, swap.requiredConfirmations, vout, null, null, false, {
61
+ waitForConfirmation: true
62
+ });
63
+ this.swapLogger.info(swap, "tryClaimSwap(): swap claimed successfully, height: " + blockHeader.getHeight() + " utxo: " + tx.txid + ":" + vout + " address: " + swap.address);
64
+ if (swap.metadata != null)
65
+ swap.metadata.times.txClaimed = Date.now();
66
+ unlock();
67
+ return true;
68
+ }
69
+ catch (e) {
70
+ this.swapLogger.error(swap, "tryClaimSwap(): error occurred claiming swap, height: " + blockHeader.getHeight() + " utxo: " + tx.txid + ":" + vout + " address: " + swap.address, e);
71
+ return false;
72
+ }
73
+ }
74
+ async processPastSwap(swap) {
75
+ const { swapContract, signer } = this.getChain(swap.chainIdentifier);
76
+ if (swap.state === ToBtcSwapAbs_1.ToBtcSwapState.SAVED) {
77
+ const isSignatureExpired = await swapContract.isInitAuthorizationExpired(swap.data, swap);
78
+ if (isSignatureExpired) {
79
+ const isCommitted = await swapContract.isCommited(swap.data);
80
+ if (!isCommitted) {
81
+ this.swapLogger.info(swap, "processPastSwap(state=SAVED): authorization expired & swap not committed, cancelling swap, address: " + swap.address);
82
+ await this.removeSwapData(swap, ToBtcSwapAbs_1.ToBtcSwapState.CANCELED);
83
+ }
84
+ else {
85
+ this.swapLogger.info(swap, "processPastSwap(state=SAVED): swap committed (detected from processPastSwap), address: " + swap.address);
86
+ await swap.setState(ToBtcSwapAbs_1.ToBtcSwapState.COMMITED);
87
+ await this.saveSwapData(swap);
88
+ }
89
+ return;
90
+ }
91
+ }
92
+ if (swap.state === ToBtcSwapAbs_1.ToBtcSwapState.NON_PAYABLE || swap.state === ToBtcSwapAbs_1.ToBtcSwapState.SAVED) {
93
+ if (await swapContract.isExpired(signer.getAddress(), swap.data)) {
94
+ this.swapLogger.info(swap, "processPastSwap(state=NON_PAYABLE|SAVED): swap expired, cancelling, address: " + swap.address);
95
+ await this.removeSwapData(swap, ToBtcSwapAbs_1.ToBtcSwapState.CANCELED);
96
+ return;
97
+ }
98
+ }
99
+ //Sanity check for sent swaps
100
+ if (swap.state === ToBtcSwapAbs_1.ToBtcSwapState.BTC_SENT) {
101
+ const isCommited = await swapContract.isCommited(swap.data);
102
+ if (!isCommited) {
103
+ const status = await swapContract.getCommitStatus(signer.getAddress(), swap.data);
104
+ if (status.type === base_1.SwapCommitStateType.PAID) {
105
+ this.swapLogger.info(swap, "processPastSwap(state=BTC_SENT): swap claimed (detected from processPastSwap), address: " + swap.address);
106
+ this.unsubscribePayment(swap);
107
+ swap.txIds ?? (swap.txIds = {});
108
+ swap.txIds.claim = await status.getClaimTxId();
109
+ await this.removeSwapData(swap, ToBtcSwapAbs_1.ToBtcSwapState.CLAIMED);
110
+ }
111
+ else if (status.type === base_1.SwapCommitStateType.EXPIRED) {
112
+ this.swapLogger.warn(swap, "processPastSwap(state=BTC_SENT): swap expired, but bitcoin was probably already sent, txId: " + swap.txId + " address: " + swap.address);
113
+ this.unsubscribePayment(swap);
114
+ swap.txIds ?? (swap.txIds = {});
115
+ swap.txIds.refund = status.getRefundTxId == null ? null : await status.getRefundTxId();
116
+ await this.removeSwapData(swap, ToBtcSwapAbs_1.ToBtcSwapState.REFUNDED);
117
+ }
118
+ return;
119
+ }
120
+ }
121
+ if (swap.state === ToBtcSwapAbs_1.ToBtcSwapState.COMMITED || swap.state === ToBtcSwapAbs_1.ToBtcSwapState.BTC_SENDING || swap.state === ToBtcSwapAbs_1.ToBtcSwapState.BTC_SENT) {
122
+ await this.processInitialized(swap);
123
+ return;
124
+ }
125
+ }
126
+ /**
127
+ * Checks past swaps, deletes ones that are already expired.
128
+ */
129
+ async processPastSwaps() {
130
+ const queriedData = await this.storageManager.query([
131
+ {
132
+ key: "state",
133
+ values: [
134
+ ToBtcSwapAbs_1.ToBtcSwapState.SAVED,
135
+ ToBtcSwapAbs_1.ToBtcSwapState.NON_PAYABLE,
136
+ ToBtcSwapAbs_1.ToBtcSwapState.COMMITED,
137
+ ToBtcSwapAbs_1.ToBtcSwapState.BTC_SENDING,
138
+ ToBtcSwapAbs_1.ToBtcSwapState.BTC_SENT,
139
+ ]
140
+ }
141
+ ]);
142
+ for (let { obj: swap } of queriedData) {
143
+ await this.processPastSwap(swap);
144
+ }
145
+ }
146
+ async processBtcTx(swap, tx) {
147
+ tx.confirmations = tx.confirmations || 0;
148
+ //Check transaction has enough confirmations
149
+ const hasEnoughConfirmations = tx.confirmations >= swap.requiredConfirmations;
150
+ if (!hasEnoughConfirmations) {
151
+ return false;
152
+ }
153
+ this.swapLogger.debug(swap, "processBtcTx(): address: " + swap.address + " amount: " + swap.amount.toString(10) + " btcTx: " + tx);
154
+ //Search for required transaction output (vout)
155
+ const outputScript = this.bitcoin.toOutputScript(swap.address);
156
+ const vout = tx.outs.find(e => BigInt(e.value) === swap.amount && Buffer.from(e.scriptPubKey.hex, "hex").equals(outputScript));
157
+ if (vout == null) {
158
+ this.swapLogger.warn(swap, "processBtcTx(): cannot find correct vout," +
159
+ " required output script: " + outputScript.toString("hex") +
160
+ " required amount: " + swap.amount.toString(10) +
161
+ " vouts: ", tx.outs);
162
+ return false;
163
+ }
164
+ if (swap.metadata != null)
165
+ swap.metadata.times.payTxConfirmed = Date.now();
166
+ const success = await this.tryClaimSwap(tx, swap, vout.n);
167
+ return success;
168
+ }
169
+ /**
170
+ * Checks active sent out bitcoin transactions
171
+ */
172
+ async processBtcTxs() {
173
+ const unsubscribeSwaps = [];
174
+ for (let txId in this.activeSubscriptions) {
175
+ const swap = this.activeSubscriptions[txId];
176
+ //TODO: RBF the transaction if it's already taking too long to confirm
177
+ try {
178
+ let tx = await this.bitcoin.getWalletTransaction(txId);
179
+ if (tx == null)
180
+ continue;
181
+ if (await this.processBtcTx(swap, tx)) {
182
+ this.swapLogger.info(swap, "processBtcTxs(): swap claimed successfully, txId: " + tx.txid + " address: " + swap.address);
183
+ unsubscribeSwaps.push(swap);
184
+ }
185
+ }
186
+ catch (e) {
187
+ this.swapLogger.error(swap, "processBtcTxs(): error processing btc transaction", e);
188
+ }
189
+ }
190
+ unsubscribeSwaps.forEach(swap => {
191
+ this.unsubscribePayment(swap);
192
+ });
193
+ }
194
+ /**
195
+ * Subscribes to and periodically checks txId used to send out funds for the swap for enough confirmations
196
+ *
197
+ * @param payment
198
+ */
199
+ subscribeToPayment(payment) {
200
+ this.swapLogger.info(payment, "subscribeToPayment(): subscribing to swap, txId: " + payment.txId + " address: " + payment.address);
201
+ this.activeSubscriptions[payment.txId] = payment;
202
+ }
203
+ unsubscribePayment(payment) {
204
+ if (payment.txId != null) {
205
+ if (this.activeSubscriptions[payment.txId] != null) {
206
+ this.swapLogger.info(payment, "unsubscribePayment(): unsubscribing swap, txId: " + payment.txId + " address: " + payment.address);
207
+ delete this.activeSubscriptions[payment.txId];
208
+ }
209
+ }
210
+ }
211
+ /**
212
+ * Checks if expiry time on the swap leaves us enough room to send a transaction and for the transaction to confirm
213
+ *
214
+ * @param swap
215
+ * @private
216
+ * @throws DefinedRuntimeError will throw an error in case there isn't enough time for us to send a BTC payout tx
217
+ */
218
+ checkExpiresTooSoon(swap) {
219
+ const currentTimestamp = BigInt(Math.floor(Date.now() / 1000));
220
+ const tsDelta = swap.data.getExpiry() - currentTimestamp;
221
+ const minRequiredCLTV = this.getExpiryFromCLTV(swap.preferedConfirmationTarget, swap.requiredConfirmations);
222
+ const hasRequiredCLTVDelta = tsDelta >= minRequiredCLTV;
223
+ if (!hasRequiredCLTVDelta)
224
+ throw {
225
+ code: 90001,
226
+ msg: "TS delta too low",
227
+ data: {
228
+ required: minRequiredCLTV.toString(10),
229
+ actual: tsDelta.toString(10)
230
+ }
231
+ };
232
+ }
233
+ /**
234
+ * Checks if the actual fee for the swap is no higher than the quoted estimate
235
+ *
236
+ * @param quotedSatsPerVbyte
237
+ * @param actualSatsPerVbyte
238
+ * @private
239
+ * @throws DefinedRuntimeError will throw an error in case the actual fee is higher than quoted fee
240
+ */
241
+ checkCalculatedTxFee(quotedSatsPerVbyte, actualSatsPerVbyte) {
242
+ const swapPaysEnoughNetworkFee = quotedSatsPerVbyte >= actualSatsPerVbyte;
243
+ if (!swapPaysEnoughNetworkFee)
244
+ throw {
245
+ code: 90003,
246
+ msg: "Fee changed too much!",
247
+ data: {
248
+ quotedFee: quotedSatsPerVbyte.toString(10),
249
+ actualFee: actualSatsPerVbyte.toString(10)
250
+ }
251
+ };
252
+ }
253
+ /**
254
+ * Sends a bitcoin transaction to payout BTC for a swap
255
+ *
256
+ * @param swap
257
+ * @private
258
+ * @throws DefinedRuntimeError will throw an error in case the payment cannot be initiated
259
+ */
260
+ sendBitcoinPayment(swap) {
261
+ //Make sure that bitcoin payouts are processed sequentially to avoid race conditions between multiple payouts,
262
+ // e.g. that 2 payouts share the same input and would effectively double-spend each other
263
+ return this.sendBtcQueue.enqueue(async () => {
264
+ //Run checks
265
+ this.checkExpiresTooSoon(swap);
266
+ if (swap.metadata != null)
267
+ swap.metadata.times.payCLTVChecked = Date.now();
268
+ const satsPerVbyte = await this.bitcoin.getFeeRate();
269
+ this.checkCalculatedTxFee(swap.satsPerVbyte, BigInt(satsPerVbyte));
270
+ if (swap.metadata != null)
271
+ swap.metadata.times.payChainFee = Date.now();
272
+ const signResult = await this.bitcoin.getSignedTransaction(swap.address, Number(swap.amount), satsPerVbyte, swap.nonce, Number(swap.satsPerVbyte));
273
+ if (signResult == null)
274
+ throw {
275
+ code: 90002,
276
+ msg: "Failed to create signed transaction (not enough funds?)"
277
+ };
278
+ if (swap.metadata != null)
279
+ swap.metadata.times.paySignPSBT = Date.now();
280
+ try {
281
+ this.swapLogger.debug(swap, "sendBitcoinPayment(): signed raw transaction: " + signResult.raw);
282
+ swap.txId = signResult.tx.id;
283
+ swap.btcRawTx = signResult.raw;
284
+ swap.setRealNetworkFee(BigInt(signResult.networkFee));
285
+ swap.sending = true;
286
+ await swap.setState(ToBtcSwapAbs_1.ToBtcSwapState.BTC_SENDING);
287
+ await this.saveSwapData(swap);
288
+ await this.bitcoin.sendRawTransaction(signResult.raw);
289
+ swap.sending = false;
290
+ }
291
+ catch (e) {
292
+ swap.sending = false;
293
+ throw e;
294
+ }
295
+ if (swap.metadata != null)
296
+ swap.metadata.times.payTxSent = Date.now();
297
+ this.swapLogger.info(swap, "sendBitcoinPayment(): btc transaction generated, signed & broadcasted, txId: " + swap.txId + " address: " + swap.address);
298
+ await swap.setState(ToBtcSwapAbs_1.ToBtcSwapState.BTC_SENT);
299
+ await this.saveSwapData(swap);
300
+ });
301
+ }
302
+ /**
303
+ * Called after swap was successfully committed, will check if bitcoin tx is already sent, if not tries to send it and subscribes to it
304
+ *
305
+ * @param swap
306
+ */
307
+ async processInitialized(swap) {
308
+ if (swap.state === ToBtcSwapAbs_1.ToBtcSwapState.BTC_SENDING) {
309
+ if (swap.sending)
310
+ return;
311
+ //Bitcoin transaction was signed (maybe also sent)
312
+ const tx = await (0, BitcoinUtils_1.checkTransactionReplaced)(swap.txId, swap.btcRawTx, this.bitcoinRpc);
313
+ const isTxSent = tx != null;
314
+ if (!isTxSent) {
315
+ //Reset the state to COMMITED
316
+ this.swapLogger.info(swap, "processInitialized(state=BTC_SENDING): btc transaction not found, resetting to COMMITED state, txId: " + swap.txId + " address: " + swap.address);
317
+ await swap.setState(ToBtcSwapAbs_1.ToBtcSwapState.COMMITED);
318
+ }
319
+ else {
320
+ this.swapLogger.info(swap, "processInitialized(state=BTC_SENDING): btc transaction found, advancing to BTC_SENT state, txId: " + swap.txId + " address: " + swap.address);
321
+ await swap.setState(ToBtcSwapAbs_1.ToBtcSwapState.BTC_SENT);
322
+ await this.saveSwapData(swap);
323
+ }
324
+ }
325
+ if (swap.state === ToBtcSwapAbs_1.ToBtcSwapState.SAVED) {
326
+ this.swapLogger.info(swap, "processInitialized(state=SAVED): advancing to COMMITED state, address: " + swap.address);
327
+ await swap.setState(ToBtcSwapAbs_1.ToBtcSwapState.COMMITED);
328
+ await this.saveSwapData(swap);
329
+ }
330
+ if (swap.state === ToBtcSwapAbs_1.ToBtcSwapState.COMMITED) {
331
+ const unlock = swap.lock(60);
332
+ if (unlock == null)
333
+ return;
334
+ this.swapLogger.debug(swap, "processInitialized(state=COMMITED): sending bitcoin transaction, address: " + swap.address);
335
+ try {
336
+ await this.sendBitcoinPayment(swap);
337
+ this.swapLogger.info(swap, "processInitialized(state=COMMITED): btc transaction sent, address: " + swap.address);
338
+ }
339
+ catch (e) {
340
+ if ((0, Utils_1.isDefinedRuntimeError)(e)) {
341
+ this.swapLogger.error(swap, "processInitialized(state=COMMITED): setting state to NON_PAYABLE due to send bitcoin payment error", e);
342
+ if (swap.metadata != null)
343
+ swap.metadata.payError = e;
344
+ await swap.setState(ToBtcSwapAbs_1.ToBtcSwapState.NON_PAYABLE);
345
+ await this.saveSwapData(swap);
346
+ }
347
+ else {
348
+ this.swapLogger.error(swap, "processInitialized(state=COMMITED): send bitcoin payment error", e);
349
+ throw e;
350
+ }
351
+ }
352
+ unlock();
353
+ }
354
+ if (swap.state === ToBtcSwapAbs_1.ToBtcSwapState.NON_PAYABLE)
355
+ return;
356
+ this.subscribeToPayment(swap);
357
+ }
358
+ async processInitializeEvent(chainIdentifier, swap, event) {
359
+ this.swapLogger.info(swap, "SC: InitializeEvent: swap initialized by the client, address: " + swap.address);
360
+ await this.processInitialized(swap);
361
+ }
362
+ async processClaimEvent(chainIdentifier, swap, event) {
363
+ this.swapLogger.info(swap, "SC: ClaimEvent: swap successfully claimed to us, address: " + swap.address);
364
+ //Also remove transaction from active subscriptions
365
+ this.unsubscribePayment(swap);
366
+ await this.removeSwapData(swap, ToBtcSwapAbs_1.ToBtcSwapState.CLAIMED);
367
+ }
368
+ async processRefundEvent(chainIdentifier, swap, event) {
369
+ this.swapLogger.info(swap, "SC: RefundEvent: swap successfully refunded by the user, address: " + swap.address);
370
+ //Also remove transaction from active subscriptions
371
+ this.unsubscribePayment(swap);
372
+ await this.removeSwapData(swap, ToBtcSwapAbs_1.ToBtcSwapState.REFUNDED);
373
+ }
374
+ /**
375
+ * Returns required expiry delta for swap params
376
+ *
377
+ * @param confirmationTarget
378
+ * @param confirmations
379
+ */
380
+ getExpiryFromCLTV(confirmationTarget, confirmations) {
381
+ //Blocks = 10 + (confirmations + confirmationTarget)*2
382
+ //Time = 3600 + (600*blocks*2)
383
+ const cltv = this.config.minChainCltv + (BigInt(confirmations + confirmationTarget) * this.config.sendSafetyFactor);
384
+ return this.config.gracePeriod + (this.config.bitcoinBlocktime * cltv * this.config.safetyFactor);
385
+ }
386
+ /**
387
+ * Checks if the requested nonce is valid
388
+ *
389
+ * @param nonce
390
+ * @throws {DefinedRuntimeError} will throw an error if the nonce is invalid
391
+ */
392
+ checkNonceValid(nonce) {
393
+ if (nonce < 0 || nonce >= (2n ** 64n))
394
+ throw {
395
+ code: 20021,
396
+ msg: "Invalid request body (nonce - cannot be parsed)"
397
+ };
398
+ const firstPart = nonce >> 24n;
399
+ const maxAllowedValue = BigInt(Math.floor(Date.now() / 1000) - 600000000);
400
+ if (firstPart > maxAllowedValue)
401
+ throw {
402
+ code: 20022,
403
+ msg: "Invalid request body (nonce - too high)"
404
+ };
405
+ }
406
+ /**
407
+ * Checks if confirmation target is within configured bounds
408
+ *
409
+ * @param confirmationTarget
410
+ * @throws {DefinedRuntimeError} will throw an error if the confirmationTarget is out of bounds
411
+ */
412
+ checkConfirmationTarget(confirmationTarget) {
413
+ if (confirmationTarget > this.config.maxConfTarget)
414
+ throw {
415
+ code: 20023,
416
+ msg: "Invalid request body (confirmationTarget - too high)"
417
+ };
418
+ if (confirmationTarget < this.config.minConfTarget)
419
+ throw {
420
+ code: 20024,
421
+ msg: "Invalid request body (confirmationTarget - too low)"
422
+ };
423
+ }
424
+ /**
425
+ * Checks if the required confirmations are within configured bounds
426
+ *
427
+ * @param confirmations
428
+ * @throws {DefinedRuntimeError} will throw an error if the confirmations are out of bounds
429
+ */
430
+ checkRequiredConfirmations(confirmations) {
431
+ if (confirmations > this.config.maxConfirmations)
432
+ throw {
433
+ code: 20025,
434
+ msg: "Invalid request body (confirmations - too high)"
435
+ };
436
+ if (confirmations < this.config.minConfirmations)
437
+ throw {
438
+ code: 20026,
439
+ msg: "Invalid request body (confirmations - too low)"
440
+ };
441
+ }
442
+ /**
443
+ * Checks the validity of the provided address, also checks if the resulting output script isn't too large
444
+ *
445
+ * @param address
446
+ * @throws {DefinedRuntimeError} will throw an error if the address is invalid
447
+ */
448
+ checkAddress(address) {
449
+ let parsedOutputScript;
450
+ try {
451
+ parsedOutputScript = this.bitcoin.toOutputScript(address);
452
+ }
453
+ catch (e) {
454
+ throw {
455
+ code: 20031,
456
+ msg: "Invalid request body (address - cannot be parsed)"
457
+ };
458
+ }
459
+ if (parsedOutputScript.length > OUTPUT_SCRIPT_MAX_LENGTH)
460
+ throw {
461
+ code: 20032,
462
+ msg: "Invalid request body (address's output script - too long)"
463
+ };
464
+ }
465
+ /**
466
+ * Checks if the swap is expired, taking into consideration on-chain time skew
467
+ *
468
+ * @param swap
469
+ * @throws {DefinedRuntimeError} will throw an error if the swap is expired
470
+ */
471
+ async checkExpired(swap) {
472
+ const { swapContract, signer } = this.getChain(swap.chainIdentifier);
473
+ const isExpired = await swapContract.isExpired(signer.getAddress(), swap.data);
474
+ if (isExpired)
475
+ throw {
476
+ _httpStatus: 200,
477
+ code: 20010,
478
+ msg: "Payment expired"
479
+ };
480
+ }
481
+ /**
482
+ * Checks & returns the network fee needed for a transaction
483
+ *
484
+ * @param address
485
+ * @param amount
486
+ * @throws {DefinedRuntimeError} will throw an error if there are not enough BTC funds
487
+ */
488
+ async checkAndGetNetworkFee(address, amount) {
489
+ let chainFeeResp = await this.bitcoin.estimateFee(address, Number(amount), null, this.config.networkFeeMultiplier);
490
+ const hasEnoughFunds = chainFeeResp != null;
491
+ if (!hasEnoughFunds)
492
+ throw {
493
+ code: 20002,
494
+ msg: "Not enough liquidity"
495
+ };
496
+ return {
497
+ networkFee: BigInt(chainFeeResp.networkFee),
498
+ satsPerVbyte: BigInt(chainFeeResp.satsPerVbyte)
499
+ };
500
+ }
501
+ startRestServer(restServer) {
502
+ restServer.use(this.path + "/payInvoice", (0, ServerParamDecoder_1.serverParamDecoder)(10 * 1000));
503
+ restServer.post(this.path + "/payInvoice", (0, Utils_1.expressHandlerWrapper)(async (req, res) => {
504
+ const metadata = { request: {}, times: {} };
505
+ const chainIdentifier = req.query.chain;
506
+ const { swapContract, signer, chainInterface } = this.getChain(chainIdentifier);
507
+ metadata.times.requestReceived = Date.now();
508
+ /**
509
+ *Sent initially:
510
+ * address: string Bitcoin destination address
511
+ * amount: string Amount to send (in satoshis)
512
+ * confirmationTarget: number Desired confirmation target for the swap, how big of a fee should be assigned to TX
513
+ * confirmations: number Required number of confirmations for us to claim the swap
514
+ * nonce: string Nonce for the swap (used for replay protection)
515
+ * token: string Desired token to use
516
+ * offerer: string Address of the caller
517
+ * exactIn: boolean Whether the swap should be an exact in instead of exact out swap
518
+ *
519
+ *Sent later:
520
+ * feeRate: string Fee rate to use for the init signature
521
+ */
522
+ const parsedBody = await req.paramReader.getParams({
523
+ address: SchemaVerifier_1.FieldTypeEnum.String,
524
+ amount: SchemaVerifier_1.FieldTypeEnum.BigInt,
525
+ confirmationTarget: SchemaVerifier_1.FieldTypeEnum.Number,
526
+ confirmations: SchemaVerifier_1.FieldTypeEnum.Number,
527
+ nonce: SchemaVerifier_1.FieldTypeEnum.BigInt,
528
+ token: (val) => val != null &&
529
+ typeof (val) === "string" &&
530
+ this.isTokenSupported(chainIdentifier, val) ? val : null,
531
+ offerer: (val) => val != null &&
532
+ typeof (val) === "string" &&
533
+ chainInterface.isValidAddress(val) ? val : null,
534
+ exactIn: SchemaVerifier_1.FieldTypeEnum.BooleanOptional
535
+ });
536
+ if (parsedBody == null)
537
+ throw {
538
+ code: 20100,
539
+ msg: "Invalid request body"
540
+ };
541
+ metadata.request = parsedBody;
542
+ const requestedAmount = { input: !!parsedBody.exactIn, amount: parsedBody.amount, token: parsedBody.token };
543
+ const request = {
544
+ chainIdentifier,
545
+ raw: req,
546
+ parsed: parsedBody,
547
+ metadata
548
+ };
549
+ const useToken = parsedBody.token;
550
+ const responseStream = res.responseStream;
551
+ this.checkNonceValid(parsedBody.nonce);
552
+ this.checkConfirmationTarget(parsedBody.confirmationTarget);
553
+ this.checkRequiredConfirmations(parsedBody.confirmations);
554
+ this.checkAddress(parsedBody.address);
555
+ await this.checkVaultInitialized(chainIdentifier, parsedBody.token);
556
+ const fees = await this.AmountAssertions.preCheckToBtcAmounts(this.type, request, requestedAmount);
557
+ metadata.times.requestChecked = Date.now();
558
+ //Initialize abort controller for the parallel async operations
559
+ const abortController = (0, Utils_1.getAbortController)(responseStream);
560
+ const { pricePrefetchPromise, signDataPrefetchPromise } = this.getToBtcPrefetches(chainIdentifier, useToken, responseStream, abortController);
561
+ const { amountBD, networkFeeData, totalInToken, swapFee, swapFeeInToken, networkFeeInToken } = await this.AmountAssertions.checkToBtcAmount(this.type, request, { ...requestedAmount, pricePrefetch: pricePrefetchPromise }, fees, async (amount) => {
562
+ metadata.times.amountsChecked = Date.now();
563
+ const resp = await this.checkAndGetNetworkFee(parsedBody.address, amount);
564
+ this.logger.debug("checkToBtcAmount(): network fee calculated, amount: " + amount.toString(10) + " fee: " + resp.networkFee.toString(10));
565
+ metadata.times.chainFeeCalculated = Date.now();
566
+ return resp;
567
+ }, abortController.signal);
568
+ metadata.times.priceCalculated = Date.now();
569
+ const paymentHash = this.getHash(chainIdentifier, parsedBody.address, parsedBody.confirmations, parsedBody.nonce, amountBD).toString("hex");
570
+ //Add grace period another time, so the user has 1 hour to commit
571
+ const expirySeconds = this.getExpiryFromCLTV(parsedBody.confirmationTarget, parsedBody.confirmations) + this.config.gracePeriod;
572
+ const currentTimestamp = BigInt(Math.floor(Date.now() / 1000));
573
+ const minRequiredExpiry = currentTimestamp + expirySeconds;
574
+ const sequence = base_1.BigIntBufferUtils.fromBuffer((0, crypto_1.randomBytes)(8));
575
+ const payObject = await swapContract.createSwapData(base_1.ChainSwapType.CHAIN_NONCED, parsedBody.offerer, signer.getAddress(), useToken, totalInToken, paymentHash, sequence, minRequiredExpiry, true, false, 0n, 0n);
576
+ abortController.signal.throwIfAborted();
577
+ metadata.times.swapCreated = Date.now();
578
+ const sigData = await this.getToBtcSignatureData(chainIdentifier, payObject, req, abortController.signal, signDataPrefetchPromise);
579
+ metadata.times.swapSigned = Date.now();
580
+ const createdSwap = new ToBtcSwapAbs_1.ToBtcSwapAbs(chainIdentifier, parsedBody.address, amountBD, swapFee, swapFeeInToken, networkFeeData.networkFee, networkFeeInToken, networkFeeData.satsPerVbyte, parsedBody.nonce, parsedBody.confirmations, parsedBody.confirmationTarget);
581
+ createdSwap.data = payObject;
582
+ createdSwap.metadata = metadata;
583
+ createdSwap.prefix = sigData.prefix;
584
+ createdSwap.timeout = sigData.timeout;
585
+ createdSwap.signature = sigData.signature;
586
+ createdSwap.feeRate = sigData.feeRate;
587
+ await PluginManager_1.PluginManager.swapCreate(createdSwap);
588
+ await this.saveSwapData(createdSwap);
589
+ this.swapLogger.info(createdSwap, "REST: /payInvoice: created swap address: " + createdSwap.address + " amount: " + amountBD.toString(10));
590
+ await responseStream.writeParamsAndEnd({
591
+ code: 20000,
592
+ msg: "Success",
593
+ data: {
594
+ amount: amountBD.toString(10),
595
+ address: signer.getAddress(),
596
+ satsPervByte: networkFeeData.satsPerVbyte.toString(10),
597
+ networkFee: networkFeeInToken.toString(10),
598
+ swapFee: swapFeeInToken.toString(10),
599
+ totalFee: (swapFeeInToken + networkFeeInToken).toString(10),
600
+ total: totalInToken.toString(10),
601
+ minRequiredExpiry: minRequiredExpiry.toString(10),
602
+ data: payObject.serialize(),
603
+ prefix: sigData.prefix,
604
+ timeout: sigData.timeout,
605
+ signature: sigData.signature
606
+ }
607
+ });
608
+ }));
609
+ const getRefundAuthorization = (0, Utils_1.expressHandlerWrapper)(async (req, res) => {
610
+ /**
611
+ * paymentHash: string Payment hash identifier of the swap
612
+ * sequence: BN Sequence identifier of the swap
613
+ */
614
+ const parsedBody = (0, SchemaVerifier_1.verifySchema)({ ...req.body, ...req.query }, {
615
+ paymentHash: (val) => val != null &&
616
+ typeof (val) === "string" &&
617
+ Utils_1.HEX_REGEX.test(val) ? val : null,
618
+ sequence: SchemaVerifier_1.FieldTypeEnum.BigInt
619
+ });
620
+ if (parsedBody == null)
621
+ throw {
622
+ code: 20100,
623
+ msg: "Invalid request body/query (paymentHash/sequence)"
624
+ };
625
+ this.checkSequence(parsedBody.sequence);
626
+ const payment = await this.storageManager.getData(parsedBody.paymentHash, parsedBody.sequence);
627
+ if (payment == null || payment.state === ToBtcSwapAbs_1.ToBtcSwapState.SAVED)
628
+ throw {
629
+ _httpStatus: 200,
630
+ code: 20007,
631
+ msg: "Payment not found"
632
+ };
633
+ await this.checkExpired(payment);
634
+ if (payment.state === ToBtcSwapAbs_1.ToBtcSwapState.COMMITED)
635
+ throw {
636
+ _httpStatus: 200,
637
+ code: 20008,
638
+ msg: "Payment processing"
639
+ };
640
+ if (payment.state === ToBtcSwapAbs_1.ToBtcSwapState.BTC_SENT || payment.state === ToBtcSwapAbs_1.ToBtcSwapState.BTC_SENDING)
641
+ throw {
642
+ _httpStatus: 200,
643
+ code: 20006,
644
+ msg: "Already paid",
645
+ data: {
646
+ txId: payment.txId
647
+ }
648
+ };
649
+ const { swapContract, signer } = this.getChain(payment.chainIdentifier);
650
+ if (payment.state === ToBtcSwapAbs_1.ToBtcSwapState.NON_PAYABLE) {
651
+ const isCommited = await swapContract.isCommited(payment.data);
652
+ if (!isCommited)
653
+ throw {
654
+ code: 20005,
655
+ msg: "Not committed"
656
+ };
657
+ const refundResponse = await swapContract.getRefundSignature(signer, payment.data, this.config.refundAuthorizationTimeout);
658
+ //Double check the state after promise result
659
+ if (payment.state !== ToBtcSwapAbs_1.ToBtcSwapState.NON_PAYABLE)
660
+ throw {
661
+ code: 20005,
662
+ msg: "Not committed"
663
+ };
664
+ this.swapLogger.info(payment, "REST: /getRefundAuthorization: returning refund authorization, because swap is in NON_PAYABLE state, address: " + payment.address);
665
+ res.status(200).json({
666
+ code: 20000,
667
+ msg: "Success",
668
+ data: {
669
+ address: signer.getAddress(),
670
+ prefix: refundResponse.prefix,
671
+ timeout: refundResponse.timeout,
672
+ signature: refundResponse.signature
673
+ }
674
+ });
675
+ return;
676
+ }
677
+ throw {
678
+ _httpStatus: 500,
679
+ code: 20009,
680
+ msg: "Invalid payment status"
681
+ };
682
+ });
683
+ restServer.post(this.path + "/getRefundAuthorization", getRefundAuthorization);
684
+ restServer.get(this.path + "/getRefundAuthorization", getRefundAuthorization);
685
+ this.logger.info("started at path: ", this.path);
686
+ }
687
+ /**
688
+ * Starts watchdog checking sent bitcoin transactions
689
+ */
690
+ async startTxTimer() {
691
+ let rerun;
692
+ rerun = async () => {
693
+ await this.processBtcTxs().catch(e => this.logger.error("startTxTimer(): call to processBtcTxs() errored", e));
694
+ setTimeout(rerun, this.config.txCheckInterval);
695
+ };
696
+ await rerun();
697
+ }
698
+ async startWatchdog() {
699
+ await super.startWatchdog();
700
+ await this.startTxTimer();
701
+ }
702
+ async init() {
703
+ await this.loadData(ToBtcSwapAbs_1.ToBtcSwapAbs);
704
+ this.subscribeToEvents();
705
+ await PluginManager_1.PluginManager.serviceInitialize(this);
706
+ }
707
+ getInfoData() {
708
+ return {
709
+ minCltv: Number(this.config.minChainCltv),
710
+ minConfirmations: this.config.minConfirmations,
711
+ maxConfirmations: this.config.maxConfirmations,
712
+ minConfTarget: this.config.minConfTarget,
713
+ maxConfTarget: this.config.maxConfTarget,
714
+ maxOutputScriptLen: OUTPUT_SCRIPT_MAX_LENGTH
715
+ };
716
+ }
717
+ }
718
+ exports.ToBtcAbs = ToBtcAbs;