@atomiqlabs/lp-lib 14.0.0-dev.11 → 14.0.0-dev.13

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 (164) hide show
  1. package/LICENSE +201 -201
  2. package/dist/fees/IBtcFeeEstimator.d.ts +3 -3
  3. package/dist/fees/IBtcFeeEstimator.js +2 -2
  4. package/dist/index.d.ts +42 -42
  5. package/dist/index.js +58 -58
  6. package/dist/info/InfoHandler.d.ts +17 -17
  7. package/dist/info/InfoHandler.js +61 -61
  8. package/dist/plugins/IPlugin.d.ts +143 -143
  9. package/dist/plugins/IPlugin.js +34 -34
  10. package/dist/plugins/PluginManager.d.ts +112 -112
  11. package/dist/plugins/PluginManager.js +259 -259
  12. package/dist/prices/BinanceSwapPrice.d.ts +26 -26
  13. package/dist/prices/BinanceSwapPrice.js +92 -92
  14. package/dist/prices/CoinGeckoSwapPrice.d.ts +30 -30
  15. package/dist/prices/CoinGeckoSwapPrice.js +64 -64
  16. package/dist/prices/ISwapPrice.d.ts +43 -43
  17. package/dist/prices/ISwapPrice.js +55 -55
  18. package/dist/prices/OKXSwapPrice.d.ts +26 -26
  19. package/dist/prices/OKXSwapPrice.js +92 -92
  20. package/dist/storage/IIntermediaryStorage.d.ts +18 -18
  21. package/dist/storage/IIntermediaryStorage.js +2 -2
  22. package/dist/storagemanager/IntermediaryStorageManager.d.ts +19 -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 +153 -153
  27. package/dist/swaps/SwapHandler.js +160 -160
  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 +72 -72
  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 +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 +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 +675 -648
  56. package/dist/swaps/escrow/frombtcln_abstract/FromBtcLnSwapAbs.d.ts +33 -33
  57. package/dist/swaps/escrow/frombtcln_abstract/FromBtcLnSwapAbs.js +91 -91
  58. package/dist/swaps/escrow/frombtcln_autoinit/FromBtcLnAuto.d.ts +104 -104
  59. package/dist/swaps/escrow/frombtcln_autoinit/FromBtcLnAuto.js +659 -629
  60. package/dist/swaps/escrow/frombtcln_autoinit/FromBtcLnAutoSwap.d.ts +55 -55
  61. package/dist/swaps/escrow/frombtcln_autoinit/FromBtcLnAutoSwap.js +120 -120
  62. package/dist/swaps/escrow/tobtc_abstract/ToBtcAbs.d.ts +171 -171
  63. package/dist/swaps/escrow/tobtc_abstract/ToBtcAbs.js +706 -706
  64. package/dist/swaps/escrow/tobtc_abstract/ToBtcSwapAbs.d.ts +26 -26
  65. package/dist/swaps/escrow/tobtc_abstract/ToBtcSwapAbs.js +62 -62
  66. package/dist/swaps/escrow/tobtcln_abstract/ToBtcLnAbs.d.ts +177 -177
  67. package/dist/swaps/escrow/tobtcln_abstract/ToBtcLnAbs.js +861 -861
  68. package/dist/swaps/escrow/tobtcln_abstract/ToBtcLnSwapAbs.d.ts +23 -23
  69. package/dist/swaps/escrow/tobtcln_abstract/ToBtcLnSwapAbs.js +56 -56
  70. package/dist/swaps/spv_vault_swap/SpvVault.d.ts +41 -41
  71. package/dist/swaps/spv_vault_swap/SpvVault.js +111 -111
  72. package/dist/swaps/spv_vault_swap/SpvVaultSwap.d.ts +67 -67
  73. package/dist/swaps/spv_vault_swap/SpvVaultSwap.js +158 -158
  74. package/dist/swaps/spv_vault_swap/SpvVaultSwapHandler.d.ts +68 -68
  75. package/dist/swaps/spv_vault_swap/SpvVaultSwapHandler.js +491 -490
  76. package/dist/swaps/spv_vault_swap/SpvVaults.d.ts +52 -52
  77. package/dist/swaps/spv_vault_swap/SpvVaults.js +364 -364
  78. package/dist/swaps/trusted/frombtc_trusted/FromBtcTrusted.d.ts +51 -51
  79. package/dist/swaps/trusted/frombtc_trusted/FromBtcTrusted.js +650 -650
  80. package/dist/swaps/trusted/frombtc_trusted/FromBtcTrustedSwap.d.ts +52 -52
  81. package/dist/swaps/trusted/frombtc_trusted/FromBtcTrustedSwap.js +118 -118
  82. package/dist/swaps/trusted/frombtcln_trusted/FromBtcLnTrusted.d.ts +76 -76
  83. package/dist/swaps/trusted/frombtcln_trusted/FromBtcLnTrusted.js +494 -494
  84. package/dist/swaps/trusted/frombtcln_trusted/FromBtcLnTrustedSwap.d.ts +34 -34
  85. package/dist/swaps/trusted/frombtcln_trusted/FromBtcLnTrustedSwap.js +81 -81
  86. package/dist/utils/Utils.d.ts +29 -29
  87. package/dist/utils/Utils.js +89 -89
  88. package/dist/utils/paramcoders/IParamReader.d.ts +5 -5
  89. package/dist/utils/paramcoders/IParamReader.js +2 -2
  90. package/dist/utils/paramcoders/IParamWriter.d.ts +4 -4
  91. package/dist/utils/paramcoders/IParamWriter.js +2 -2
  92. package/dist/utils/paramcoders/LegacyParamEncoder.d.ts +10 -10
  93. package/dist/utils/paramcoders/LegacyParamEncoder.js +22 -22
  94. package/dist/utils/paramcoders/ParamDecoder.d.ts +25 -25
  95. package/dist/utils/paramcoders/ParamDecoder.js +222 -222
  96. package/dist/utils/paramcoders/ParamEncoder.d.ts +9 -9
  97. package/dist/utils/paramcoders/ParamEncoder.js +22 -22
  98. package/dist/utils/paramcoders/SchemaVerifier.d.ts +21 -21
  99. package/dist/utils/paramcoders/SchemaVerifier.js +84 -84
  100. package/dist/utils/paramcoders/server/ServerParamDecoder.d.ts +8 -8
  101. package/dist/utils/paramcoders/server/ServerParamDecoder.js +107 -107
  102. package/dist/utils/paramcoders/server/ServerParamEncoder.d.ts +11 -11
  103. package/dist/utils/paramcoders/server/ServerParamEncoder.js +65 -65
  104. package/dist/wallets/IBitcoinWallet.d.ts +67 -67
  105. package/dist/wallets/IBitcoinWallet.js +2 -2
  106. package/dist/wallets/ILightningWallet.d.ts +117 -117
  107. package/dist/wallets/ILightningWallet.js +37 -37
  108. package/dist/wallets/ISpvVaultSigner.d.ts +7 -7
  109. package/dist/wallets/ISpvVaultSigner.js +2 -2
  110. package/package.json +36 -36
  111. package/src/fees/IBtcFeeEstimator.ts +6 -6
  112. package/src/index.ts +53 -53
  113. package/src/info/InfoHandler.ts +106 -106
  114. package/src/plugins/IPlugin.ts +168 -168
  115. package/src/plugins/PluginManager.ts +336 -336
  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 +118 -118
  122. package/src/storagemanager/StorageManager.ts +78 -78
  123. package/src/swaps/SwapHandler.ts +277 -277
  124. package/src/swaps/SwapHandlerSwap.ts +141 -141
  125. package/src/swaps/assertions/AmountAssertions.ts +76 -76
  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 +286 -286
  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 +856 -828
  138. package/src/swaps/escrow/frombtcln_abstract/FromBtcLnSwapAbs.ts +141 -141
  139. package/src/swaps/escrow/frombtcln_autoinit/FromBtcLnAuto.ts +822 -789
  140. package/src/swaps/escrow/frombtcln_autoinit/FromBtcLnAutoSwap.ts +196 -196
  141. package/src/swaps/escrow/tobtc_abstract/ToBtcAbs.ts +879 -879
  142. package/src/swaps/escrow/tobtc_abstract/ToBtcSwapAbs.ts +102 -102
  143. package/src/swaps/escrow/tobtcln_abstract/ToBtcLnAbs.ts +1110 -1110
  144. package/src/swaps/escrow/tobtcln_abstract/ToBtcLnSwapAbs.ts +77 -77
  145. package/src/swaps/spv_vault_swap/SpvVault.ts +143 -143
  146. package/src/swaps/spv_vault_swap/SpvVaultSwap.ts +225 -225
  147. package/src/swaps/spv_vault_swap/SpvVaultSwapHandler.ts +627 -626
  148. package/src/swaps/spv_vault_swap/SpvVaults.ts +435 -435
  149. package/src/swaps/trusted/frombtc_trusted/FromBtcTrusted.ts +747 -747
  150. package/src/swaps/trusted/frombtc_trusted/FromBtcTrustedSwap.ts +185 -185
  151. package/src/swaps/trusted/frombtcln_trusted/FromBtcLnTrusted.ts +590 -590
  152. package/src/swaps/trusted/frombtcln_trusted/FromBtcLnTrustedSwap.ts +121 -121
  153. package/src/utils/Utils.ts +104 -104
  154. package/src/utils/paramcoders/IParamReader.ts +7 -7
  155. package/src/utils/paramcoders/IParamWriter.ts +8 -8
  156. package/src/utils/paramcoders/LegacyParamEncoder.ts +27 -27
  157. package/src/utils/paramcoders/ParamDecoder.ts +218 -218
  158. package/src/utils/paramcoders/ParamEncoder.ts +29 -29
  159. package/src/utils/paramcoders/SchemaVerifier.ts +96 -96
  160. package/src/utils/paramcoders/server/ServerParamDecoder.ts +118 -118
  161. package/src/utils/paramcoders/server/ServerParamEncoder.ts +75 -75
  162. package/src/wallets/IBitcoinWallet.ts +68 -68
  163. package/src/wallets/ILightningWallet.ts +178 -178
  164. package/src/wallets/ISpvVaultSigner.ts +10 -10
