@atomiqlabs/lp-lib 17.5.1 → 17.5.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (170) hide show
  1. package/LICENSE +201 -201
  2. package/dist/fees/IBtcFeeEstimator.d.ts +3 -3
  3. package/dist/fees/IBtcFeeEstimator.js +2 -2
  4. package/dist/index.d.ts +43 -43
  5. package/dist/index.js +59 -59
  6. package/dist/info/InfoHandler.d.ts +17 -17
  7. package/dist/info/InfoHandler.js +61 -61
  8. package/dist/plugins/IPlugin.d.ts +158 -158
  9. package/dist/plugins/IPlugin.js +35 -35
  10. package/dist/plugins/PluginManager.d.ts +115 -115
  11. package/dist/plugins/PluginManager.js +304 -304
  12. package/dist/prices/BinanceSwapPrice.d.ts +29 -29
  13. package/dist/prices/BinanceSwapPrice.js +79 -79
  14. package/dist/prices/CoinGeckoSwapPrice.d.ts +33 -33
  15. package/dist/prices/CoinGeckoSwapPrice.js +51 -51
  16. package/dist/prices/ISwapPrice.d.ts +43 -43
  17. package/dist/prices/ISwapPrice.js +55 -55
  18. package/dist/prices/OKXSwapPrice.d.ts +29 -29
  19. package/dist/prices/OKXSwapPrice.js +79 -79
  20. package/dist/storage/IIntermediaryStorage.d.ts +18 -18
  21. package/dist/storage/IIntermediaryStorage.js +2 -2
  22. package/dist/storagemanager/IntermediaryStorageManager.d.ts +19 -19
  23. package/dist/storagemanager/IntermediaryStorageManager.js +111 -111
  24. package/dist/storagemanager/StorageManager.d.ts +13 -13
  25. package/dist/storagemanager/StorageManager.js +64 -64
  26. package/dist/swaps/SwapHandler.d.ts +178 -178
  27. package/dist/swaps/SwapHandler.js +225 -225
  28. package/dist/swaps/SwapHandlerSwap.d.ts +80 -80
  29. package/dist/swaps/SwapHandlerSwap.js +78 -78
  30. package/dist/swaps/assertions/AmountAssertions.d.ts +37 -37
  31. package/dist/swaps/assertions/AmountAssertions.js +83 -83
  32. package/dist/swaps/assertions/FromBtcAmountAssertions.d.ts +76 -76
  33. package/dist/swaps/assertions/FromBtcAmountAssertions.js +189 -189
  34. package/dist/swaps/assertions/LightningAssertions.d.ts +44 -44
  35. package/dist/swaps/assertions/LightningAssertions.js +86 -86
  36. package/dist/swaps/assertions/ToBtcAmountAssertions.d.ts +53 -53
  37. package/dist/swaps/assertions/ToBtcAmountAssertions.js +154 -154
  38. package/dist/swaps/escrow/EscrowHandler.d.ts +50 -50
  39. package/dist/swaps/escrow/EscrowHandler.js +151 -151
  40. package/dist/swaps/escrow/EscrowHandlerSwap.d.ts +35 -35
  41. package/dist/swaps/escrow/EscrowHandlerSwap.js +69 -69
  42. package/dist/swaps/escrow/FromBtcBaseSwap.d.ts +14 -14
  43. package/dist/swaps/escrow/FromBtcBaseSwap.js +32 -32
  44. package/dist/swaps/escrow/FromBtcBaseSwapHandler.d.ts +102 -102
  45. package/dist/swaps/escrow/FromBtcBaseSwapHandler.js +210 -210
  46. package/dist/swaps/escrow/ToBtcBaseSwap.d.ts +36 -36
  47. package/dist/swaps/escrow/ToBtcBaseSwap.js +67 -67
  48. package/dist/swaps/escrow/ToBtcBaseSwapHandler.d.ts +53 -53
  49. package/dist/swaps/escrow/ToBtcBaseSwapHandler.js +81 -81
  50. package/dist/swaps/escrow/frombtc_abstract/FromBtcAbs.d.ts +84 -84
  51. package/dist/swaps/escrow/frombtc_abstract/FromBtcAbs.js +329 -329
  52. package/dist/swaps/escrow/frombtc_abstract/FromBtcSwapAbs.d.ts +22 -22
  53. package/dist/swaps/escrow/frombtc_abstract/FromBtcSwapAbs.js +53 -53
  54. package/dist/swaps/escrow/frombtcln_abstract/FromBtcLnAbs.d.ts +115 -115
  55. package/dist/swaps/escrow/frombtcln_abstract/FromBtcLnAbs.js +719 -719
  56. package/dist/swaps/escrow/frombtcln_abstract/FromBtcLnSwapAbs.d.ts +34 -34
  57. package/dist/swaps/escrow/frombtcln_abstract/FromBtcLnSwapAbs.js +94 -94
  58. package/dist/swaps/escrow/frombtcln_autoinit/FromBtcLnAuto.d.ts +119 -119
  59. package/dist/swaps/escrow/frombtcln_autoinit/FromBtcLnAuto.js +730 -730
  60. package/dist/swaps/escrow/frombtcln_autoinit/FromBtcLnAutoSwap.d.ts +56 -56
  61. package/dist/swaps/escrow/frombtcln_autoinit/FromBtcLnAutoSwap.js +123 -123
  62. package/dist/swaps/escrow/tobtc_abstract/ToBtcAbs.d.ts +170 -170
  63. package/dist/swaps/escrow/tobtc_abstract/ToBtcAbs.js +769 -769
  64. package/dist/swaps/escrow/tobtc_abstract/ToBtcSwapAbs.d.ts +29 -29
  65. package/dist/swaps/escrow/tobtc_abstract/ToBtcSwapAbs.js +67 -67
  66. package/dist/swaps/escrow/tobtcln_abstract/ToBtcLnAbs.d.ts +178 -178
  67. package/dist/swaps/escrow/tobtcln_abstract/ToBtcLnAbs.js +912 -912
  68. package/dist/swaps/escrow/tobtcln_abstract/ToBtcLnSwapAbs.d.ts +25 -25
  69. package/dist/swaps/escrow/tobtcln_abstract/ToBtcLnSwapAbs.js +61 -61
  70. package/dist/swaps/spv_vault_swap/SpvVault.d.ts +45 -45
  71. package/dist/swaps/spv_vault_swap/SpvVault.js +148 -148
  72. package/dist/swaps/spv_vault_swap/SpvVaultSwap.d.ts +71 -71
  73. package/dist/swaps/spv_vault_swap/SpvVaultSwap.js +165 -165
  74. package/dist/swaps/spv_vault_swap/SpvVaultSwapHandler.d.ts +72 -72
  75. package/dist/swaps/spv_vault_swap/SpvVaultSwapHandler.js +710 -710
  76. package/dist/swaps/spv_vault_swap/SpvVaults.d.ts +70 -70
  77. package/dist/swaps/spv_vault_swap/SpvVaults.js +554 -554
  78. package/dist/swaps/spv_vault_swap/StickyAddress.d.ts +6 -6
  79. package/dist/swaps/spv_vault_swap/StickyAddress.js +19 -19
  80. package/dist/swaps/trusted/frombtc_trusted/FromBtcTrusted.d.ts +52 -52
  81. package/dist/swaps/trusted/frombtc_trusted/FromBtcTrusted.js +677 -677
  82. package/dist/swaps/trusted/frombtc_trusted/FromBtcTrustedSwap.d.ts +53 -53
  83. package/dist/swaps/trusted/frombtc_trusted/FromBtcTrustedSwap.js +121 -121
  84. package/dist/swaps/trusted/frombtcln_trusted/FromBtcLnTrusted.d.ts +77 -77
  85. package/dist/swaps/trusted/frombtcln_trusted/FromBtcLnTrusted.js +518 -518
  86. package/dist/swaps/trusted/frombtcln_trusted/FromBtcLnTrustedSwap.d.ts +35 -35
  87. package/dist/swaps/trusted/frombtcln_trusted/FromBtcLnTrustedSwap.js +84 -84
  88. package/dist/utils/BitcoinUtils.d.ts +4 -4
  89. package/dist/utils/BitcoinUtils.js +61 -61
  90. package/dist/utils/Utils.d.ts +32 -32
  91. package/dist/utils/Utils.js +130 -130
  92. package/dist/utils/paramcoders/IParamReader.d.ts +5 -5
  93. package/dist/utils/paramcoders/IParamReader.js +2 -2
  94. package/dist/utils/paramcoders/IParamWriter.d.ts +4 -4
  95. package/dist/utils/paramcoders/IParamWriter.js +2 -2
  96. package/dist/utils/paramcoders/LegacyParamEncoder.d.ts +10 -10
  97. package/dist/utils/paramcoders/LegacyParamEncoder.js +22 -22
  98. package/dist/utils/paramcoders/ParamDecoder.d.ts +25 -25
  99. package/dist/utils/paramcoders/ParamDecoder.js +222 -222
  100. package/dist/utils/paramcoders/ParamEncoder.d.ts +9 -9
  101. package/dist/utils/paramcoders/ParamEncoder.js +22 -22
  102. package/dist/utils/paramcoders/SchemaVerifier.d.ts +21 -21
  103. package/dist/utils/paramcoders/SchemaVerifier.js +84 -84
  104. package/dist/utils/paramcoders/server/ServerParamDecoder.d.ts +8 -8
  105. package/dist/utils/paramcoders/server/ServerParamDecoder.js +107 -107
  106. package/dist/utils/paramcoders/server/ServerParamEncoder.d.ts +11 -11
  107. package/dist/utils/paramcoders/server/ServerParamEncoder.js +65 -65
  108. package/dist/wallets/IBitcoinWallet.d.ts +155 -155
  109. package/dist/wallets/IBitcoinWallet.js +100 -100
  110. package/dist/wallets/ILightningWallet.d.ts +137 -137
  111. package/dist/wallets/ILightningWallet.js +37 -37
  112. package/dist/wallets/ISpvVaultSigner.d.ts +7 -7
  113. package/dist/wallets/ISpvVaultSigner.js +2 -2
  114. package/package.json +37 -37
  115. package/src/fees/IBtcFeeEstimator.ts +6 -6
  116. package/src/index.ts +54 -54
  117. package/src/info/InfoHandler.ts +106 -106
  118. package/src/plugins/IPlugin.ts +196 -196
  119. package/src/plugins/PluginManager.ts +389 -389
  120. package/src/prices/BinanceSwapPrice.ts +101 -101
  121. package/src/prices/CoinGeckoSwapPrice.ts +75 -75
  122. package/src/prices/ISwapPrice.ts +88 -88
  123. package/src/prices/OKXSwapPrice.ts +101 -101
  124. package/src/storage/IIntermediaryStorage.ts +19 -19
  125. package/src/storagemanager/IntermediaryStorageManager.ts +118 -118
  126. package/src/storagemanager/StorageManager.ts +78 -78
  127. package/src/swaps/SwapHandler.ts +337 -337
  128. package/src/swaps/SwapHandlerSwap.ts +143 -143
  129. package/src/swaps/assertions/AmountAssertions.ts +97 -97
  130. package/src/swaps/assertions/FromBtcAmountAssertions.ts +257 -257
  131. package/src/swaps/assertions/LightningAssertions.ts +103 -103
  132. package/src/swaps/assertions/ToBtcAmountAssertions.ts +209 -209
  133. package/src/swaps/escrow/EscrowHandler.ts +172 -172
  134. package/src/swaps/escrow/EscrowHandlerSwap.ts +86 -86
  135. package/src/swaps/escrow/FromBtcBaseSwap.ts +38 -38
  136. package/src/swaps/escrow/FromBtcBaseSwapHandler.ts +286 -286
  137. package/src/swaps/escrow/ToBtcBaseSwap.ts +85 -85
  138. package/src/swaps/escrow/ToBtcBaseSwapHandler.ts +129 -129
  139. package/src/swaps/escrow/frombtc_abstract/FromBtcAbs.ts +468 -468
  140. package/src/swaps/escrow/frombtc_abstract/FromBtcSwapAbs.ts +65 -65
  141. package/src/swaps/escrow/frombtcln_abstract/FromBtcLnAbs.ts +900 -900
  142. package/src/swaps/escrow/frombtcln_abstract/FromBtcLnSwapAbs.ts +145 -145
  143. package/src/swaps/escrow/frombtcln_autoinit/FromBtcLnAuto.ts +892 -892
  144. package/src/swaps/escrow/frombtcln_autoinit/FromBtcLnAutoSwap.ts +200 -200
  145. package/src/swaps/escrow/tobtc_abstract/ToBtcAbs.ts +947 -947
  146. package/src/swaps/escrow/tobtc_abstract/ToBtcSwapAbs.ts +112 -112
  147. package/src/swaps/escrow/tobtcln_abstract/ToBtcLnAbs.ts +1167 -1167
  148. package/src/swaps/escrow/tobtcln_abstract/ToBtcLnSwapAbs.ts +84 -84
  149. package/src/swaps/spv_vault_swap/SpvVault.ts +182 -182
  150. package/src/swaps/spv_vault_swap/SpvVaultSwap.ts +239 -239
  151. package/src/swaps/spv_vault_swap/SpvVaultSwapHandler.ts +897 -897
  152. package/src/swaps/spv_vault_swap/SpvVaults.ts +640 -640
  153. package/src/swaps/spv_vault_swap/StickyAddress.ts +20 -20
  154. package/src/swaps/trusted/frombtc_trusted/FromBtcTrusted.ts +782 -782
  155. package/src/swaps/trusted/frombtc_trusted/FromBtcTrustedSwap.ts +189 -189
  156. package/src/swaps/trusted/frombtcln_trusted/FromBtcLnTrusted.ts +622 -622
  157. package/src/swaps/trusted/frombtcln_trusted/FromBtcLnTrustedSwap.ts +125 -125
  158. package/src/utils/BitcoinUtils.ts +59 -59
  159. package/src/utils/Utils.ts +152 -152
  160. package/src/utils/paramcoders/IParamReader.ts +7 -7
  161. package/src/utils/paramcoders/IParamWriter.ts +8 -8
  162. package/src/utils/paramcoders/LegacyParamEncoder.ts +27 -27
  163. package/src/utils/paramcoders/ParamDecoder.ts +218 -218
  164. package/src/utils/paramcoders/ParamEncoder.ts +29 -29
  165. package/src/utils/paramcoders/SchemaVerifier.ts +96 -96
  166. package/src/utils/paramcoders/server/ServerParamDecoder.ts +118 -118
  167. package/src/utils/paramcoders/server/ServerParamEncoder.ts +75 -75
  168. package/src/wallets/IBitcoinWallet.ts +246 -246
  169. package/src/wallets/ILightningWallet.ts +201 -201
  170. package/src/wallets/ISpvVaultSigner.ts +10 -10