@@ -1,436 +1,436 @@
1
- import {SpvVault, SpvVaultState} from "./SpvVault";
2
- import {
3
- BitcoinRpc,
4
- IStorageManager,
5
- SpvVaultClaimEvent,
6
- SpvVaultCloseEvent,
7
- SpvVaultDepositEvent,
8
- SpvVaultOpenEvent, SpvWithdrawalTransactionData
9
- } from "@atomiqlabs/base";
10
- import {SpvVaultSwap} from "./SpvVaultSwap";
11
- import {bigIntSorter, getLogger} from "../../utils/Utils";
12
- import {PluginManager} from "../../plugins/PluginManager";
13
- import {IBitcoinWallet} from "../../wallets/IBitcoinWallet";
14
- import {ISpvVaultSigner} from "../../wallets/ISpvVaultSigner";
15
- import {AmountAssertions} from "../assertions/AmountAssertions";
16
- import {ChainData} from "../SwapHandler";
17
- import {Transaction} from "@scure/btc-signer";
18
-
19
- export const VAULT_DUST_AMOUNT = 600;
20
- const VAULT_INIT_CONFIRMATIONS = 2;
21
- const BTC_FINALIZATION_CONFIRMATIONS = 6;
22
-
23
- export class SpvVaults {
24
-
25
- readonly vaultStorage: IStorageManager<SpvVault>;
26
-
27
- readonly bitcoin: IBitcoinWallet;
28
- readonly vaultSigner: ISpvVaultSigner;
29
- readonly bitcoinRpc: BitcoinRpc<any>;
30
- readonly config: {vaultsCheckInterval: number, maxUnclaimedWithdrawals?: number};
31
- readonly getChain: (chainId: string) => ChainData
32
-
33
- readonly logger = getLogger("SpvVaults: ");
34
-
35
- constructor(
36
- vaultStorage: IStorageManager<SpvVault>,
37
- bitcoin: IBitcoinWallet,
38
- vaultSigner: ISpvVaultSigner,
39
- bitcoinRpc: BitcoinRpc<any>,
40
- getChain: (chainId: string) => ChainData,
41
- config: {vaultsCheckInterval: number, maxUnclaimedWithdrawals?: number}
42
- ) {
43
- this.vaultStorage = vaultStorage;
44
- this.bitcoin = bitcoin;
45
- this.vaultSigner = vaultSigner;
46
- this.bitcoinRpc = bitcoinRpc;
47
- this.getChain = getChain;
48
- this.config = config;
49
- }
50
-
51
- async processDepositEvent(vault: SpvVault, event: SpvVaultDepositEvent): Promise<void> {
52
- vault.update(event);
53
- await this.saveVault(vault);
54
- }
55
-
56
- async processOpenEvent(vault: SpvVault, event: SpvVaultOpenEvent): Promise<void> {
57
- if(vault.state===SpvVaultState.BTC_CONFIRMED) {
58
- vault.state = SpvVaultState.OPENED;
59
- }
60
- vault.update(event);
61
- await this.saveVault(vault);
62
- }
63
-
64
- async processCloseEvent(vault: SpvVault, event: SpvVaultCloseEvent): Promise<void> {
65
- if(vault.state===SpvVaultState.OPENED) {
66
- vault.state = SpvVaultState.CLOSED;
67
- }
68
- vault.update(event);
69
- await this.saveVault(vault);
70
- }
71
-
72
- async processClaimEvent(vault: SpvVault, swap: SpvVaultSwap | null, event: SpvVaultClaimEvent): Promise<void> {
73
- //Update vault
74
- const foundPendingWithdrawal = vault.pendingWithdrawals.findIndex(val => val.btcTx.txid===event.btcTxId);
75
- if(foundPendingWithdrawal!==-1) vault.pendingWithdrawals.splice(foundPendingWithdrawal, 1);
76
- vault.update(event);
77
- await this.saveVault(vault);
78
- }
79
-
80
- async createVaults(chainId: string, count: number, token: string, confirmations: number = 2, feeRate?: number): Promise<{vaultsCreated: bigint[], btcTxId: string}> {
81
- const {signer, chainInterface, tokenMultipliers, spvVaultContract} = this.getChain(chainId);
82
-
83
- const signerAddress = signer.getAddress();
84
-
85
- //Check vaultId of the latest saved vault
86
- let latestVaultId: bigint = -1n;
87
- for(let key in this.vaultStorage.data) {
88
- const vault = this.vaultStorage.data[key];
89
- if(vault.chainId!==chainId) continue;
90
- if(vault.data.getOwner()!==signerAddress) continue;
91
- if(vault.data.getVaultId() > latestVaultId) latestVaultId = vault.data.getVaultId();
92
- }
93
-
94
- latestVaultId++;
95
-
96
- const vaultAddreses: {vaultId: bigint, address: string}[] = [];
97
- for(let i=0;i<count;i++) {
98
- const vaultId = latestVaultId + BigInt(i);
99
- const address = await this.vaultSigner.getAddress(chainId, vaultId);
100
- vaultAddreses.push({vaultId, address});
101
- }
102
-
103
- //Construct transaction
104
- const txResult = await this.bitcoin.getSignedMultiTransaction(vaultAddreses.map(val => {
105
- return {address: val.address, amount: VAULT_DUST_AMOUNT}
106
- }), feeRate);
107
-
108
- const nativeToken = chainInterface.getNativeCurrencyAddress();
109
-
110
- const vaults = await Promise.all(vaultAddreses.map(async (val, index) => {
111
- const vaultData = await spvVaultContract.createVaultData(signerAddress, val.vaultId, txResult.txId+":"+index, confirmations, [
112
- {token, multiplier: tokenMultipliers?.[token] ?? 1n},
113
- {token: nativeToken, multiplier: tokenMultipliers?.[nativeToken] ?? 1n}
114
- ]);
115
- return new SpvVault(chainId, vaultData, val.address);
116
- }));
117
-
118
- //Save vaults
119
- if(this.vaultStorage.saveDataArr!=null) {
120
- await this.vaultStorage.saveDataArr(vaults.map(val => {
121
- return {id: val.getIdentifier(), object: val}
122
- }));
123
- } else {
124
- for(let vault of vaults) {
125
- await this.vaultStorage.saveData(vault.getIdentifier(), vault);
126
- }
127
- }
128
-
129
- //Send bitcoin tx
130
- await this.bitcoin.sendRawTransaction(txResult.raw);
131
-
132
- this.logger.info("createVaults(): Funding "+count+" vaults, bitcoin txId: "+txResult.txId);
133
-
134
- return {
135
- vaultsCreated: vaults.map(val => val.data.getVaultId()),
136
- btcTxId: txResult.txId
137
- };
138
- }
139
-
140
- async listVaults(chainId?: string, token?: string) {
141
- return Object.keys(this.vaultStorage.data)
142
- .map(key => this.vaultStorage.data[key])
143
- .filter(val => chainId==null ? true : val.chainId===chainId)
144
- .filter(val => val.data.getOwner()===this.getChain(val.chainId)?.signer?.getAddress())
145
- .filter(val => token==null ? true : val.data.getTokenData()[0].token===token);
146
- }
147
-
148
- async fundVault(vault: SpvVault, tokenAmounts: bigint[]): Promise<string> {
149
- if(vault.state!==SpvVaultState.OPENED) throw new Error("Vault not opened!");
150
-
151
- this.logger.info("fundVault(): Depositing tokens to the vault "+vault.data.getVaultId().toString(10)+", amounts: "+tokenAmounts.map(val => val.toString(10)).join(", "));
152
-
153
- const {signer, spvVaultContract} = this.getChain(vault.chainId);
154
-
155
- const txId = await spvVaultContract.deposit(signer, vault.data, tokenAmounts, {waitForConfirmation: true});
156
-
157
- this.logger.info("fundVault(): Tokens deposited to vault "+vault.data.getVaultId().toString(10)+", amounts: "+tokenAmounts.map(val => val.toString(10)).join(", ")+", txId: "+txId);
158
-
159
- return txId;
160
- }
161
-
162
- async withdrawFromVault(vault: SpvVault, tokenAmounts: bigint[], feeRate?: number): Promise<string> {
163
- tokenAmounts.forEach((rawAmount, index) => {
164
- if(vault.balances[index]==null) throw new Error("Token not found in the vault");
165
- if(vault.balances[index].rawAmount<rawAmount) throw new Error("Not enough balance in the vault");
166
- });
167
-
168
- if(!vault.isReady()) throw new Error("Vault not ready, wait for the latest swap to get at least 1 confirmation!");
169
-
170
- const {signer, spvVaultContract} = this.getChain(vault.chainId);
171
-
172
- const latestUtxo = vault.getLatestUtxo();
173
- const [txId, voutStr] = latestUtxo.split(":");
174
-
175
- const opReturnData = spvVaultContract.toOpReturnData(signer.getAddress(), tokenAmounts);
176
- let opReturnScript: Buffer;
177
- if(opReturnData.length<76) {
178
- opReturnScript = Buffer.concat([
179
- Buffer.from([0x6a, opReturnData.length]),
180
- opReturnData
181
- ]);
182
- } else {
183
- opReturnScript = Buffer.concat([
184
- Buffer.from([0x6a, 0x4c, opReturnData.length]),
185
- opReturnData
186
- ]);
187
- }
188
-
189
- let psbt = new Transaction({
190
- allowUnknownOutputs: true
191
- });
192
- psbt.addInput({
193
- txid: txId,
194
- index: parseInt(voutStr),
195
- witnessUtxo: {
196
- amount: BigInt(VAULT_DUST_AMOUNT),
197
- script: this.bitcoin.toOutputScript(vault.btcAddress)
198
- }
199
- });
200
- psbt.addOutput({
201
- amount: BigInt(VAULT_DUST_AMOUNT),
202
- script: this.bitcoin.toOutputScript(vault.btcAddress)
203
- });
204
- psbt.addOutput({
205
- amount: 0n,
206
- script: opReturnScript
207
- });
208
-
209
- psbt = await this.bitcoin.fundPsbt(psbt, feeRate);
210
- if(psbt.inputsLength<2) throw new Error("PSBT needs at least 2 inputs!");
211
- psbt.updateInput(0, {sequence: 0x80000000});
212
- psbt.updateInput(1, {sequence: 0x80000000});
213
- psbt = await this.vaultSigner.signPsbt(vault.chainId, vault.data.getVaultId(), psbt, [0]);
214
- const res = await this.bitcoin.signPsbt(psbt);
215
-
216
- const parsedTransaction = await this.bitcoinRpc.parseTransaction(res.raw);
217
- const withdrawalData = await spvVaultContract.getWithdrawalData(parsedTransaction);
218
-
219
- if(withdrawalData.getSpentVaultUtxo()!==vault.getLatestUtxo()) {
220
- throw new Error("Latest vault UTXO already spent! Please try again later.");
221
- }
222
- vault.addWithdrawal(withdrawalData);
223
- await this.saveVault(vault);
224
-
225
- try {
226
- await this.bitcoin.sendRawTransaction(res.raw);
227
- } catch (e) {
228
- vault.removeWithdrawal(withdrawalData);
229
- await this.saveVault(vault);
230
- throw e;
231
- }
232
-
233
- return res.txId;
234
- }
235
-
236
- async checkVaults() {
237
- const vaults = Object.keys(this.vaultStorage.data).map(key => this.vaultStorage.data[key]);
238
-
239
- const claimWithdrawals: {vault: SpvVault, withdrawals: SpvWithdrawalTransactionData[]}[] = [];
240
-
241
- for(let vault of vaults) {
242
- const {signer, spvVaultContract, chainInterface} = this.getChain(vault.chainId);
243
- if(vault.data.getOwner()!==signer.getAddress()) continue;
244
-
245
- if(vault.state===SpvVaultState.BTC_INITIATED) {
246
- //Check if btc tx confirmed
247
- const txId = vault.initialUtxo.split(":")[0];
248
- const btcTx = await this.bitcoinRpc.getTransaction(txId);
249
- if(btcTx.confirmations >= VAULT_INIT_CONFIRMATIONS) {
250
- //Double-check the state here to prevent race condition
251
- if(vault.state===SpvVaultState.BTC_INITIATED) {
252
- vault.state = SpvVaultState.BTC_CONFIRMED;
253
- await this.saveVault(vault);
254
- }
255
- this.logger.info("checkVaults(): Vault ID "+vault.data.getVaultId().toString(10)+" confirmed on bitcoin, opening vault on "+vault.chainId);
256
- }
257
- }
258
-
259
- if(vault.state===SpvVaultState.BTC_CONFIRMED) {
260
- //Check if open txs were sent already
261
- if(vault.scOpenTx!=null) {
262
- //Check if confirmed
263
- const status = await chainInterface.getTxStatus(vault.scOpenTx.rawTx);
264
- if(status==="pending") return;
265
- if(status==="success") {
266
- vault.state = SpvVaultState.OPENED;
267
- await this.saveVault(vault);
268
- return;
269
- }
270
- }
271
-
272
- const txs = await spvVaultContract.txsOpen(signer.getAddress(), vault.data);
273
- let numTx = 0;
274
- const txIds = await chainInterface.sendAndConfirm(
275
- signer, txs, true, undefined, false,
276
- async (txId: string, rawTx: string) => {
277
- numTx++;
278
- if(numTx===txs.length) {
279
- //Final tx
280
- vault.scOpenTx = {txId, rawTx};
281
- await this.saveVault(vault);
282
- }
283
- }
284
- );
285
- this.logger.info("checkVaults(): Vault ID "+vault.data.getVaultId().toString(10)+" opened on "+vault.chainId+" txId: "+txIds.join(", "));
286
-
287
- vault.state = SpvVaultState.OPENED;
288
- await this.saveVault(vault);
289
- }
290
-
291
- if(vault.state===SpvVaultState.OPENED) {
292
- let changed = false;
293
- //Check if some of the pendingWithdrawals got confirmed
294
- let latestOwnWithdrawalIndex = -1;
295
- let latestConfirmedWithdrawalIndex = -1;
296
- for(let i=0; i<vault.pendingWithdrawals.length; i++) {
297
- const pendingWithdrawal = vault.pendingWithdrawals[i];
298
- //Check all the pending withdrawals that were not finalized yet
299
- if(pendingWithdrawal.btcTx.confirmations==null || pendingWithdrawal.btcTx.confirmations < BTC_FINALIZATION_CONFIRMATIONS) {
300
- const btcTx = await this.bitcoinRpc.getTransaction(pendingWithdrawal.btcTx.txid);
301
- if(btcTx==null) {
302
- //Probable double-spend, remove from pending withdrawals
303
- const index = vault.pendingWithdrawals.indexOf(pendingWithdrawal);
304
- if(index===-1) {
305
- this.logger.warn("checkVaults(): Tried to remove pending withdrawal txId: "+pendingWithdrawal.btcTx.txid+", but doesn't exist anymore!")
306
- } else {
307
- vault.pendingWithdrawals.splice(index, 1);
308
- }
309
- changed = true;
310
- } else {
311
- //Update confirmations count
312
- if(
313
- pendingWithdrawal.btcTx.confirmations !== btcTx.confirmations ||
314
- pendingWithdrawal.btcTx.blockhash !== btcTx.blockhash
315
- ) {
316
- pendingWithdrawal.btcTx.confirmations = btcTx.confirmations;
317
- pendingWithdrawal.btcTx.blockhash = btcTx.blockhash;
318
- changed = true;
319
- }
320
- }
321
- }
322
- //Check it has enough confirmations
323
- if(pendingWithdrawal.btcTx.confirmations >= vault.data.getConfirmations()) {
324
- latestConfirmedWithdrawalIndex = i;
325
- //Check if the pending withdrawals contain a withdrawal to our own address
326
- if (pendingWithdrawal.isRecipient(signer.getAddress())) {
327
- latestOwnWithdrawalIndex = i;
328
- }
329
- }
330
- }
331
- if(changed) {
332
- await this.saveVault(vault);
333
- }
334
- if(this.config.maxUnclaimedWithdrawals!=null && latestConfirmedWithdrawalIndex+1 >= this.config.maxUnclaimedWithdrawals) {
335
- this.logger.info("checkVaults(): Processing withdrawals by self, because a lot of them are unclaimed!");
336
- claimWithdrawals.push({vault, withdrawals: vault.pendingWithdrawals.slice(0, latestConfirmedWithdrawalIndex+1)});
337
- } else if(latestOwnWithdrawalIndex!==-1) {
338
- claimWithdrawals.push({vault, withdrawals: vault.pendingWithdrawals.slice(0, latestOwnWithdrawalIndex+1)});
339
- }
340
- }
341
- }
342
-
343
- for(let {vault, withdrawals} of claimWithdrawals) {
344
- if(!await this.claimWithdrawals(vault, withdrawals)) {
345
- this.logger.error("checkVaults(): Cannot process withdrawals "+withdrawals.map(val => val.btcTx.txid).join(", ")+" for vault: "+vault.data.getVaultId());
346
- break;
347
- }
348
- }
349
- }
350
-
351
- async claimWithdrawals(vault: SpvVault, withdrawal: SpvWithdrawalTransactionData[]): Promise<boolean> {
352
- const {signer, spvVaultContract} = this.getChain(vault.chainId);
353
-
354
- try {
355
- const txId = await spvVaultContract.claim(signer, vault.data, withdrawal.map(tx => {
356
- return {tx};
357
- }), undefined, true, {waitForConfirmation: true});
358
- this.logger.info("claimWithdrawal(): Successfully claimed withdrawals, btcTxIds: "+withdrawal.map(val => val.btcTx.txid).join(", ")+" smartChainTxId: "+txId);
359
- return true;
360
- } catch (e) {
361
- this.logger.error("claimWithdrawal(): Tried to claim but got error: ", e);
362
- return false;
363
- }
364
- }
365
-
366
- async getVault(chainId: string, owner: string, vaultId: bigint) {
367
- return this.vaultStorage.data[chainId+"_"+owner+"_"+vaultId.toString(10)];
368
- }
369
-
370
- /**
371
- * Returns a ready-to-use vault for a specific request
372
- *
373
- * @param chainIdentifier
374
- * @param totalSats
375
- * @param token
376
- * @param amount
377
- * @param gasToken
378
- * @param gasTokenAmount
379
- * @protected
380
- */
381
- async findVaultForSwap(chainIdentifier: string, totalSats: bigint, token: string, amount: bigint, gasToken: string, gasTokenAmount: bigint): Promise<SpvVault | null> {
382
- const {signer} = this.getChain(chainIdentifier);
383
-
384
- const pluginResponse = await PluginManager.onVaultSelection(
385
- chainIdentifier, totalSats, {token, amount}, {token: gasToken, amount: gasTokenAmount}
386
- );
387
- if(pluginResponse!=null) {
388
- AmountAssertions.handlePluginErrorResponses(pluginResponse);
389
- return pluginResponse as SpvVault;
390
- }
391
-
392
- const candidates = Object.keys(this.vaultStorage.data)
393
- .map(key => this.vaultStorage.data[key])
394
- .filter(vault =>
395
- vault.chainId===chainIdentifier && vault.data.getOwner()===signer.getAddress() && vault.isReady()
396
- )
397
- .filter(vault => {
398
- const token0 = vault.balances[0];
399
- if(token0.token!==token || token0.scaledAmount < amount) return false;
400
- if(gasToken!=null && gasTokenAmount!==0n) {
401
- const token1 = vault.balances[1];
402
- if(token1.token!==gasToken || token1.scaledAmount < gasTokenAmount) return false;
403
- }
404
- return true;
405
- });
406
-
407
- candidates.sort((a, b) => bigIntSorter(a.balances[0].scaledAmount, b.balances[0].scaledAmount));
408
-
409
- const result = candidates[0];
410
-
411
- if(result==null) throw {
412
- code: 20301,
413
- msg: "No suitable swap vault found, try again later!"
414
- };
415
-
416
- return result;
417
- }
418
-
419
- saveVault(vault: SpvVault) {
420
- return this.vaultStorage.saveData(vault.getIdentifier(), vault);
421
- }
422
-
423
- async startVaultsWatchdog() {
424
- let rerun: () => Promise<void>;
425
- rerun = async () => {
426
- await this.checkVaults().catch( e => this.logger.error("startVaultsWatchdog(): Error when periodically checking SPV vaults: ", e));
427
- setTimeout(rerun, this.config.vaultsCheckInterval);
428
- };
429
- await rerun();
430
- }
431
-
432
- async init() {
433
- const vaults = await this.vaultStorage.loadData(SpvVault);
434
- }
435
-
1
+ import {SpvVault, SpvVaultState} from "./SpvVault";
2
+ import {
3
+ BitcoinRpc,
4
+ IStorageManager,
5
+ SpvVaultClaimEvent,
6
+ SpvVaultCloseEvent,
7
+ SpvVaultDepositEvent,
8
+ SpvVaultOpenEvent, SpvWithdrawalTransactionData
9
+ } from "@atomiqlabs/base";
10
+ import {SpvVaultSwap} from "./SpvVaultSwap";
11
+ import {bigIntSorter, getLogger} from "../../utils/Utils";
12
+ import {PluginManager} from "../../plugins/PluginManager";
13
+ import {IBitcoinWallet} from "../../wallets/IBitcoinWallet";
14
+ import {ISpvVaultSigner} from "../../wallets/ISpvVaultSigner";
15
+ import {AmountAssertions} from "../assertions/AmountAssertions";
16
+ import {ChainData} from "../SwapHandler";
17
+ import {Transaction} from "@scure/btc-signer";
18
+
19
+ export const VAULT_DUST_AMOUNT = 600;
20
+ const VAULT_INIT_CONFIRMATIONS = 2;
21
+ const BTC_FINALIZATION_CONFIRMATIONS = 6;
22
+
23
+ export class SpvVaults {
24
+
25
+ readonly vaultStorage: IStorageManager<SpvVault>;
26
+
27
+ readonly bitcoin: IBitcoinWallet;
28
+ readonly vaultSigner: ISpvVaultSigner;
29
+ readonly bitcoinRpc: BitcoinRpc<any>;
30
+ readonly config: {vaultsCheckInterval: number, maxUnclaimedWithdrawals?: number};
31
+ readonly getChain: (chainId: string) => ChainData
32
+
33
+ readonly logger = getLogger("SpvVaults: ");
34
+
35
+ constructor(
36
+ vaultStorage: IStorageManager<SpvVault>,
37
+ bitcoin: IBitcoinWallet,
38
+ vaultSigner: ISpvVaultSigner,
39
+ bitcoinRpc: BitcoinRpc<any>,
40
+ getChain: (chainId: string) => ChainData,
41
+ config: {vaultsCheckInterval: number, maxUnclaimedWithdrawals?: number}
42
+ ) {
43
+ this.vaultStorage = vaultStorage;
44
+ this.bitcoin = bitcoin;
45
+ this.vaultSigner = vaultSigner;
46
+ this.bitcoinRpc = bitcoinRpc;
47
+ this.getChain = getChain;
48
+ this.config = config;
49
+ }
50
+
51
+ async processDepositEvent(vault: SpvVault, event: SpvVaultDepositEvent): Promise<void> {
52
+ vault.update(event);
53
+ await this.saveVault(vault);
54
+ }
55
+
56
+ async processOpenEvent(vault: SpvVault, event: SpvVaultOpenEvent): Promise<void> {
57
+ if(vault.state===SpvVaultState.BTC_CONFIRMED) {
58
+ vault.state = SpvVaultState.OPENED;
59
+ }
60
+ vault.update(event);
61
+ await this.saveVault(vault);
62
+ }
63
+
64
+ async processCloseEvent(vault: SpvVault, event: SpvVaultCloseEvent): Promise<void> {
65
+ if(vault.state===SpvVaultState.OPENED) {
66
+ vault.state = SpvVaultState.CLOSED;
67
+ }
68
+ vault.update(event);
69
+ await this.saveVault(vault);
70
+ }
71
+
72
+ async processClaimEvent(vault: SpvVault, swap: SpvVaultSwap | null, event: SpvVaultClaimEvent): Promise<void> {
73
+ //Update vault
74
+ const foundPendingWithdrawal = vault.pendingWithdrawals.findIndex(val => val.btcTx.txid===event.btcTxId);
75
+ if(foundPendingWithdrawal!==-1) vault.pendingWithdrawals.splice(foundPendingWithdrawal, 1);
76
+ vault.update(event);
77
+ await this.saveVault(vault);
78
+ }
79
+
80
+ async createVaults(chainId: string, count: number, token: string, confirmations: number = 2, feeRate?: number): Promise<{vaultsCreated: bigint[], btcTxId: string}> {
81
+ const {signer, chainInterface, tokenMultipliers, spvVaultContract} = this.getChain(chainId);
82
+
83
+ const signerAddress = signer.getAddress();
84
+
85
+ //Check vaultId of the latest saved vault
86
+ let latestVaultId: bigint = -1n;
87
+ for(let key in this.vaultStorage.data) {
88
+ const vault = this.vaultStorage.data[key];
89
+ if(vault.chainId!==chainId) continue;
90
+ if(vault.data.getOwner()!==signerAddress) continue;
91
+ if(vault.data.getVaultId() > latestVaultId) latestVaultId = vault.data.getVaultId();
92
+ }
93
+
94
+ latestVaultId++;
95
+
96
+ const vaultAddreses: {vaultId: bigint, address: string}[] = [];
97
+ for(let i=0;i<count;i++) {
98
+ const vaultId = latestVaultId + BigInt(i);
99
+ const address = await this.vaultSigner.getAddress(chainId, vaultId);
100
+ vaultAddreses.push({vaultId, address});
101
+ }
102
+
103
+ //Construct transaction
104
+ const txResult = await this.bitcoin.getSignedMultiTransaction(vaultAddreses.map(val => {
105
+ return {address: val.address, amount: VAULT_DUST_AMOUNT}
106
+ }), feeRate);
107
+
108
+ const nativeToken = chainInterface.getNativeCurrencyAddress();
109
+
110
+ const vaults = await Promise.all(vaultAddreses.map(async (val, index) => {
111
+ const vaultData = await spvVaultContract.createVaultData(signerAddress, val.vaultId, txResult.txId+":"+index, confirmations, [
112
+ {token, multiplier: tokenMultipliers?.[token] ?? 1n},
113
+ {token: nativeToken, multiplier: tokenMultipliers?.[nativeToken] ?? 1n}
114
+ ]);
115
+ return new SpvVault(chainId, vaultData, val.address);
116
+ }));
117
+
118
+ //Save vaults
119
+ if(this.vaultStorage.saveDataArr!=null) {
120
+ await this.vaultStorage.saveDataArr(vaults.map(val => {
121
+ return {id: val.getIdentifier(), object: val}
122
+ }));
123
+ } else {
124
+ for(let vault of vaults) {
125
+ await this.vaultStorage.saveData(vault.getIdentifier(), vault);
126
+ }
127
+ }
128
+
129
+ //Send bitcoin tx
130
+ await this.bitcoin.sendRawTransaction(txResult.raw);
131
+
132
+ this.logger.info("createVaults(): Funding "+count+" vaults, bitcoin txId: "+txResult.txId);
133
+
134
+ return {
135
+ vaultsCreated: vaults.map(val => val.data.getVaultId()),
136
+ btcTxId: txResult.txId
137
+ };
138
+ }
139
+
140
+ async listVaults(chainId?: string, token?: string) {
141
+ return Object.keys(this.vaultStorage.data)
142
+ .map(key => this.vaultStorage.data[key])
143
+ .filter(val => chainId==null ? true : val.chainId===chainId)
144
+ .filter(val => val.data.getOwner()===this.getChain(val.chainId)?.signer?.getAddress())
145
+ .filter(val => token==null ? true : val.data.getTokenData()[0].token===token);
146
+ }
147
+
148
+ async fundVault(vault: SpvVault, tokenAmounts: bigint[]): Promise<string> {
149
+ if(vault.state!==SpvVaultState.OPENED) throw new Error("Vault not opened!");
150
+
151
+ this.logger.info("fundVault(): Depositing tokens to the vault "+vault.data.getVaultId().toString(10)+", amounts: "+tokenAmounts.map(val => val.toString(10)).join(", "));
152
+
153
+ const {signer, spvVaultContract} = this.getChain(vault.chainId);
154
+
155
+ const txId = await spvVaultContract.deposit(signer, vault.data, tokenAmounts, {waitForConfirmation: true});
156
+
157
+ this.logger.info("fundVault(): Tokens deposited to vault "+vault.data.getVaultId().toString(10)+", amounts: "+tokenAmounts.map(val => val.toString(10)).join(", ")+", txId: "+txId);
158
+
159
+ return txId;
160
+ }
161
+
162
+ async withdrawFromVault(vault: SpvVault, tokenAmounts: bigint[], feeRate?: number): Promise<string> {
163
+ tokenAmounts.forEach((rawAmount, index) => {
164
+ if(vault.balances[index]==null) throw new Error("Token not found in the vault");
165
+ if(vault.balances[index].rawAmount<rawAmount) throw new Error("Not enough balance in the vault");
166
+ });
167
+
168
+ if(!vault.isReady()) throw new Error("Vault not ready, wait for the latest swap to get at least 1 confirmation!");
169
+
170
+ const {signer, spvVaultContract} = this.getChain(vault.chainId);
171
+
172
+ const latestUtxo = vault.getLatestUtxo();
173
+ const [txId, voutStr] = latestUtxo.split(":");
174
+
175
+ const opReturnData = spvVaultContract.toOpReturnData(signer.getAddress(), tokenAmounts);
176
+ let opReturnScript: Buffer;
177
+ if(opReturnData.length<76) {
178
+ opReturnScript = Buffer.concat([
179
+ Buffer.from([0x6a, opReturnData.length]),
180
+ opReturnData
181
+ ]);
182
+ } else {
183
+ opReturnScript = Buffer.concat([
184
+ Buffer.from([0x6a, 0x4c, opReturnData.length]),
185
+ opReturnData
186
+ ]);
187
+ }
188
+
189
+ let psbt = new Transaction({
190
+ allowUnknownOutputs: true
191
+ });
192
+ psbt.addInput({
193
+ txid: txId,
194
+ index: parseInt(voutStr),
195
+ witnessUtxo: {
196
+ amount: BigInt(VAULT_DUST_AMOUNT),
197
+ script: this.bitcoin.toOutputScript(vault.btcAddress)
198
+ }
199
+ });
200
+ psbt.addOutput({
201
+ amount: BigInt(VAULT_DUST_AMOUNT),
202
+ script: this.bitcoin.toOutputScript(vault.btcAddress)
203
+ });
204
+ psbt.addOutput({
205
+ amount: 0n,
206
+ script: opReturnScript
207
+ });
208
+
209
+ psbt = await this.bitcoin.fundPsbt(psbt, feeRate);
210
+ if(psbt.inputsLength<2) throw new Error("PSBT needs at least 2 inputs!");
211
+ psbt.updateInput(0, {sequence: 0x80000000});
212
+ psbt.updateInput(1, {sequence: 0x80000000});
213
+ psbt = await this.vaultSigner.signPsbt(vault.chainId, vault.data.getVaultId(), psbt, [0]);
214
+ const res = await this.bitcoin.signPsbt(psbt);
215
+
216
+ const parsedTransaction = await this.bitcoinRpc.parseTransaction(res.raw);
217
+ const withdrawalData = await spvVaultContract.getWithdrawalData(parsedTransaction);
218
+
219
+ if(withdrawalData.getSpentVaultUtxo()!==vault.getLatestUtxo()) {
220
+ throw new Error("Latest vault UTXO already spent! Please try again later.");
221
+ }
222
+ vault.addWithdrawal(withdrawalData);
223
+ await this.saveVault(vault);
224
+
225
+ try {
226
+ await this.bitcoin.sendRawTransaction(res.raw);
227
+ } catch (e) {
228
+ vault.removeWithdrawal(withdrawalData);
229
+ await this.saveVault(vault);
230
+ throw e;
231
+ }
232
+
233
+ return res.txId;
234
+ }
235
+
236
+ async checkVaults() {
237
+ const vaults = Object.keys(this.vaultStorage.data).map(key => this.vaultStorage.data[key]);
238
+
239
+ const claimWithdrawals: {vault: SpvVault, withdrawals: SpvWithdrawalTransactionData[]}[] = [];
240
+
241
+ for(let vault of vaults) {
242
+ const {signer, spvVaultContract, chainInterface} = this.getChain(vault.chainId);
243
+ if(vault.data.getOwner()!==signer.getAddress()) continue;
244
+
245
+ if(vault.state===SpvVaultState.BTC_INITIATED) {
246
+ //Check if btc tx confirmed
247
+ const txId = vault.initialUtxo.split(":")[0];
248
+ const btcTx = await this.bitcoinRpc.getTransaction(txId);
249
+ if(btcTx.confirmations >= VAULT_INIT_CONFIRMATIONS) {
250
+ //Double-check the state here to prevent race condition
251
+ if(vault.state===SpvVaultState.BTC_INITIATED) {
252
+ vault.state = SpvVaultState.BTC_CONFIRMED;
253
+ await this.saveVault(vault);
254
+ }
255
+ this.logger.info("checkVaults(): Vault ID "+vault.data.getVaultId().toString(10)+" confirmed on bitcoin, opening vault on "+vault.chainId);
256
+ }
257
+ }
258
+
259
+ if(vault.state===SpvVaultState.BTC_CONFIRMED) {
260
+ //Check if open txs were sent already
261
+ if(vault.scOpenTx!=null) {
262
+ //Check if confirmed
263
+ const status = await chainInterface.getTxStatus(vault.scOpenTx.rawTx);
264
+ if(status==="pending") return;
265
+ if(status==="success") {
266
+ vault.state = SpvVaultState.OPENED;
267
+ await this.saveVault(vault);
268
+ return;
269
+ }
270
+ }
271
+
272
+ const txs = await spvVaultContract.txsOpen(signer.getAddress(), vault.data);
273
+ let numTx = 0;
274
+ const txIds = await chainInterface.sendAndConfirm(
275
+ signer, txs, true, undefined, false,
276
+ async (txId: string, rawTx: string) => {
277
+ numTx++;
278
+ if(numTx===txs.length) {
279
+ //Final tx
280
+ vault.scOpenTx = {txId, rawTx};
281
+ await this.saveVault(vault);
282
+ }
283
+ }
284
+ );
285
+ this.logger.info("checkVaults(): Vault ID "+vault.data.getVaultId().toString(10)+" opened on "+vault.chainId+" txId: "+txIds.join(", "));
286
+
287
+ vault.state = SpvVaultState.OPENED;
288
+ await this.saveVault(vault);
289
+ }
290
+
291
+ if(vault.state===SpvVaultState.OPENED) {
292
+ let changed = false;
293
+ //Check if some of the pendingWithdrawals got confirmed
294
+ let latestOwnWithdrawalIndex = -1;
295
+ let latestConfirmedWithdrawalIndex = -1;
296
+ for(let i=0; i<vault.pendingWithdrawals.length; i++) {
297
+ const pendingWithdrawal = vault.pendingWithdrawals[i];
298
+ //Check all the pending withdrawals that were not finalized yet
299
+ if(pendingWithdrawal.btcTx.confirmations==null || pendingWithdrawal.btcTx.confirmations < BTC_FINALIZATION_CONFIRMATIONS) {
300
+ const btcTx = await this.bitcoinRpc.getTransaction(pendingWithdrawal.btcTx.txid);
301
+ if(btcTx==null) {
302
+ //Probable double-spend, remove from pending withdrawals
303
+ const index = vault.pendingWithdrawals.indexOf(pendingWithdrawal);
304
+ if(index===-1) {
305
+ this.logger.warn("checkVaults(): Tried to remove pending withdrawal txId: "+pendingWithdrawal.btcTx.txid+", but doesn't exist anymore!")
306
+ } else {
307
+ vault.pendingWithdrawals.splice(index, 1);
308
+ }
309
+ changed = true;
310
+ } else {
311
+ //Update confirmations count
312
+ if(
313
+ pendingWithdrawal.btcTx.confirmations !== btcTx.confirmations ||
314
+ pendingWithdrawal.btcTx.blockhash !== btcTx.blockhash
315
+ ) {
316
+ pendingWithdrawal.btcTx.confirmations = btcTx.confirmations;
317
+ pendingWithdrawal.btcTx.blockhash = btcTx.blockhash;
318
+ changed = true;
319
+ }
320
+ }
321
+ }
322
+ //Check it has enough confirmations
323
+ if(pendingWithdrawal.btcTx.confirmations >= vault.data.getConfirmations()) {
324
+ latestConfirmedWithdrawalIndex = i;
325
+ //Check if the pending withdrawals contain a withdrawal to our own address
326
+ if (pendingWithdrawal.isRecipient(signer.getAddress())) {
327
+ latestOwnWithdrawalIndex = i;
328
+ }
329
+ }
330
+ }
331
+ if(changed) {
332
+ await this.saveVault(vault);
333
+ }
334
+ if(this.config.maxUnclaimedWithdrawals!=null && latestConfirmedWithdrawalIndex+1 >= this.config.maxUnclaimedWithdrawals) {
335
+ this.logger.info("checkVaults(): Processing withdrawals by self, because a lot of them are unclaimed!");
336
+ claimWithdrawals.push({vault, withdrawals: vault.pendingWithdrawals.slice(0, latestConfirmedWithdrawalIndex+1)});
337
+ } else if(latestOwnWithdrawalIndex!==-1) {
338
+ claimWithdrawals.push({vault, withdrawals: vault.pendingWithdrawals.slice(0, latestOwnWithdrawalIndex+1)});
339
+ }
340
+ }
341
+ }
342
+
343
+ for(let {vault, withdrawals} of claimWithdrawals) {
344
+ if(!await this.claimWithdrawals(vault, withdrawals)) {
345
+ this.logger.error("checkVaults(): Cannot process withdrawals "+withdrawals.map(val => val.btcTx.txid).join(", ")+" for vault: "+vault.data.getVaultId());
346
+ break;
347
+ }
348
+ }
349
+ }
350
+
351
+ async claimWithdrawals(vault: SpvVault, withdrawal: SpvWithdrawalTransactionData[]): Promise<boolean> {
352
+ const {signer, spvVaultContract} = this.getChain(vault.chainId);
353
+
354
+ try {
355
+ const txId = await spvVaultContract.claim(signer, vault.data, withdrawal.map(tx => {
356
+ return {tx};
357
+ }), undefined, true, {waitForConfirmation: true});
358
+ this.logger.info("claimWithdrawal(): Successfully claimed withdrawals, btcTxIds: "+withdrawal.map(val => val.btcTx.txid).join(", ")+" smartChainTxId: "+txId);
359
+ return true;
360
+ } catch (e) {
361
+ this.logger.error("claimWithdrawal(): Tried to claim but got error: ", e);
362
+ return false;
363
+ }
364
+ }
365
+
366
+ async getVault(chainId: string, owner: string, vaultId: bigint) {
367
+ return this.vaultStorage.data[chainId+"_"+owner+"_"+vaultId.toString(10)];
368
+ }
369
+
370
+ /**
371
+ * Returns a ready-to-use vault for a specific request
372
+ *
373
+ * @param chainIdentifier
374
+ * @param totalSats
375
+ * @param token
376
+ * @param amount
377
+ * @param gasToken
378
+ * @param gasTokenAmount
379
+ * @protected
380
+ */
381
+ async findVaultForSwap(chainIdentifier: string, totalSats: bigint, token: string, amount: bigint, gasToken: string, gasTokenAmount: bigint): Promise<SpvVault | null> {
382
+ const {signer} = this.getChain(chainIdentifier);
383
+
384
+ const pluginResponse = await PluginManager.onVaultSelection(
385
+ chainIdentifier, totalSats, {token, amount}, {token: gasToken, amount: gasTokenAmount}
386
+ );
387
+ if(pluginResponse!=null) {
388
+ AmountAssertions.handlePluginErrorResponses(pluginResponse);
389
+ return pluginResponse as SpvVault;
390
+ }
391
+
392
+ const candidates = Object.keys(this.vaultStorage.data)
393
+ .map(key => this.vaultStorage.data[key])
394
+ .filter(vault =>
395
+ vault.chainId===chainIdentifier && vault.data.getOwner()===signer.getAddress() && vault.isReady()
396
+ )
397
+ .filter(vault => {
398
+ const token0 = vault.balances[0];
399
+ if(token0.token!==token || token0.scaledAmount < amount) return false;
400
+ if(gasToken!=null && gasTokenAmount!==0n) {
401
+ const token1 = vault.balances[1];
402
+ if(token1.token!==gasToken || token1.scaledAmount < gasTokenAmount) return false;
403
+ }
404
+ return true;
405
+ });
406
+
407
+ candidates.sort((a, b) => bigIntSorter(a.balances[0].scaledAmount, b.balances[0].scaledAmount));
408
+
409
+ const result = candidates[0];
410
+
411
+ if(result==null) throw {
412
+ code: 20301,
413
+ msg: "No suitable swap vault found, try again later!"
414
+ };
415
+
416
+ return result;
417
+ }
418
+
419
+ saveVault(vault: SpvVault) {
420
+ return this.vaultStorage.saveData(vault.getIdentifier(), vault);
421
+ }
422
+
423
+ async startVaultsWatchdog() {
424
+ let rerun: () => Promise<void>;
425
+ rerun = async () => {
426
+ await this.checkVaults().catch( e => this.logger.error("startVaultsWatchdog(): Error when periodically checking SPV vaults: ", e));
427
+ setTimeout(rerun, this.config.vaultsCheckInterval);
428
+ };
429
+ await rerun();
430
+ }
431
+
432
+ async init() {
433
+ const vaults = await this.vaultStorage.loadData(SpvVault);
434
+ }
435
+
436
436
  }