@@ -1,710 +1,710 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.SpvVaultSwapHandler = void 0;
4
- const SwapHandler_1 = require("../SwapHandler");
5
- const base_1 = require("@atomiqlabs/base");
6
- const SpvVaultSwap_1 = require("./SpvVaultSwap");
7
- const PluginManager_1 = require("../../plugins/PluginManager");
8
- const ServerParamDecoder_1 = require("../../utils/paramcoders/server/ServerParamDecoder");
9
- const Utils_1 = require("../../utils/Utils");
10
- const SchemaVerifier_1 = require("../../utils/paramcoders/SchemaVerifier");
11
- const FromBtcAmountAssertions_1 = require("../assertions/FromBtcAmountAssertions");
12
- const crypto_1 = require("crypto");
13
- const btc_signer_1 = require("@scure/btc-signer");
14
- const SpvVaults_1 = require("./SpvVaults");
15
- const BitcoinUtils_1 = require("../../utils/BitcoinUtils");
16
- const AmountAssertions_1 = require("../assertions/AmountAssertions");
17
- const IPlugin_1 = require("../../plugins/IPlugin");
18
- const StickyAddress_1 = require("./StickyAddress");
19
- const TX_MAX_VSIZE = 16 * 1024;
20
- function parseAmountAdjustUtxos(amountAdjustUtxos) {
21
- if (!Array.isArray(amountAdjustUtxos))
22
- return null;
23
- if (amountAdjustUtxos.length > 250)
24
- return null;
25
- const validArray = amountAdjustUtxos.every(value => value != null && typeof (value) === "object" && typeof (value.value) === "number" && typeof (value.vSize) === "number" &&
26
- (value.cpfp == null || (typeof (value.cpfp) === "object" && typeof (value.cpfp.effectiveVSize) === "number" && typeof (value.cpfp.effectiveFeeRate) === "number")));
27
- if (!validArray)
28
- return null;
29
- return amountAdjustUtxos;
30
- }
31
- class SpvVaultSwapHandler extends SwapHandler_1.SwapHandler {
32
- constructor(storageDirectory, vaultStorage, path, chainsData, swapPricing, bitcoin, bitcoinRpc, spvVaultSigner, config, stickyAddresses) {
33
- super(storageDirectory, path, chainsData, swapPricing);
34
- this.type = SwapHandler_1.SwapHandlerType.FROM_BTC_SPV;
35
- this.inflightSwapStates = new Set([SpvVaultSwap_1.SpvVaultSwapState.SIGNED, SpvVaultSwap_1.SpvVaultSwapState.SENT, SpvVaultSwap_1.SpvVaultSwapState.BTC_CONFIRMED]);
36
- this.btcTxIdIndex = new Map();
37
- this.bitcoinRpc = bitcoinRpc;
38
- this.bitcoin = bitcoin;
39
- this.vaultSigner = spvVaultSigner;
40
- this.config = config;
41
- this.AmountAssertions = new FromBtcAmountAssertions_1.FromBtcAmountAssertions(config, swapPricing);
42
- this.Vaults = new SpvVaults_1.SpvVaults(vaultStorage, bitcoin, spvVaultSigner, bitcoinRpc, this.chains, config);
43
- this.stickyAddresses = stickyAddresses;
44
- for (let chain in this.allowedTokens) {
45
- //Remove chains that don't support spv vault swaps
46
- const { spvVaultContract } = this.getChain(chain);
47
- if (spvVaultContract == null)
48
- this.allowedTokens[chain].clear();
49
- }
50
- }
51
- async getStickyAddress(chainId, address) {
52
- if (this.stickyAddresses == null)
53
- throw new Error("Sticky addresses are not supported!");
54
- const { chainInterface } = this.getChain(chainId);
55
- const normalizedAddress = chainInterface.normalizeAddress(address);
56
- const addressIdentifier = chainId + "-" + normalizedAddress;
57
- const result = this.stickyAddresses.data[addressIdentifier];
58
- if (result == null)
59
- return;
60
- const btcAddress = result.address;
61
- if (this.bitcoin.isOwnedAddress != null) {
62
- if (!(await this.bitcoin.isOwnedAddress(btcAddress))) {
63
- this.logger.warn(`getStickyAddress(): Failed to get sticky address, address ${btcAddress} is not controlled by our bitcoin wallet!`);
64
- return;
65
- }
66
- }
67
- return btcAddress;
68
- }
69
- async addStickyAddress(chainId, address, btcAddress) {
70
- if (this.stickyAddresses == null)
71
- throw new Error("Sticky addresses are not supported!");
72
- const { chainInterface } = this.getChain(chainId);
73
- const normalizedAddress = chainInterface.normalizeAddress(address);
74
- if (this.bitcoin.isOwnedAddress != null) {
75
- if (!(await this.bitcoin.isOwnedAddress(btcAddress))) {
76
- this.logger.warn(`addStickyAddress(): Failed to create sticky address, address ${btcAddress} is not controlled by our bitcoin wallet!`);
77
- return;
78
- }
79
- }
80
- const addressIdentifier = chainId + "-" + normalizedAddress;
81
- if (this.stickyAddresses.data[addressIdentifier] != null) {
82
- this.logger.warn(`addStickyAddress(): Failed to create sticky address, address sticky address already exists for ${addressIdentifier}!`);
83
- return;
84
- }
85
- await this.stickyAddresses.saveData(addressIdentifier, new StickyAddress_1.StickyAddress(btcAddress));
86
- }
87
- async processClaimEvent(swap, event) {
88
- if (swap == null)
89
- return;
90
- //Update swap
91
- swap.txIds.claim = event.meta?.txId;
92
- await this.removeSwapData(swap, SpvVaultSwap_1.SpvVaultSwapState.CLAIMED);
93
- if (swap.saveStickyAddress)
94
- try {
95
- await this.addStickyAddress(swap.chainIdentifier, swap.recipient, swap.btcAddress);
96
- }
97
- catch (e) {
98
- this.logger.error(`processClaimEvent(): Failed to create the sticky address for swap ${swap.getIdentifier()}`);
99
- }
100
- }
101
- /**
102
- * Chain event processor
103
- *
104
- * @param chainIdentifier
105
- * @param eventData
106
- */
107
- async processEvent(chainIdentifier, eventData) {
108
- for (let event of eventData) {
109
- if (!(event instanceof base_1.SpvVaultEvent))
110
- continue;
111
- const vault = await this.Vaults.getVault(chainIdentifier, event.owner, event.vaultId);
112
- if (vault == null)
113
- continue;
114
- if (event instanceof base_1.SpvVaultOpenEvent) {
115
- await this.Vaults.processOpenEvent(vault, event);
116
- }
117
- else if (event instanceof base_1.SpvVaultCloseEvent) {
118
- await this.Vaults.processCloseEvent(vault, event);
119
- }
120
- else if (event instanceof base_1.SpvVaultClaimEvent) {
121
- const swap = this.btcTxIdIndex.get(event.btcTxId);
122
- if (swap != null) {
123
- swap.txIds.claim = event.meta?.txId;
124
- if (swap.metadata != null)
125
- swap.metadata.times.claimTxReceived = Date.now();
126
- }
127
- await this.Vaults.processClaimEvent(vault, swap, event);
128
- await this.processClaimEvent(swap, event);
129
- }
130
- else if (event instanceof base_1.SpvVaultDepositEvent) {
131
- await this.Vaults.processDepositEvent(vault, event);
132
- }
133
- }
134
- return true;
135
- }
136
- /**
137
- * Initializes chain events subscription
138
- */
139
- subscribeToEvents() {
140
- for (let key in this.chains.chains) {
141
- this.chains.chains[key].chainEvents.registerListener((events) => this.processEvent(key, events));
142
- }
143
- this.logger.info("SC: Events: subscribed to smartchain events");
144
- }
145
- async startWatchdog() {
146
- await super.startWatchdog();
147
- await this.Vaults.startVaultsWatchdog();
148
- }
149
- async init() {
150
- await this.storageManager.loadData(SpvVaultSwap_1.SpvVaultSwap);
151
- for (let { obj: swap, hash, sequence } of await this.storageManager.query([])) {
152
- if (swap.btcTxId != null)
153
- this.btcTxIdIndex.set(swap.btcTxId, swap);
154
- }
155
- await this.Vaults.init();
156
- if (this.stickyAddresses != null) {
157
- await this.stickyAddresses.init();
158
- await this.stickyAddresses.loadData(StickyAddress_1.StickyAddress);
159
- }
160
- this.subscribeToEvents();
161
- await PluginManager_1.PluginManager.serviceInitialize(this);
162
- }
163
- async processPastSwap(swap) {
164
- if (swap.state === SpvVaultSwap_1.SpvVaultSwapState.CREATED) {
165
- if (swap.expiry < Date.now() / 1000) {
166
- await this.removeSwapData(swap, SpvVaultSwap_1.SpvVaultSwapState.EXPIRED);
167
- if (!swap.hasStickyAddress)
168
- await this.bitcoin.addUnusedAddress(swap.btcAddress);
169
- }
170
- }
171
- if (swap.state === SpvVaultSwap_1.SpvVaultSwapState.SIGNED) {
172
- if (swap.sending)
173
- return;
174
- const vault = await this.Vaults.getVault(swap.chainIdentifier, swap.vaultOwner, swap.vaultId);
175
- const foundWithdrawal = vault.pendingWithdrawals.find(val => val.btcTx.txid === swap.btcTxId);
176
- let tx = foundWithdrawal?.btcTx;
177
- if (tx == null)
178
- tx = await this.bitcoinRpc.getTransaction(swap.btcTxId);
179
- if (tx == null) {
180
- await this.removeSwapData(swap, SpvVaultSwap_1.SpvVaultSwapState.FAILED);
181
- return;
182
- }
183
- else if (tx.confirmations === 0) {
184
- await swap.setState(SpvVaultSwap_1.SpvVaultSwapState.SENT);
185
- await this.saveSwapData(swap);
186
- return;
187
- }
188
- else {
189
- await swap.setState(SpvVaultSwap_1.SpvVaultSwapState.BTC_CONFIRMED);
190
- await this.saveSwapData(swap);
191
- }
192
- }
193
- if (swap.state === SpvVaultSwap_1.SpvVaultSwapState.SENT || swap.state === SpvVaultSwap_1.SpvVaultSwapState.BTC_CONFIRMED) {
194
- //Check if confirmed or double-spent
195
- if (swap.sending)
196
- return;
197
- const vault = await this.Vaults.getVault(swap.chainIdentifier, swap.vaultOwner, swap.vaultId);
198
- const foundWithdrawal = vault.pendingWithdrawals.find(val => val.btcTx.txid === swap.btcTxId);
199
- let tx = foundWithdrawal?.btcTx;
200
- if (tx == null)
201
- tx = await this.bitcoinRpc.getTransaction(swap.btcTxId);
202
- if (tx == null) {
203
- await this.removeSwapData(swap, SpvVaultSwap_1.SpvVaultSwapState.DOUBLE_SPENT);
204
- return;
205
- }
206
- else if (tx.confirmations > 0) {
207
- if (swap.state !== SpvVaultSwap_1.SpvVaultSwapState.BTC_CONFIRMED) {
208
- await swap.setState(SpvVaultSwap_1.SpvVaultSwapState.BTC_CONFIRMED);
209
- await this.saveSwapData(swap);
210
- }
211
- }
212
- }
213
- }
214
- async processPastSwaps() {
215
- const swaps = await this.storageManager.query([
216
- {
217
- key: "state",
218
- value: [
219
- SpvVaultSwap_1.SpvVaultSwapState.CREATED,
220
- SpvVaultSwap_1.SpvVaultSwapState.SIGNED,
221
- SpvVaultSwap_1.SpvVaultSwapState.SENT //Check if confirmed or double-spent
222
- ]
223
- }
224
- ]);
225
- for (let { obj: swap } of swaps) {
226
- await this.processPastSwap(swap);
227
- }
228
- }
229
- getPricePrefetches(chainIdentifier, token, gasToken, abortController) {
230
- const pricePrefetchPromise = this.swapPricing.preFetchPrice(token, chainIdentifier).catch(e => {
231
- this.logger.error("getPricePrefetches(): pricePrefetchPromise error: ", e);
232
- abortController.abort(e);
233
- return null;
234
- });
235
- const gasTokenPricePrefetchPromise = token === gasToken ?
236
- pricePrefetchPromise :
237
- this.swapPricing.preFetchPrice(gasToken, chainIdentifier).catch(e => {
238
- this.logger.error("getPricePrefetches(): gasTokenPricePrefetchPromise error: ", e);
239
- abortController.abort(e);
240
- return null;
241
- });
242
- return { pricePrefetchPromise, gasTokenPricePrefetchPromise };
243
- }
244
- startRestServer(restServer) {
245
- restServer.use(this.path + "/getQuote", (0, ServerParamDecoder_1.serverParamDecoder)(10 * 1000));
246
- restServer.post(this.path + "/getQuote", (0, Utils_1.expressHandlerWrapper)(async (req, res) => {
247
- const metadata = { request: {}, times: {} };
248
- const chainIdentifier = req.query.chain;
249
- const { signer, chainInterface, spvVaultContract } = this.getChain(chainIdentifier);
250
- metadata.times.requestReceived = Date.now();
251
- /**
252
- * address: string smart chain address of the recipient
253
- * token: string Desired token to use
254
- * gasToken: string
255
- */
256
- const preFetchParsedBody = await req.paramReader.getParams({
257
- address: (val) => val != null &&
258
- typeof (val) === "string" &&
259
- chainInterface.isValidAddress(val, true) ? val : null,
260
- token: (val) => val != null &&
261
- typeof (val) === "string" &&
262
- this.isTokenSupported(chainIdentifier, val) ? val : null,
263
- gasToken: (val) => val != null &&
264
- typeof (val) === "string" &&
265
- chainInterface.isValidToken(val) ? val : null
266
- });
267
- if (preFetchParsedBody == null)
268
- throw {
269
- code: 20100,
270
- msg: "Invalid request body"
271
- };
272
- const stickyAddressObject = req.paramReader.getExistingParamsOrNull({
273
- stickyAddress: SchemaVerifier_1.FieldTypeEnum.BooleanOptional
274
- });
275
- const useStickyAddress = stickyAddressObject?.stickyAddress;
276
- //Create abortController for parallel prefetches
277
- const responseStream = res.responseStream;
278
- const abortController = (0, Utils_1.getAbortController)(responseStream);
279
- //Pre-fetch data
280
- const { pricePrefetchPromise, gasTokenPricePrefetchPromise } = this.getPricePrefetches(chainIdentifier, preFetchParsedBody.token, preFetchParsedBody.gasToken, abortController);
281
- const nativeBalancePrefetch = this.prefetchNativeBalanceIfNeeded(chainIdentifier, abortController);
282
- const btcFeeRatePrefetch = this.bitcoin.getFeeRate().catch(e => {
283
- abortController.abort(e);
284
- return null;
285
- });
286
- //Listener that re-adds the returned bitcoin address to the unused address list if request fails or closes
287
- let abortAddUnusedAddressListener;
288
- const bitcoinAddressPrefetch = (async () => {
289
- if (useStickyAddress) {
290
- const result = await this.getStickyAddress(chainIdentifier, preFetchParsedBody.address);
291
- if (result != null)
292
- return { address: result, isStickyAddress: true };
293
- }
294
- const value = await this.bitcoin.getAddress();
295
- //Already aborted
296
- if (abortController.signal.aborted) {
297
- this.bitcoin.addUnusedAddress(value);
298
- return null;
299
- }
300
- //Not aborted yet, add an event listener to re-add the address to the unused list
301
- abortController.signal.addEventListener("abort", abortAddUnusedAddressListener = () => {
302
- this.bitcoin.addUnusedAddress(value);
303
- });
304
- return { address: value, isStickyAddress: false };
305
- })().catch(e => {
306
- abortController.abort(e);
307
- return null;
308
- });
309
- /**
310
- * amount: string amount (in sats)
311
- * gasAmount: string Desired amount in gas token to also get
312
- * exactOut: boolean Whether the swap should be an exact out instead of exact in swap
313
- * callerFeeRate: string Caller/watchtower fee (in output token) to assign to the swap
314
- * frontingFeeRate: string Fronting fee (in output token) to assign to the swap
315
- */
316
- const actualParsedBody = await req.paramReader.getParams({
317
- amount: SchemaVerifier_1.FieldTypeEnum.BigInt,
318
- gasAmount: SchemaVerifier_1.FieldTypeEnum.BigInt,
319
- exactOut: SchemaVerifier_1.FieldTypeEnum.BooleanOptional,
320
- callerFeeRate: SchemaVerifier_1.FieldTypeEnum.BigInt,
321
- frontingFeeRate: SchemaVerifier_1.FieldTypeEnum.BigInt,
322
- });
323
- abortController.signal.throwIfAborted();
324
- if (actualParsedBody == null)
325
- throw {
326
- code: 20100,
327
- msg: "Invalid request body"
328
- };
329
- const inputAmountAdjustments = req.paramReader.getExistingParamsOrNull({
330
- amountUtxos: SchemaVerifier_1.FieldTypeEnum.AnyOptional,
331
- amountFeeRate: SchemaVerifier_1.FieldTypeEnum.NumberOptional
332
- });
333
- if (inputAmountAdjustments == null)
334
- throw {
335
- code: 20100,
336
- msg: "Invalid request body"
337
- };
338
- const clientInputUtxos = inputAmountAdjustments?.amountUtxos != null
339
- ? parseAmountAdjustUtxos(inputAmountAdjustments.amountUtxos)
340
- : null;
341
- if (inputAmountAdjustments?.amountUtxos != null && clientInputUtxos == null)
342
- throw {
343
- code: 20100,
344
- msg: "Invalid request body (amountUtxos)"
345
- };
346
- const parsedBody = { ...preFetchParsedBody, ...actualParsedBody };
347
- metadata.request = parsedBody;
348
- if (parsedBody.gasToken !== chainInterface.getNativeCurrencyAddress())
349
- throw {
350
- code: 20190,
351
- msg: "Unsupported gas token"
352
- };
353
- if (parsedBody.callerFeeRate < 0n || parsedBody.callerFeeRate >= 2n ** 20n)
354
- throw {
355
- code: 20191,
356
- msg: "Invalid caller fee rate"
357
- };
358
- if (parsedBody.frontingFeeRate < 0n || parsedBody.frontingFeeRate >= 2n ** 20n)
359
- throw {
360
- code: 20192,
361
- msg: "Invalid fronting fee rate"
362
- };
363
- const requestedAmount = {
364
- input: !parsedBody.exactOut,
365
- amount: parsedBody.exactOut ?
366
- (parsedBody.amount * (100000n + parsedBody.callerFeeRate + parsedBody.frontingFeeRate) / 100000n) :
367
- parsedBody.amount,
368
- token: parsedBody.token
369
- };
370
- if (clientInputUtxos != null) {
371
- if (parsedBody.exactOut)
372
- throw {
373
- code: 20193,
374
- msg: "amountAdjustUtxos cannot be specified for exactOut swaps!"
375
- };
376
- let btcFeeRate = await btcFeeRatePrefetch;
377
- if (inputAmountAdjustments.amountFeeRate != null && inputAmountAdjustments.amountFeeRate > btcFeeRate)
378
- btcFeeRate = inputAmountAdjustments.amountFeeRate;
379
- let feeAccumulator = 0;
380
- let valueAccumulator = 0;
381
- for (let utxo of clientInputUtxos) {
382
- const cpfpAdditionalFee = utxo.cpfp == null ? 0 : Math.ceil(utxo.cpfp.effectiveVSize * Math.max(0, btcFeeRate - utxo.cpfp.effectiveFeeRate));
383
- const spendFee = utxo.vSize * btcFeeRate;
384
- const totalFee = cpfpAdditionalFee + spendFee;
385
- if (totalFee > utxo.value)
386
- continue; //Skip detrimental UTXO
387
- feeAccumulator += totalFee;
388
- valueAccumulator += utxo.value;
389
- }
390
- let baseTxVSize = 10.5; // 4b version, 1b inputs, 1b outputs, 4b locktime, 0.5vB witness flag + witness elements count
391
- //vault input and output
392
- baseTxVSize += 32 + 4 + 1 + 4; //Input base
393
- baseTxVSize += this.vaultSigner.getAddressType() === "p2tr" ? (1 + 1 + 65) / 4 : (1 + 1 + 72 + 1 + 33) / 4;
394
- baseTxVSize += 8 + 1; //Output base
395
- baseTxVSize += this.vaultSigner.getAddressType() === "p2tr" ? 34 : 22;
396
- //opreturn output
397
- baseTxVSize += 8 + 1; //Output base
398
- const opReturnDataSize = spvVaultContract.toOpReturnData(parsedBody.address, parsedBody.gasAmount > 0 ? [0xffffffffffffffffn, 0xffffffffffffffffn] : [0xffffffffffffffffn]).length;
399
- baseTxVSize += (opReturnDataSize <= 0x4b ? 2 : 3 /*Needs an OP_PUSHDATA1 opcode*/) + opReturnDataSize;
400
- //LP output
401
- baseTxVSize += 8 + 1; //Output base
402
- baseTxVSize += this.bitcoin.getAddressType() === "p2tr" ? 34 : this.bitcoin.getAddressType() === "p2wpkh" ? 22 : 23;
403
- const baseTxFee = Math.ceil(baseTxVSize) * btcFeeRate;
404
- feeAccumulator += baseTxFee;
405
- const amount = Math.floor(valueAccumulator - Math.ceil(feeAccumulator));
406
- requestedAmount.amount = BigInt(amount);
407
- }
408
- const gasTokenAmount = {
409
- input: false,
410
- amount: parsedBody.gasAmount * (100000n + parsedBody.callerFeeRate + parsedBody.frontingFeeRate) / 100000n,
411
- token: parsedBody.gasToken
412
- };
413
- const request = {
414
- chainIdentifier,
415
- raw: req,
416
- parsed: parsedBody,
417
- metadata
418
- };
419
- const useToken = parsedBody.token;
420
- const gasToken = parsedBody.gasToken;
421
- this.checkTooManyInflightSwaps();
422
- //Check request params
423
- const fees = await this.AmountAssertions.preCheckFromBtcAmounts(this.type, request, requestedAmount, gasTokenAmount);
424
- metadata.times.requestChecked = Date.now();
425
- await this.checkNativeBalance(chainIdentifier, nativeBalancePrefetch, abortController.signal);
426
- //Check valid amount specified (min/max)
427
- let { amountBD, swapFee, swapFeeInToken, totalInToken, amountBDgas, gasSwapFee, gasSwapFeeInToken, totalInGasToken } = await this.AmountAssertions.checkFromBtcAmount(this.type, request, { ...requestedAmount, pricePrefetch: pricePrefetchPromise }, fees, abortController.signal, { ...gasTokenAmount, pricePrefetch: gasTokenPricePrefetchPromise });
428
- metadata.times.priceCalculated = Date.now();
429
- const totalBtcOutput = amountBD + amountBDgas;
430
- //Check if we have enough funds to honor the request
431
- let vault;
432
- do {
433
- vault = await this.Vaults.findVaultForSwap(chainIdentifier, totalBtcOutput, useToken, totalInToken, gasToken, totalInGasToken);
434
- } while (await this.Vaults.checkVaultReplacedTransactions(vault, true));
435
- abortController.signal.throwIfAborted();
436
- metadata.times.vaultPicked = Date.now();
437
- //Create swap receive bitcoin address
438
- const btcFeeRate = await btcFeeRatePrefetch;
439
- const btcAddressObject = await bitcoinAddressPrefetch;
440
- abortController.signal.throwIfAborted();
441
- metadata.times.addressCreated = Date.now();
442
- const receiveAddress = btcAddressObject.address;
443
- const hasStickyAddress = btcAddressObject.isStickyAddress;
444
- //Adjust the amounts based on passed fees
445
- if (parsedBody.exactOut) {
446
- totalInToken = parsedBody.amount;
447
- }
448
- else {
449
- totalInToken = (totalInToken * 100000n / (100000n + parsedBody.callerFeeRate + parsedBody.frontingFeeRate));
450
- }
451
- totalInGasToken = (totalInGasToken * 100000n / (100000n + parsedBody.callerFeeRate + parsedBody.frontingFeeRate));
452
- //Calculate raw amounts
453
- const [rawTokenAmount, rawGasTokenAmount] = vault.toRawAmounts([totalInToken, totalInGasToken]);
454
- [totalInToken, totalInGasToken] = vault.fromRawAmounts([rawTokenAmount, rawGasTokenAmount]);
455
- const expiry = Math.floor(Date.now() / 1000) + this.getInitAuthorizationTimeout(chainIdentifier);
456
- //Get PSBT data
457
- const callerFeeShare = parsedBody.callerFeeRate;
458
- const frontingFeeShare = parsedBody.frontingFeeRate;
459
- const executionFeeShare = 0n;
460
- const utxo = vault.getLatestUtxo();
461
- const quoteId = (0, crypto_1.randomBytes)(32).toString("hex");
462
- const swap = new SpvVaultSwap_1.SpvVaultSwap(chainIdentifier, quoteId, expiry, vault, utxo, receiveAddress, btcFeeRate, parsedBody.address, totalBtcOutput, totalInToken, totalInGasToken, swapFee, swapFeeInToken, gasSwapFee, gasSwapFeeInToken, callerFeeShare, frontingFeeShare, executionFeeShare, useToken, gasToken);
463
- swap.metadata = metadata;
464
- swap.saveStickyAddress = useStickyAddress && !hasStickyAddress;
465
- swap.hasStickyAddress = hasStickyAddress;
466
- //We can remove the listener to add unused address now, as we are about to save the swap
467
- abortController.signal.removeEventListener("abort", abortAddUnusedAddressListener);
468
- await PluginManager_1.PluginManager.swapCreate(swap);
469
- await this.saveSwapData(swap);
470
- this.swapLogger.info(swap, "REST: /getQuote: Created swap address: " + receiveAddress + " amount: " + totalBtcOutput.toString(10));
471
- await responseStream.writeParamsAndEnd({
472
- code: 20000,
473
- msg: "Success",
474
- data: {
475
- quoteId,
476
- expiry,
477
- address: signer.getAddress(),
478
- vaultId: vault.data.getVaultId().toString(10),
479
- vaultBtcAddress: vault.btcAddress,
480
- btcAddress: receiveAddress,
481
- btcUtxo: utxo,
482
- btcFeeRate,
483
- btcAmount: totalBtcOutput.toString(10),
484
- btcAmountSwap: amountBD.toString(10),
485
- btcAmountGas: amountBDgas.toString(10),
486
- total: totalInToken.toString(10),
487
- totalGas: totalInGasToken.toString(10),
488
- totalFeeBtc: (swapFee + gasSwapFee).toString(10),
489
- swapFeeBtc: swapFee.toString(10),
490
- swapFee: swapFeeInToken.toString(10),
491
- gasSwapFeeBtc: gasSwapFee.toString(10),
492
- gasSwapFee: gasSwapFeeInToken.toString(10),
493
- callerFeeShare: callerFeeShare.toString(10),
494
- frontingFeeShare: frontingFeeShare.toString(10),
495
- executionFeeShare: executionFeeShare.toString(10),
496
- usedUtxoInputCalculation: clientInputUtxos != null
497
- }
498
- });
499
- }));
500
- restServer.use(this.path + "/postQuote", (0, ServerParamDecoder_1.serverParamDecoder)(10 * 1000));
501
- restServer.post(this.path + "/postQuote", (0, Utils_1.expressHandlerWrapper)(async (req, res) => {
502
- let requestReceived = Date.now();
503
- const parsedBody = await req.paramReader.getParams({
504
- quoteId: SchemaVerifier_1.FieldTypeEnum.String,
505
- psbtHex: (val) => val != null &&
506
- typeof (val) === "string" &&
507
- Utils_1.HEX_REGEX.test(val) ? val : null
508
- });
509
- const swap = await this.storageManager.getData(parsedBody.quoteId, 0n);
510
- if (swap == null || swap.state !== SpvVaultSwap_1.SpvVaultSwapState.CREATED || swap.expiry < Date.now() / 1000)
511
- throw {
512
- code: 20505,
513
- msg: "Invalid quote ID, not found or expired!"
514
- };
515
- const metadata = swap.metadata;
516
- metadata.times ?? (metadata.times = {});
517
- metadata.times.requestReceived = requestReceived;
518
- this.checkTooManyInflightSwaps();
519
- const vault = await this.Vaults.getVault(swap.chainIdentifier, swap.vaultOwner, swap.vaultId);
520
- if (vault == null || !vault.isReady()) {
521
- throw {
522
- code: 20506,
523
- msg: "Used vault not found!"
524
- };
525
- }
526
- //Try parse psbt
527
- let transaction;
528
- try {
529
- transaction = btc_signer_1.Transaction.fromPSBT(Buffer.from(parsedBody.psbtHex, "hex"));
530
- }
531
- catch (e) {
532
- this.swapLogger.error(swap, "REST: /postQuote: failed to parse provided PSBT: ", e);
533
- throw {
534
- code: 20507,
535
- msg: "Error parsing PSBT, hex format required!"
536
- };
537
- }
538
- for (let i = 1; i < transaction.inputsLength; i++) { //Skip first vault input
539
- const txIn = transaction.getInput(i);
540
- if ((0, BitcoinUtils_1.isLegacyInput)(txIn))
541
- throw {
542
- code: 20514,
543
- msg: "Legacy (pre-segwit) inputs in tx are not allowed!"
544
- };
545
- }
546
- //Check the posted quote with the plugins
547
- AmountAssertions_1.AmountAssertions.handlePluginErrorResponses(await PluginManager_1.PluginManager.onHandlePostedFromBtcQuote(this.type, { chainIdentifier: swap.chainIdentifier, raw: req, parsed: parsedBody, metadata }, swap));
548
- //Check correct psbt
549
- for (let i = 1; i < transaction.inputsLength; i++) { //Skip first vault input
550
- const txIn = transaction.getInput(i);
551
- //Check UTXOs exist and are unspent
552
- if (await this.bitcoinRpc.isSpent(Buffer.from(txIn.txid).toString("hex") + ":" + txIn.index.toString(10)))
553
- throw {
554
- code: 20515,
555
- msg: "Spent UTXO in inputs!"
556
- };
557
- }
558
- const { spvVaultContract } = this.getChain(swap.chainIdentifier);
559
- let data;
560
- try {
561
- data = await spvVaultContract.getWithdrawalData((0, Utils_1.parsePsbt)(transaction));
562
- }
563
- catch (e) {
564
- this.swapLogger.error(swap, "REST: /postQuote: failed to parse PSBT to withdrawal tx data: ", e);
565
- throw {
566
- code: 20508,
567
- msg: "PSBT transaction cannot be parsed!"
568
- };
569
- }
570
- if (!data.isRecipient(swap.recipient) ||
571
- data.callerFeeRate !== swap.callerFeeShare ||
572
- data.frontingFeeRate !== swap.frontingFeeShare ||
573
- data.executionFeeRate !== swap.executionFeeShare ||
574
- data.rawAmounts[0] !== swap.rawAmountToken ||
575
- data.rawAmounts[1] !== swap.rawAmountGasToken ||
576
- data.getExecutionData() != null ||
577
- data.getSpentVaultUtxo() !== swap.vaultUtxo ||
578
- data.btcTx.outs[0].value !== SpvVaults_1.VAULT_DUST_AMOUNT ||
579
- !Buffer.from(data.btcTx.outs[0].scriptPubKey.hex, "hex").equals(this.bitcoin.toOutputScript(swap.vaultAddress)) ||
580
- BigInt(data.btcTx.outs[2].value) !== swap.amountBtc ||
581
- !Buffer.from(data.btcTx.outs[2].scriptPubKey.hex, "hex").equals(this.bitcoin.toOutputScript(swap.btcAddress)) ||
582
- (data.btcTx.locktime > 0 && data.btcTx.locktime < 500000000) ||
583
- data.btcTx.locktime > Math.floor(Date.now() / 1000) - 1000000) {
584
- this.swapLogger.error(swap, "REST: /postQuote: Invalid psbt data submitted, raw psbt hex: ", parsedBody.psbtHex);
585
- throw {
586
- code: 20509,
587
- msg: "Invalid PSBT provided!"
588
- };
589
- }
590
- if (swap.vaultUtxo !== vault.getLatestUtxo()) {
591
- throw {
592
- code: 20510,
593
- msg: "Vault UTXO already spent, please get another quote and try again!"
594
- };
595
- }
596
- //Create abortController for parallel prefetches
597
- const responseStream = res.responseStream;
598
- const signedTx = await this.vaultSigner.signPsbt(swap.chainIdentifier, swap.vaultId, transaction, [0]);
599
- if (!signedTx.isFinal)
600
- throw {
601
- code: 20513,
602
- msg: "One or more PSBT inputs not finalized!"
603
- };
604
- const effectiveFeeRate = await this.bitcoinRpc.getEffectiveFeeRate((0, Utils_1.parsePsbt)(signedTx));
605
- if (effectiveFeeRate.feeRate < 1 || Math.round(effectiveFeeRate.feeRate) < swap.btcFeeRate)
606
- throw {
607
- code: 20511,
608
- msg: "Bitcoin transaction fee too low, expected minimum: " + swap.btcFeeRate + " adjusted effective fee rate: " + effectiveFeeRate.feeRate
609
- };
610
- const txVsize = signedTx.vsize;
611
- if (txVsize > TX_MAX_VSIZE)
612
- throw {
613
- code: 20516,
614
- msg: "Bitcoin transaction size too large, maximum: " + TX_MAX_VSIZE + " actual: " + txVsize
615
- };
616
- const pluginCheckResult = await PluginManager_1.PluginManager.onHandlePreFromBtcExecute(SwapHandler_1.SwapHandlerType.FROM_BTC_SPV, swap);
617
- if ((0, IPlugin_1.isQuoteThrow)(pluginCheckResult)) {
618
- const error = {
619
- code: 29999,
620
- msg: pluginCheckResult.message
621
- };
622
- if (swap.metadata != null)
623
- swap.metadata.postQuoteError = error;
624
- await this.removeSwapData(swap, SpvVaultSwap_1.SpvVaultSwapState.FAILED);
625
- throw error;
626
- }
627
- await this.Vaults.checkVaultReplacedTransactions(vault, true);
628
- if (swap.vaultUtxo !== vault.getLatestUtxo()) {
629
- throw {
630
- code: 20510,
631
- msg: "Vault UTXO already spent, please get another quote and try again!"
632
- };
633
- }
634
- try {
635
- const btcRawTx = Buffer.from(signedTx.toBytes(true, true)).toString("hex");
636
- //Double-check the state to prevent race condition
637
- if (swap.state !== SpvVaultSwap_1.SpvVaultSwapState.CREATED) {
638
- throw {
639
- code: 20505,
640
- msg: "Invalid quote ID, not found or expired!"
641
- };
642
- }
643
- //Double check in-flight swap count
644
- this.checkTooManyInflightSwaps();
645
- swap.btcTxId = signedTx.id;
646
- swap.state = SpvVaultSwap_1.SpvVaultSwapState.SIGNED;
647
- swap.sending = true;
648
- await this.saveSwapData(swap);
649
- data.btcTx.raw = btcRawTx;
650
- data.sending = true;
651
- vault.addWithdrawal(data);
652
- await this.Vaults.saveVault(vault);
653
- this.swapLogger.info(swap, "REST: /postQuote: BTC transaction signed, txId: " + swap.btcTxId);
654
- try {
655
- await this.bitcoin.sendRawTransaction(btcRawTx);
656
- await swap.setState(SpvVaultSwap_1.SpvVaultSwapState.SENT);
657
- data.sending = false;
658
- swap.sending = false;
659
- }
660
- catch (e) {
661
- this.swapLogger.error(swap, "REST: /postQuote: Failed to send BTC transaction: ", e);
662
- throw {
663
- code: 20512,
664
- msg: "Error broadcasting bitcoin transaction!"
665
- };
666
- }
667
- }
668
- catch (e) {
669
- data.sending = false;
670
- swap.sending = false;
671
- vault.removeWithdrawal(data);
672
- await this.Vaults.saveVault(vault);
673
- if ((0, Utils_1.isDefinedRuntimeError)(e) && swap.metadata != null)
674
- swap.metadata.postQuoteError = e;
675
- await this.removeSwapData(swap, SpvVaultSwap_1.SpvVaultSwapState.FAILED);
676
- throw e;
677
- }
678
- await responseStream.writeParamsAndEnd({
679
- code: 20000,
680
- msg: "Success",
681
- data: {
682
- txId: swap.btcTxId
683
- }
684
- });
685
- }));
686
- }
687
- getInfoData() {
688
- const mappedDict = {};
689
- for (let chainId in this.config.gasTokenMax) {
690
- mappedDict[chainId] = {
691
- gasToken: this.getChain(chainId).chainInterface.getNativeCurrencyAddress(),
692
- max: this.config.gasTokenMax[chainId].toString(10)
693
- };
694
- }
695
- return {
696
- gasTokens: mappedDict
697
- };
698
- }
699
- async saveSwapData(swap) {
700
- if (swap.btcTxId != null)
701
- this.btcTxIdIndex.set(swap.btcTxId, swap);
702
- return super.saveSwapData(swap);
703
- }
704
- async removeSwapData(swap, ultimateState) {
705
- if (swap.btcTxId != null)
706
- this.btcTxIdIndex.delete(swap.btcTxId);
707
- return super.removeSwapData(swap, ultimateState);
708
- }
709
- }
710
- exports.SpvVaultSwapHandler = SpvVaultSwapHandler;
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.SpvVaultSwapHandler = void 0;
4
+ const SwapHandler_1 = require("../SwapHandler");
5
+ const base_1 = require("@atomiqlabs/base");
6
+ const SpvVaultSwap_1 = require("./SpvVaultSwap");
7
+ const PluginManager_1 = require("../../plugins/PluginManager");
8
+ const ServerParamDecoder_1 = require("../../utils/paramcoders/server/ServerParamDecoder");
9
+ const Utils_1 = require("../../utils/Utils");
10
+ const SchemaVerifier_1 = require("../../utils/paramcoders/SchemaVerifier");
11
+ const FromBtcAmountAssertions_1 = require("../assertions/FromBtcAmountAssertions");
12
+ const crypto_1 = require("crypto");
13
+ const btc_signer_1 = require("@scure/btc-signer");
14
+ const SpvVaults_1 = require("./SpvVaults");
15
+ const BitcoinUtils_1 = require("../../utils/BitcoinUtils");
16
+ const AmountAssertions_1 = require("../assertions/AmountAssertions");
17
+ const IPlugin_1 = require("../../plugins/IPlugin");
18
+ const StickyAddress_1 = require("./StickyAddress");
19
+ const TX_MAX_VSIZE = 16 * 1024;
20
+ function parseAmountAdjustUtxos(amountAdjustUtxos) {
21
+ if (!Array.isArray(amountAdjustUtxos))
22
+ return null;
23
+ if (amountAdjustUtxos.length > 250)
24
+ return null;
25
+ const validArray = amountAdjustUtxos.every(value => value != null && typeof (value) === "object" && typeof (value.value) === "number" && typeof (value.vSize) === "number" &&
26
+ (value.cpfp == null || (typeof (value.cpfp) === "object" && typeof (value.cpfp.effectiveVSize) === "number" && typeof (value.cpfp.effectiveFeeRate) === "number")));
27
+ if (!validArray)
28
+ return null;
29
+ return amountAdjustUtxos;
30
+ }
31
+ class SpvVaultSwapHandler extends SwapHandler_1.SwapHandler {
32
+ constructor(storageDirectory, vaultStorage, path, chainsData, swapPricing, bitcoin, bitcoinRpc, spvVaultSigner, config, stickyAddresses) {
33
+ super(storageDirectory, path, chainsData, swapPricing);
34
+ this.type = SwapHandler_1.SwapHandlerType.FROM_BTC_SPV;
35
+ this.inflightSwapStates = new Set([SpvVaultSwap_1.SpvVaultSwapState.SIGNED, SpvVaultSwap_1.SpvVaultSwapState.SENT, SpvVaultSwap_1.SpvVaultSwapState.BTC_CONFIRMED]);
36
+ this.btcTxIdIndex = new Map();
37
+ this.bitcoinRpc = bitcoinRpc;
38
+ this.bitcoin = bitcoin;
39
+ this.vaultSigner = spvVaultSigner;
40
+ this.config = config;
41
+ this.AmountAssertions = new FromBtcAmountAssertions_1.FromBtcAmountAssertions(config, swapPricing);
42
+ this.Vaults = new SpvVaults_1.SpvVaults(vaultStorage, bitcoin, spvVaultSigner, bitcoinRpc, this.chains, config);
43
+ this.stickyAddresses = stickyAddresses;
44
+ for (let chain in this.allowedTokens) {
45
+ //Remove chains that don't support spv vault swaps
46
+ const { spvVaultContract } = this.getChain(chain);
47
+ if (spvVaultContract == null)
48
+ this.allowedTokens[chain].clear();
49
+ }
50
+ }
51
+ async getStickyAddress(chainId, address) {
52
+ if (this.stickyAddresses == null)
53
+ throw new Error("Sticky addresses are not supported!");
54
+ const { chainInterface } = this.getChain(chainId);
55
+ const normalizedAddress = chainInterface.normalizeAddress(address);
56
+ const addressIdentifier = chainId + "-" + normalizedAddress;
57
+ const result = this.stickyAddresses.data[addressIdentifier];
58
+ if (result == null)
59
+ return;
60
+ const btcAddress = result.address;
61
+ if (this.bitcoin.isOwnedAddress != null) {
62
+ if (!(await this.bitcoin.isOwnedAddress(btcAddress))) {
63
+ this.logger.warn(`getStickyAddress(): Failed to get sticky address, address ${btcAddress} is not controlled by our bitcoin wallet!`);
64
+ return;
65
+ }
66
+ }
67
+ return btcAddress;
68
+ }
69
+ async addStickyAddress(chainId, address, btcAddress) {
70
+ if (this.stickyAddresses == null)
71
+ throw new Error("Sticky addresses are not supported!");
72
+ const { chainInterface } = this.getChain(chainId);
73
+ const normalizedAddress = chainInterface.normalizeAddress(address);
74
+ if (this.bitcoin.isOwnedAddress != null) {
75
+ if (!(await this.bitcoin.isOwnedAddress(btcAddress))) {
76
+ this.logger.warn(`addStickyAddress(): Failed to create sticky address, address ${btcAddress} is not controlled by our bitcoin wallet!`);
77
+ return;
78
+ }
79
+ }
80
+ const addressIdentifier = chainId + "-" + normalizedAddress;
81
+ if (this.stickyAddresses.data[addressIdentifier] != null) {
82
+ this.logger.warn(`addStickyAddress(): Failed to create sticky address, address sticky address already exists for ${addressIdentifier}!`);
83
+ return;
84
+ }
85
+ await this.stickyAddresses.saveData(addressIdentifier, new StickyAddress_1.StickyAddress(btcAddress));
86
+ }
87
+ async processClaimEvent(swap, event) {
88
+ if (swap == null)
89
+ return;
90
+ //Update swap
91
+ swap.txIds.claim = event.meta?.txId;
92
+ await this.removeSwapData(swap, SpvVaultSwap_1.SpvVaultSwapState.CLAIMED);
93
+ if (swap.saveStickyAddress)
94
+ try {
95
+ await this.addStickyAddress(swap.chainIdentifier, swap.recipient, swap.btcAddress);
96
+ }
97
+ catch (e) {
98
+ this.logger.error(`processClaimEvent(): Failed to create the sticky address for swap ${swap.getIdentifier()}`);
99
+ }
100
+ }
101
+ /**
102
+ * Chain event processor
103
+ *
104
+ * @param chainIdentifier
105
+ * @param eventData
106
+ */
107
+ async processEvent(chainIdentifier, eventData) {
108
+ for (let event of eventData) {
109
+ if (!(event instanceof base_1.SpvVaultEvent))
110
+ continue;
111
+ const vault = await this.Vaults.getVault(chainIdentifier, event.owner, event.vaultId);
112
+ if (vault == null)
113
+ continue;
114
+ if (event instanceof base_1.SpvVaultOpenEvent) {
115
+ await this.Vaults.processOpenEvent(vault, event);
116
+ }
117
+ else if (event instanceof base_1.SpvVaultCloseEvent) {
118
+ await this.Vaults.processCloseEvent(vault, event);
119
+ }
120
+ else if (event instanceof base_1.SpvVaultClaimEvent) {
121
+ const swap = this.btcTxIdIndex.get(event.btcTxId);
122
+ if (swap != null) {
123
+ swap.txIds.claim = event.meta?.txId;
124
+ if (swap.metadata != null)
125
+ swap.metadata.times.claimTxReceived = Date.now();
126
+ }
127
+ await this.Vaults.processClaimEvent(vault, swap, event);
128
+ await this.processClaimEvent(swap, event);
129
+ }
130
+ else if (event instanceof base_1.SpvVaultDepositEvent) {
131
+ await this.Vaults.processDepositEvent(vault, event);
132
+ }
133
+ }
134
+ return true;
135
+ }
136
+ /**
137
+ * Initializes chain events subscription
138
+ */
139
+ subscribeToEvents() {
140
+ for (let key in this.chains.chains) {
141
+ this.chains.chains[key].chainEvents.registerListener((events) => this.processEvent(key, events));
142
+ }
143
+ this.logger.info("SC: Events: subscribed to smartchain events");
144
+ }
145
+ async startWatchdog() {
146
+ await super.startWatchdog();
147
+ await this.Vaults.startVaultsWatchdog();
148
+ }
149
+ async init() {
150
+ await this.storageManager.loadData(SpvVaultSwap_1.SpvVaultSwap);
151
+ for (let { obj: swap, hash, sequence } of await this.storageManager.query([])) {
152
+ if (swap.btcTxId != null)
153
+ this.btcTxIdIndex.set(swap.btcTxId, swap);
154
+ }
155
+ await this.Vaults.init();
156
+ if (this.stickyAddresses != null) {
157
+ await this.stickyAddresses.init();
158
+ await this.stickyAddresses.loadData(StickyAddress_1.StickyAddress);
159
+ }
160
+ this.subscribeToEvents();
161
+ await PluginManager_1.PluginManager.serviceInitialize(this);
162
+ }
163
+ async processPastSwap(swap) {
164
+ if (swap.state === SpvVaultSwap_1.SpvVaultSwapState.CREATED) {
165
+ if (swap.expiry < Date.now() / 1000) {
166
+ await this.removeSwapData(swap, SpvVaultSwap_1.SpvVaultSwapState.EXPIRED);
167
+ if (!swap.hasStickyAddress)
168
+ await this.bitcoin.addUnusedAddress(swap.btcAddress);
169
+ }
170
+ }
171
+ if (swap.state === SpvVaultSwap_1.SpvVaultSwapState.SIGNED) {
172
+ if (swap.sending)
173
+ return;
174
+ const vault = await this.Vaults.getVault(swap.chainIdentifier, swap.vaultOwner, swap.vaultId);
175
+ const foundWithdrawal = vault.pendingWithdrawals.find(val => val.btcTx.txid === swap.btcTxId);
176
+ let tx = foundWithdrawal?.btcTx;
177
+ if (tx == null)
178
+ tx = await this.bitcoinRpc.getTransaction(swap.btcTxId);
179
+ if (tx == null) {
180
+ await this.removeSwapData(swap, SpvVaultSwap_1.SpvVaultSwapState.FAILED);
181
+ return;
182
+ }
183
+ else if (tx.confirmations === 0) {
184
+ await swap.setState(SpvVaultSwap_1.SpvVaultSwapState.SENT);
185
+ await this.saveSwapData(swap);
186
+ return;
187
+ }
188
+ else {
189
+ await swap.setState(SpvVaultSwap_1.SpvVaultSwapState.BTC_CONFIRMED);
190
+ await this.saveSwapData(swap);
191
+ }
192
+ }
193
+ if (swap.state === SpvVaultSwap_1.SpvVaultSwapState.SENT || swap.state === SpvVaultSwap_1.SpvVaultSwapState.BTC_CONFIRMED) {
194
+ //Check if confirmed or double-spent
195
+ if (swap.sending)
196
+ return;
197
+ const vault = await this.Vaults.getVault(swap.chainIdentifier, swap.vaultOwner, swap.vaultId);
198
+ const foundWithdrawal = vault.pendingWithdrawals.find(val => val.btcTx.txid === swap.btcTxId);
199
+ let tx = foundWithdrawal?.btcTx;
200
+ if (tx == null)
201
+ tx = await this.bitcoinRpc.getTransaction(swap.btcTxId);
202
+ if (tx == null) {
203
+ await this.removeSwapData(swap, SpvVaultSwap_1.SpvVaultSwapState.DOUBLE_SPENT);
204
+ return;
205
+ }
206
+ else if (tx.confirmations > 0) {
207
+ if (swap.state !== SpvVaultSwap_1.SpvVaultSwapState.BTC_CONFIRMED) {
208
+ await swap.setState(SpvVaultSwap_1.SpvVaultSwapState.BTC_CONFIRMED);
209
+ await this.saveSwapData(swap);
210
+ }
211
+ }
212
+ }
213
+ }
214
+ async processPastSwaps() {
215
+ const swaps = await this.storageManager.query([
216
+ {
217
+ key: "state",
218
+ value: [
219
+ SpvVaultSwap_1.SpvVaultSwapState.CREATED,
220
+ SpvVaultSwap_1.SpvVaultSwapState.SIGNED,
221
+ SpvVaultSwap_1.SpvVaultSwapState.SENT //Check if confirmed or double-spent
222
+ ]
223
+ }
224
+ ]);
225
+ for (let { obj: swap } of swaps) {
226
+ await this.processPastSwap(swap);
227
+ }
228
+ }
229
+ getPricePrefetches(chainIdentifier, token, gasToken, abortController) {
230
+ const pricePrefetchPromise = this.swapPricing.preFetchPrice(token, chainIdentifier).catch(e => {
231
+ this.logger.error("getPricePrefetches(): pricePrefetchPromise error: ", e);
232
+ abortController.abort(e);
233
+ return null;
234
+ });
235
+ const gasTokenPricePrefetchPromise = token === gasToken ?
236
+ pricePrefetchPromise :
237
+ this.swapPricing.preFetchPrice(gasToken, chainIdentifier).catch(e => {
238
+ this.logger.error("getPricePrefetches(): gasTokenPricePrefetchPromise error: ", e);
239
+ abortController.abort(e);
240
+ return null;
241
+ });
242
+ return { pricePrefetchPromise, gasTokenPricePrefetchPromise };
243
+ }
244
+ startRestServer(restServer) {
245
+ restServer.use(this.path + "/getQuote", (0, ServerParamDecoder_1.serverParamDecoder)(10 * 1000));
246
+ restServer.post(this.path + "/getQuote", (0, Utils_1.expressHandlerWrapper)(async (req, res) => {
247
+ const metadata = { request: {}, times: {} };
248
+ const chainIdentifier = req.query.chain;
249
+ const { signer, chainInterface, spvVaultContract } = this.getChain(chainIdentifier);
250
+ metadata.times.requestReceived = Date.now();
251
+ /**
252
+ * address: string smart chain address of the recipient
253
+ * token: string Desired token to use
254
+ * gasToken: string
255
+ */
256
+ const preFetchParsedBody = await req.paramReader.getParams({
257
+ address: (val) => val != null &&
258
+ typeof (val) === "string" &&
259
+ chainInterface.isValidAddress(val, true) ? val : null,
260
+ token: (val) => val != null &&
261
+ typeof (val) === "string" &&
262
+ this.isTokenSupported(chainIdentifier, val) ? val : null,
263
+ gasToken: (val) => val != null &&
264
+ typeof (val) === "string" &&
265
+ chainInterface.isValidToken(val) ? val : null
266
+ });
267
+ if (preFetchParsedBody == null)
268
+ throw {
269
+ code: 20100,
270
+ msg: "Invalid request body"
271
+ };
272
+ const stickyAddressObject = req.paramReader.getExistingParamsOrNull({
273
+ stickyAddress: SchemaVerifier_1.FieldTypeEnum.BooleanOptional
274
+ });
275
+ const useStickyAddress = stickyAddressObject?.stickyAddress;
276
+ //Create abortController for parallel prefetches
277
+ const responseStream = res.responseStream;
278
+ const abortController = (0, Utils_1.getAbortController)(responseStream);
279
+ //Pre-fetch data
280
+ const { pricePrefetchPromise, gasTokenPricePrefetchPromise } = this.getPricePrefetches(chainIdentifier, preFetchParsedBody.token, preFetchParsedBody.gasToken, abortController);
281
+ const nativeBalancePrefetch = this.prefetchNativeBalanceIfNeeded(chainIdentifier, abortController);
282
+ const btcFeeRatePrefetch = this.bitcoin.getFeeRate().catch(e => {
283
+ abortController.abort(e);
284
+ return null;
285
+ });
286
+ //Listener that re-adds the returned bitcoin address to the unused address list if request fails or closes
287
+ let abortAddUnusedAddressListener;
288
+ const bitcoinAddressPrefetch = (async () => {
289
+ if (useStickyAddress) {
290
+ const result = await this.getStickyAddress(chainIdentifier, preFetchParsedBody.address);
291
+ if (result != null)
292
+ return { address: result, isStickyAddress: true };
293
+ }
294
+ const value = await this.bitcoin.getAddress();
295
+ //Already aborted
296
+ if (abortController.signal.aborted) {
297
+ this.bitcoin.addUnusedAddress(value);
298
+ return null;
299
+ }
300
+ //Not aborted yet, add an event listener to re-add the address to the unused list
301
+ abortController.signal.addEventListener("abort", abortAddUnusedAddressListener = () => {
302
+ this.bitcoin.addUnusedAddress(value);
303
+ });
304
+ return { address: value, isStickyAddress: false };
305
+ })().catch(e => {
306
+ abortController.abort(e);
307
+ return null;
308
+ });
309
+ /**
310
+ * amount: string amount (in sats)
311
+ * gasAmount: string Desired amount in gas token to also get
312
+ * exactOut: boolean Whether the swap should be an exact out instead of exact in swap
313
+ * callerFeeRate: string Caller/watchtower fee (in output token) to assign to the swap
314
+ * frontingFeeRate: string Fronting fee (in output token) to assign to the swap
315
+ */
316
+ const actualParsedBody = await req.paramReader.getParams({
317
+ amount: SchemaVerifier_1.FieldTypeEnum.BigInt,
318
+ gasAmount: SchemaVerifier_1.FieldTypeEnum.BigInt,
319
+ exactOut: SchemaVerifier_1.FieldTypeEnum.BooleanOptional,
320
+ callerFeeRate: SchemaVerifier_1.FieldTypeEnum.BigInt,
321
+ frontingFeeRate: SchemaVerifier_1.FieldTypeEnum.BigInt,
322
+ });
323
+ abortController.signal.throwIfAborted();
324
+ if (actualParsedBody == null)
325
+ throw {
326
+ code: 20100,
327
+ msg: "Invalid request body"
328
+ };
329
+ const inputAmountAdjustments = req.paramReader.getExistingParamsOrNull({
330
+ amountUtxos: SchemaVerifier_1.FieldTypeEnum.AnyOptional,
331
+ amountFeeRate: SchemaVerifier_1.FieldTypeEnum.NumberOptional
332
+ });
333
+ if (inputAmountAdjustments == null)
334
+ throw {
335
+ code: 20100,
336
+ msg: "Invalid request body"
337
+ };
338
+ const clientInputUtxos = inputAmountAdjustments?.amountUtxos != null
339
+ ? parseAmountAdjustUtxos(inputAmountAdjustments.amountUtxos)
340
+ : null;
341
+ if (inputAmountAdjustments?.amountUtxos != null && clientInputUtxos == null)
342
+ throw {
343
+ code: 20100,
344
+ msg: "Invalid request body (amountUtxos)"
345
+ };
346
+ const parsedBody = { ...preFetchParsedBody, ...actualParsedBody };
347
+ metadata.request = parsedBody;
348
+ if (parsedBody.gasToken !== chainInterface.getNativeCurrencyAddress())
349
+ throw {
350
+ code: 20190,
351
+ msg: "Unsupported gas token"
352
+ };
353
+ if (parsedBody.callerFeeRate < 0n || parsedBody.callerFeeRate >= 2n ** 20n)
354
+ throw {
355
+ code: 20191,
356
+ msg: "Invalid caller fee rate"
357
+ };
358
+ if (parsedBody.frontingFeeRate < 0n || parsedBody.frontingFeeRate >= 2n ** 20n)
359
+ throw {
360
+ code: 20192,
361
+ msg: "Invalid fronting fee rate"
362
+ };
363
+ const requestedAmount = {
364
+ input: !parsedBody.exactOut,
365
+ amount: parsedBody.exactOut ?
366
+ (parsedBody.amount * (100000n + parsedBody.callerFeeRate + parsedBody.frontingFeeRate) / 100000n) :
367
+ parsedBody.amount,
368
+ token: parsedBody.token
369
+ };
370
+ if (clientInputUtxos != null) {
371
+ if (parsedBody.exactOut)
372
+ throw {
373
+ code: 20193,
374
+ msg: "amountAdjustUtxos cannot be specified for exactOut swaps!"
375
+ };
376
+ let btcFeeRate = await btcFeeRatePrefetch;
377
+ if (inputAmountAdjustments.amountFeeRate != null && inputAmountAdjustments.amountFeeRate > btcFeeRate)
378
+ btcFeeRate = inputAmountAdjustments.amountFeeRate;
379
+ let feeAccumulator = 0;
380
+ let valueAccumulator = 0;
381
+ for (let utxo of clientInputUtxos) {
382
+ const cpfpAdditionalFee = utxo.cpfp == null ? 0 : Math.ceil(utxo.cpfp.effectiveVSize * Math.max(0, btcFeeRate - utxo.cpfp.effectiveFeeRate));
383
+ const spendFee = utxo.vSize * btcFeeRate;
384
+ const totalFee = cpfpAdditionalFee + spendFee;
385
+ if (totalFee > utxo.value)
386
+ continue; //Skip detrimental UTXO
387
+ feeAccumulator += totalFee;
388
+ valueAccumulator += utxo.value;
389
+ }
390
+ let baseTxVSize = 10.5; // 4b version, 1b inputs, 1b outputs, 4b locktime, 0.5vB witness flag + witness elements count
391
+ //vault input and output
392
+ baseTxVSize += 32 + 4 + 1 + 4; //Input base
393
+ baseTxVSize += this.vaultSigner.getAddressType() === "p2tr" ? (1 + 1 + 65) / 4 : (1 + 1 + 72 + 1 + 33) / 4;
394
+ baseTxVSize += 8 + 1; //Output base
395
+ baseTxVSize += this.vaultSigner.getAddressType() === "p2tr" ? 34 : 22;
396
+ //opreturn output
397
+ baseTxVSize += 8 + 1; //Output base
398
+ const opReturnDataSize = spvVaultContract.toOpReturnData(parsedBody.address, parsedBody.gasAmount > 0 ? [0xffffffffffffffffn, 0xffffffffffffffffn] : [0xffffffffffffffffn]).length;
399
+ baseTxVSize += (opReturnDataSize <= 0x4b ? 2 : 3 /*Needs an OP_PUSHDATA1 opcode*/) + opReturnDataSize;
400
+ //LP output
401
+ baseTxVSize += 8 + 1; //Output base
402
+ baseTxVSize += this.bitcoin.getAddressType() === "p2tr" ? 34 : this.bitcoin.getAddressType() === "p2wpkh" ? 22 : 23;
403
+ const baseTxFee = Math.ceil(baseTxVSize) * btcFeeRate;
404
+ feeAccumulator += baseTxFee;
405
+ const amount = Math.floor(valueAccumulator - Math.ceil(feeAccumulator));
406
+ requestedAmount.amount = BigInt(amount);
407
+ }
408
+ const gasTokenAmount = {
409
+ input: false,
410
+ amount: parsedBody.gasAmount * (100000n + parsedBody.callerFeeRate + parsedBody.frontingFeeRate) / 100000n,
411
+ token: parsedBody.gasToken
412
+ };
413
+ const request = {
414
+ chainIdentifier,
415
+ raw: req,
416
+ parsed: parsedBody,
417
+ metadata
418
+ };
419
+ const useToken = parsedBody.token;
420
+ const gasToken = parsedBody.gasToken;
421
+ this.checkTooManyInflightSwaps();
422
+ //Check request params
423
+ const fees = await this.AmountAssertions.preCheckFromBtcAmounts(this.type, request, requestedAmount, gasTokenAmount);
424
+ metadata.times.requestChecked = Date.now();
425
+ await this.checkNativeBalance(chainIdentifier, nativeBalancePrefetch, abortController.signal);
426
+ //Check valid amount specified (min/max)
427
+ let { amountBD, swapFee, swapFeeInToken, totalInToken, amountBDgas, gasSwapFee, gasSwapFeeInToken, totalInGasToken } = await this.AmountAssertions.checkFromBtcAmount(this.type, request, { ...requestedAmount, pricePrefetch: pricePrefetchPromise }, fees, abortController.signal, { ...gasTokenAmount, pricePrefetch: gasTokenPricePrefetchPromise });
428
+ metadata.times.priceCalculated = Date.now();
429
+ const totalBtcOutput = amountBD + amountBDgas;
430
+ //Check if we have enough funds to honor the request
431
+ let vault;
432
+ do {
433
+ vault = await this.Vaults.findVaultForSwap(chainIdentifier, totalBtcOutput, useToken, totalInToken, gasToken, totalInGasToken);
434
+ } while (await this.Vaults.checkVaultReplacedTransactions(vault, true));
435
+ abortController.signal.throwIfAborted();
436
+ metadata.times.vaultPicked = Date.now();
437
+ //Create swap receive bitcoin address
438
+ const btcFeeRate = await btcFeeRatePrefetch;
439
+ const btcAddressObject = await bitcoinAddressPrefetch;
440
+ abortController.signal.throwIfAborted();
441
+ metadata.times.addressCreated = Date.now();
442
+ const receiveAddress = btcAddressObject.address;
443
+ const hasStickyAddress = btcAddressObject.isStickyAddress;
444
+ //Adjust the amounts based on passed fees
445
+ if (parsedBody.exactOut) {
446
+ totalInToken = parsedBody.amount;
447
+ }
448
+ else {
449
+ totalInToken = (totalInToken * 100000n / (100000n + parsedBody.callerFeeRate + parsedBody.frontingFeeRate));
450
+ }
451
+ totalInGasToken = (totalInGasToken * 100000n / (100000n + parsedBody.callerFeeRate + parsedBody.frontingFeeRate));
452
+ //Calculate raw amounts
453
+ const [rawTokenAmount, rawGasTokenAmount] = vault.toRawAmounts([totalInToken, totalInGasToken]);
454
+ [totalInToken, totalInGasToken] = vault.fromRawAmounts([rawTokenAmount, rawGasTokenAmount]);
455
+ const expiry = Math.floor(Date.now() / 1000) + this.getInitAuthorizationTimeout(chainIdentifier);
456
+ //Get PSBT data
457
+ const callerFeeShare = parsedBody.callerFeeRate;
458
+ const frontingFeeShare = parsedBody.frontingFeeRate;
459
+ const executionFeeShare = 0n;
460
+ const utxo = vault.getLatestUtxo();
461
+ const quoteId = (0, crypto_1.randomBytes)(32).toString("hex");
462
+ const swap = new SpvVaultSwap_1.SpvVaultSwap(chainIdentifier, quoteId, expiry, vault, utxo, receiveAddress, btcFeeRate, parsedBody.address, totalBtcOutput, totalInToken, totalInGasToken, swapFee, swapFeeInToken, gasSwapFee, gasSwapFeeInToken, callerFeeShare, frontingFeeShare, executionFeeShare, useToken, gasToken);
463
+ swap.metadata = metadata;
464
+ swap.saveStickyAddress = useStickyAddress && !hasStickyAddress;
465
+ swap.hasStickyAddress = hasStickyAddress;
466
+ //We can remove the listener to add unused address now, as we are about to save the swap
467
+ abortController.signal.removeEventListener("abort", abortAddUnusedAddressListener);
468
+ await PluginManager_1.PluginManager.swapCreate(swap);
469
+ await this.saveSwapData(swap);
470
+ this.swapLogger.info(swap, "REST: /getQuote: Created swap address: " + receiveAddress + " amount: " + totalBtcOutput.toString(10));
471
+ await responseStream.writeParamsAndEnd({
472
+ code: 20000,
473
+ msg: "Success",
474
+ data: {
475
+ quoteId,
476
+ expiry,
477
+ address: signer.getAddress(),
478
+ vaultId: vault.data.getVaultId().toString(10),
479
+ vaultBtcAddress: vault.btcAddress,
480
+ btcAddress: receiveAddress,
481
+ btcUtxo: utxo,
482
+ btcFeeRate,
483
+ btcAmount: totalBtcOutput.toString(10),
484
+ btcAmountSwap: amountBD.toString(10),
485
+ btcAmountGas: amountBDgas.toString(10),
486
+ total: totalInToken.toString(10),
487
+ totalGas: totalInGasToken.toString(10),
488
+ totalFeeBtc: (swapFee + gasSwapFee).toString(10),
489
+ swapFeeBtc: swapFee.toString(10),
490
+ swapFee: swapFeeInToken.toString(10),
491
+ gasSwapFeeBtc: gasSwapFee.toString(10),
492
+ gasSwapFee: gasSwapFeeInToken.toString(10),
493
+ callerFeeShare: callerFeeShare.toString(10),
494
+ frontingFeeShare: frontingFeeShare.toString(10),
495
+ executionFeeShare: executionFeeShare.toString(10),
496
+ usedUtxoInputCalculation: clientInputUtxos != null
497
+ }
498
+ });
499
+ }));
500
+ restServer.use(this.path + "/postQuote", (0, ServerParamDecoder_1.serverParamDecoder)(10 * 1000));
501
+ restServer.post(this.path + "/postQuote", (0, Utils_1.expressHandlerWrapper)(async (req, res) => {
502
+ let requestReceived = Date.now();
503
+ const parsedBody = await req.paramReader.getParams({
504
+ quoteId: SchemaVerifier_1.FieldTypeEnum.String,
505
+ psbtHex: (val) => val != null &&
506
+ typeof (val) === "string" &&
507
+ Utils_1.HEX_REGEX.test(val) ? val : null
508
+ });
509
+ const swap = await this.storageManager.getData(parsedBody.quoteId, 0n);
510
+ if (swap == null || swap.state !== SpvVaultSwap_1.SpvVaultSwapState.CREATED || swap.expiry < Date.now() / 1000)
511
+ throw {
512
+ code: 20505,
513
+ msg: "Invalid quote ID, not found or expired!"
514
+ };
515
+ const metadata = swap.metadata;
516
+ metadata.times ?? (metadata.times = {});
517
+ metadata.times.requestReceived = requestReceived;
518
+ this.checkTooManyInflightSwaps();
519
+ const vault = await this.Vaults.getVault(swap.chainIdentifier, swap.vaultOwner, swap.vaultId);
520
+ if (vault == null || !vault.isReady()) {
521
+ throw {
522
+ code: 20506,
523
+ msg: "Used vault not found!"
524
+ };
525
+ }
526
+ //Try parse psbt
527
+ let transaction;
528
+ try {
529
+ transaction = btc_signer_1.Transaction.fromPSBT(Buffer.from(parsedBody.psbtHex, "hex"));
530
+ }
531
+ catch (e) {
532
+ this.swapLogger.error(swap, "REST: /postQuote: failed to parse provided PSBT: ", e);
533
+ throw {
534
+ code: 20507,
535
+ msg: "Error parsing PSBT, hex format required!"
536
+ };
537
+ }
538
+ for (let i = 1; i < transaction.inputsLength; i++) { //Skip first vault input
539
+ const txIn = transaction.getInput(i);
540
+ if ((0, BitcoinUtils_1.isLegacyInput)(txIn))
541
+ throw {
542
+ code: 20514,
543
+ msg: "Legacy (pre-segwit) inputs in tx are not allowed!"
544
+ };
545
+ }
546
+ //Check the posted quote with the plugins
547
+ AmountAssertions_1.AmountAssertions.handlePluginErrorResponses(await PluginManager_1.PluginManager.onHandlePostedFromBtcQuote(this.type, { chainIdentifier: swap.chainIdentifier, raw: req, parsed: parsedBody, metadata }, swap));
548
+ //Check correct psbt
549
+ for (let i = 1; i < transaction.inputsLength; i++) { //Skip first vault input
550
+ const txIn = transaction.getInput(i);
551
+ //Check UTXOs exist and are unspent
552
+ if (await this.bitcoinRpc.isSpent(Buffer.from(txIn.txid).toString("hex") + ":" + txIn.index.toString(10)))
553
+ throw {
554
+ code: 20515,
555
+ msg: "Spent UTXO in inputs!"
556
+ };
557
+ }
558
+ const { spvVaultContract } = this.getChain(swap.chainIdentifier);
559
+ let data;
560
+ try {
561
+ data = await spvVaultContract.getWithdrawalData((0, Utils_1.parsePsbt)(transaction));
562
+ }
563
+ catch (e) {
564
+ this.swapLogger.error(swap, "REST: /postQuote: failed to parse PSBT to withdrawal tx data: ", e);
565
+ throw {
566
+ code: 20508,
567
+ msg: "PSBT transaction cannot be parsed!"
568
+ };
569
+ }
570
+ if (!data.isRecipient(swap.recipient) ||
571
+ data.callerFeeRate !== swap.callerFeeShare ||
572
+ data.frontingFeeRate !== swap.frontingFeeShare ||
573
+ data.executionFeeRate !== swap.executionFeeShare ||
574
+ data.rawAmounts[0] !== swap.rawAmountToken ||
575
+ data.rawAmounts[1] !== swap.rawAmountGasToken ||
576
+ data.getExecutionData() != null ||
577
+ data.getSpentVaultUtxo() !== swap.vaultUtxo ||
578
+ data.btcTx.outs[0].value !== SpvVaults_1.VAULT_DUST_AMOUNT ||
579
+ !Buffer.from(data.btcTx.outs[0].scriptPubKey.hex, "hex").equals(this.bitcoin.toOutputScript(swap.vaultAddress)) ||
580
+ BigInt(data.btcTx.outs[2].value) !== swap.amountBtc ||
581
+ !Buffer.from(data.btcTx.outs[2].scriptPubKey.hex, "hex").equals(this.bitcoin.toOutputScript(swap.btcAddress)) ||
582
+ (data.btcTx.locktime > 0 && data.btcTx.locktime < 500000000) ||
583
+ data.btcTx.locktime > Math.floor(Date.now() / 1000) - 1000000) {
584
+ this.swapLogger.error(swap, "REST: /postQuote: Invalid psbt data submitted, raw psbt hex: ", parsedBody.psbtHex);
585
+ throw {
586
+ code: 20509,
587
+ msg: "Invalid PSBT provided!"
588
+ };
589
+ }
590
+ if (swap.vaultUtxo !== vault.getLatestUtxo()) {
591
+ throw {
592
+ code: 20510,
593
+ msg: "Vault UTXO already spent, please get another quote and try again!"
594
+ };
595
+ }
596
+ //Create abortController for parallel prefetches
597
+ const responseStream = res.responseStream;
598
+ const signedTx = await this.vaultSigner.signPsbt(swap.chainIdentifier, swap.vaultId, transaction, [0]);
599
+ if (!signedTx.isFinal)
600
+ throw {
601
+ code: 20513,
602
+ msg: "One or more PSBT inputs not finalized!"
603
+ };
604
+ const effectiveFeeRate = await this.bitcoinRpc.getEffectiveFeeRate((0, Utils_1.parsePsbt)(signedTx));
605
+ if (effectiveFeeRate.feeRate < 1 || Math.round(effectiveFeeRate.feeRate) < swap.btcFeeRate)
606
+ throw {
607
+ code: 20511,
608
+ msg: "Bitcoin transaction fee too low, expected minimum: " + swap.btcFeeRate + " adjusted effective fee rate: " + effectiveFeeRate.feeRate
609
+ };
610
+ const txVsize = signedTx.vsize;
611
+ if (txVsize > TX_MAX_VSIZE)
612
+ throw {
613
+ code: 20516,
614
+ msg: "Bitcoin transaction size too large, maximum: " + TX_MAX_VSIZE + " actual: " + txVsize
615
+ };
616
+ const pluginCheckResult = await PluginManager_1.PluginManager.onHandlePreFromBtcExecute(SwapHandler_1.SwapHandlerType.FROM_BTC_SPV, swap);
617
+ if ((0, IPlugin_1.isQuoteThrow)(pluginCheckResult)) {
618
+ const error = {
619
+ code: 29999,
620
+ msg: pluginCheckResult.message
621
+ };
622
+ if (swap.metadata != null)
623
+ swap.metadata.postQuoteError = error;
624
+ await this.removeSwapData(swap, SpvVaultSwap_1.SpvVaultSwapState.FAILED);
625
+ throw error;
626
+ }
627
+ await this.Vaults.checkVaultReplacedTransactions(vault, true);
628
+ if (swap.vaultUtxo !== vault.getLatestUtxo()) {
629
+ throw {
630
+ code: 20510,
631
+ msg: "Vault UTXO already spent, please get another quote and try again!"
632
+ };
633
+ }
634
+ try {
635
+ const btcRawTx = Buffer.from(signedTx.toBytes(true, true)).toString("hex");
636
+ //Double-check the state to prevent race condition
637
+ if (swap.state !== SpvVaultSwap_1.SpvVaultSwapState.CREATED) {
638
+ throw {
639
+ code: 20505,
640
+ msg: "Invalid quote ID, not found or expired!"
641
+ };
642
+ }
643
+ //Double check in-flight swap count
644
+ this.checkTooManyInflightSwaps();
645
+ swap.btcTxId = signedTx.id;
646
+ swap.state = SpvVaultSwap_1.SpvVaultSwapState.SIGNED;
647
+ swap.sending = true;
648
+ await this.saveSwapData(swap);
649
+ data.btcTx.raw = btcRawTx;
650
+ data.sending = true;
651
+ vault.addWithdrawal(data);
652
+ await this.Vaults.saveVault(vault);
653
+ this.swapLogger.info(swap, "REST: /postQuote: BTC transaction signed, txId: " + swap.btcTxId);
654
+ try {
655
+ await this.bitcoin.sendRawTransaction(btcRawTx);
656
+ await swap.setState(SpvVaultSwap_1.SpvVaultSwapState.SENT);
657
+ data.sending = false;
658
+ swap.sending = false;
659
+ }
660
+ catch (e) {
661
+ this.swapLogger.error(swap, "REST: /postQuote: Failed to send BTC transaction: ", e);
662
+ throw {
663
+ code: 20512,
664
+ msg: "Error broadcasting bitcoin transaction!"
665
+ };
666
+ }
667
+ }
668
+ catch (e) {
669
+ data.sending = false;
670
+ swap.sending = false;
671
+ vault.removeWithdrawal(data);
672
+ await this.Vaults.saveVault(vault);
673
+ if ((0, Utils_1.isDefinedRuntimeError)(e) && swap.metadata != null)
674
+ swap.metadata.postQuoteError = e;
675
+ await this.removeSwapData(swap, SpvVaultSwap_1.SpvVaultSwapState.FAILED);
676
+ throw e;
677
+ }
678
+ await responseStream.writeParamsAndEnd({
679
+ code: 20000,
680
+ msg: "Success",
681
+ data: {
682
+ txId: swap.btcTxId
683
+ }
684
+ });
685
+ }));
686
+ }
687
+ getInfoData() {
688
+ const mappedDict = {};
689
+ for (let chainId in this.config.gasTokenMax) {
690
+ mappedDict[chainId] = {
691
+ gasToken: this.getChain(chainId).chainInterface.getNativeCurrencyAddress(),
692
+ max: this.config.gasTokenMax[chainId].toString(10)
693
+ };
694
+ }
695
+ return {
696
+ gasTokens: mappedDict
697
+ };
698
+ }
699
+ async saveSwapData(swap) {
700
+ if (swap.btcTxId != null)
701
+ this.btcTxIdIndex.set(swap.btcTxId, swap);
702
+ return super.saveSwapData(swap);
703
+ }
704
+ async removeSwapData(swap, ultimateState) {
705
+ if (swap.btcTxId != null)
706
+ this.btcTxIdIndex.delete(swap.btcTxId);
707
+ return super.removeSwapData(swap, ultimateState);
708
+ }
709
+ }
710
+ exports.SpvVaultSwapHandler = SpvVaultSwapHandler;