@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,641 +1,641 @@
1
- import {SpvVault, SpvVaultState} from "./SpvVault";
2
- import {
3
- BitcoinRpc,
4
- BtcBlock,
5
- BtcTx,
6
- IStorageManager,
7
- SpvVaultClaimEvent,
8
- SpvVaultCloseEvent,
9
- SpvVaultDepositEvent,
10
- SpvVaultOpenEvent,
11
- SpvWithdrawalTransactionData
12
- } from "@atomiqlabs/base";
13
- import {SpvVaultSwap} from "./SpvVaultSwap";
14
- import {bigIntSorter, getLogger} from "../../utils/Utils";
15
- import {PluginManager} from "../../plugins/PluginManager";
16
- import {IBitcoinWallet} from "../../wallets/IBitcoinWallet";
17
- import {ISpvVaultSigner} from "../../wallets/ISpvVaultSigner";
18
- import {AmountAssertions} from "../assertions/AmountAssertions";
19
- import {MultichainData} from "../SwapHandler";
20
- import {Transaction} from "@scure/btc-signer";
21
- import {checkTransactionReplaced} from "../../utils/BitcoinUtils";
22
-
23
- export const VAULT_DUST_AMOUNT = 600;
24
- const VAULT_INIT_CONFIRMATIONS = 2;
25
- const MAX_PARALLEL_VAULTS_OPENING = 10;
26
-
27
- export class SpvVaults {
28
-
29
- readonly vaultStorage: IStorageManager<SpvVault>;
30
-
31
- readonly bitcoin: IBitcoinWallet;
32
- readonly vaultSigner: ISpvVaultSigner;
33
- readonly bitcoinRpc: BitcoinRpc<BtcBlock>;
34
- readonly config: {vaultsCheckInterval: number, maxUnclaimedWithdrawals?: number};
35
- readonly chains: MultichainData;
36
-
37
- readonly logger = getLogger("SpvVaults: ");
38
-
39
- constructor(
40
- vaultStorage: IStorageManager<SpvVault>,
41
- bitcoin: IBitcoinWallet,
42
- vaultSigner: ISpvVaultSigner,
43
- bitcoinRpc: BitcoinRpc<any>,
44
- chains: MultichainData,
45
- config: {vaultsCheckInterval: number, maxUnclaimedWithdrawals?: number}
46
- ) {
47
- this.vaultStorage = vaultStorage;
48
- this.bitcoin = bitcoin;
49
- this.vaultSigner = vaultSigner;
50
- this.bitcoinRpc = bitcoinRpc;
51
- this.chains = chains;
52
- this.config = config;
53
-
54
- for(let chainId in chains.chains) {
55
- const {chainInterface} = chains.chains[chainId];
56
- chainInterface.onBeforeTxReplace(async (oldTx: string, oldTxId: string, newTx: string, newTxId: string) => {
57
- for(let key in this.vaultStorage.data) {
58
- const vaultData = this.vaultStorage.data[key];
59
- if(vaultData.scOpenTxs!=null && vaultData.scOpenTxs[oldTxId]!=null){
60
- vaultData.scOpenTxs[newTxId] = newTx;
61
- await this.saveVault(vaultData);
62
- break;
63
- }
64
- }
65
- });
66
- }
67
- }
68
-
69
- async processDepositEvent(vault: SpvVault, event: SpvVaultDepositEvent): Promise<void> {
70
- vault.update(event);
71
- await this.saveVault(vault);
72
- }
73
-
74
- async processOpenEvent(vault: SpvVault, event: SpvVaultOpenEvent): Promise<void> {
75
- if(vault.state===SpvVaultState.BTC_CONFIRMED) {
76
- vault.state = SpvVaultState.OPENED;
77
- }
78
- vault.update(event);
79
- await this.saveVault(vault);
80
- }
81
-
82
- async processCloseEvent(vault: SpvVault, event: SpvVaultCloseEvent): Promise<void> {
83
- if(vault.state===SpvVaultState.OPENED) {
84
- vault.state = SpvVaultState.CLOSED;
85
- }
86
- vault.update(event);
87
- await this.saveVault(vault);
88
- }
89
-
90
- async processClaimEvent(vault: SpvVault, swap: SpvVaultSwap | null, event: SpvVaultClaimEvent): Promise<void> {
91
- //Update vault
92
- const foundPendingWithdrawal = vault.pendingWithdrawals.findIndex(val => val.btcTx.txid===event.btcTxId);
93
- if(foundPendingWithdrawal!==-1) vault.pendingWithdrawals.splice(foundPendingWithdrawal, 1);
94
- vault.update(event);
95
- await this.saveVault(vault);
96
- }
97
-
98
- async createVaults(chainId: string, count: number, token: string, confirmations: number = 2, feeRate?: number): Promise<{vaultsCreated: bigint[], btcTxId: string}> {
99
- const {signer, chainInterface, tokenMultipliers, spvVaultContract} = this.chains.chains[chainId];
100
-
101
- const signerAddress = signer.getAddress();
102
-
103
- //Check vaultId of the latest saved vault
104
- let latestVaultId: bigint = -1n;
105
- for(let key in this.vaultStorage.data) {
106
- const vault = this.vaultStorage.data[key];
107
- if(vault.chainId!==chainId) continue;
108
- if(vault.data.getOwner()!==signerAddress) continue;
109
- if(vault.data.getVaultId() > latestVaultId) latestVaultId = vault.data.getVaultId();
110
- }
111
-
112
- latestVaultId++;
113
-
114
- const vaultAddreses: {vaultId: bigint, address: string}[] = [];
115
- for(let i=0;i<count;i++) {
116
- const vaultId = latestVaultId + BigInt(i);
117
- const address = await this.vaultSigner.getAddress(chainId, vaultId);
118
- vaultAddreses.push({vaultId, address});
119
- }
120
-
121
- const nativeToken = chainInterface.getNativeCurrencyAddress();
122
-
123
- let txId: string = null;
124
- let vaults: SpvVault[] = null;
125
- await this.bitcoin.execute(async () => {
126
- //Construct transaction
127
- const txResult = await this.bitcoin.getSignedMultiTransaction(vaultAddreses.map(val => {
128
- return {address: val.address, amount: VAULT_DUST_AMOUNT}
129
- }), feeRate);
130
-
131
- vaults = await Promise.all(vaultAddreses.map(async (val, index) => {
132
- const vaultData = await spvVaultContract.createVaultData(signerAddress, val.vaultId, txResult.txId+":"+index, confirmations, [
133
- {token, multiplier: tokenMultipliers?.[token] ?? 1n},
134
- {token: nativeToken, multiplier: tokenMultipliers?.[nativeToken] ?? 1n}
135
- ]);
136
- return new SpvVault(chainId, vaultData, val.address);
137
- }));
138
-
139
- //Save vaults
140
- if(this.vaultStorage.saveDataArr!=null) {
141
- await this.vaultStorage.saveDataArr(vaults.map(val => {
142
- return {id: val.getIdentifier(), object: val}
143
- }));
144
- } else {
145
- for(let vault of vaults) {
146
- await this.vaultStorage.saveData(vault.getIdentifier(), vault);
147
- }
148
- }
149
-
150
- //Send bitcoin tx
151
- await this.bitcoin.sendRawTransaction(txResult.raw);
152
-
153
- txId = txResult.txId;
154
- });
155
-
156
- this.logger.info("createVaults(): Funding "+count+" vaults, bitcoin txId: "+txId);
157
-
158
- return {
159
- vaultsCreated: vaults.map(val => val.data.getVaultId()),
160
- btcTxId: txId
161
- };
162
- }
163
-
164
- async listVaults(chainId?: string, token?: string) {
165
- return Object.keys(this.vaultStorage.data)
166
- .map(key => this.vaultStorage.data[key])
167
- .filter(val => chainId==null ? true : val.chainId===chainId)
168
- .filter(val => this.chains.chains[val.chainId]!=null && val.data.getOwner()===this.chains.chains[val.chainId]?.signer?.getAddress())
169
- .filter(val => token==null ? true : val.data.getTokenData()[0].token===token);
170
- }
171
-
172
- async fundVault(vault: SpvVault, tokenAmounts: bigint[]): Promise<string> {
173
- if(vault.state!==SpvVaultState.OPENED) throw new Error("Vault not opened!");
174
-
175
- this.logger.info("fundVault(): Depositing tokens to the vault "+vault.data.getVaultId().toString(10)+", amounts: "+tokenAmounts.map(val => val.toString(10)).join(", "));
176
-
177
- const {signer, spvVaultContract} = this.chains.chains[vault.chainId];
178
-
179
- const txId = await spvVaultContract.deposit(signer, vault.data, tokenAmounts, {waitForConfirmation: true});
180
-
181
- this.logger.info("fundVault(): Tokens deposited to vault "+vault.data.getVaultId().toString(10)+", amounts: "+tokenAmounts.map(val => val.toString(10)).join(", ")+", txId: "+txId);
182
-
183
- return txId;
184
- }
185
-
186
- async withdrawFromVault(vault: SpvVault, tokenAmounts: bigint[], feeRate?: number): Promise<string> {
187
- tokenAmounts.forEach((rawAmount, index) => {
188
- if(vault.balances[index]==null) throw new Error("Token not found in the vault");
189
- if(vault.balances[index].rawAmount<rawAmount) throw new Error("Not enough balance in the vault");
190
- });
191
-
192
- if(!vault.isReady()) throw new Error("Vault not ready, wait for the latest swap to get at least 1 confirmation!");
193
-
194
- const {signer, spvVaultContract} = this.chains.chains[vault.chainId];
195
-
196
- const latestUtxo = vault.getLatestUtxo();
197
- const [txId, voutStr] = latestUtxo.split(":");
198
-
199
- const opReturnData = spvVaultContract.toOpReturnData(signer.getAddress(), tokenAmounts);
200
- let opReturnScript: Buffer;
201
- if(opReturnData.length<76) {
202
- opReturnScript = Buffer.concat([
203
- Buffer.from([0x6a, opReturnData.length]),
204
- opReturnData
205
- ]);
206
- } else {
207
- opReturnScript = Buffer.concat([
208
- Buffer.from([0x6a, 0x4c, opReturnData.length]),
209
- opReturnData
210
- ]);
211
- }
212
-
213
- let psbt = new Transaction({
214
- allowUnknownOutputs: true
215
- });
216
- psbt.addInput({
217
- txid: txId,
218
- index: parseInt(voutStr),
219
- witnessUtxo: {
220
- amount: BigInt(VAULT_DUST_AMOUNT),
221
- script: this.bitcoin.toOutputScript(vault.btcAddress)
222
- }
223
- });
224
- psbt.addOutput({
225
- amount: BigInt(VAULT_DUST_AMOUNT),
226
- script: this.bitcoin.toOutputScript(vault.btcAddress)
227
- });
228
- psbt.addOutput({
229
- amount: 0n,
230
- script: opReturnScript
231
- });
232
-
233
- let withdrawalTxId: string = null;
234
- await this.bitcoin.execute(async () => {
235
- psbt = await this.bitcoin.fundPsbt(psbt, feeRate);
236
- if(psbt.inputsLength<2) throw new Error("PSBT needs at least 2 inputs!");
237
- psbt.updateInput(0, {sequence: 0x80000000});
238
- psbt.updateInput(1, {sequence: 0x80000000});
239
- psbt = await this.vaultSigner.signPsbt(vault.chainId, vault.data.getVaultId(), psbt, [0]);
240
- const res = await this.bitcoin.signPsbt(psbt);
241
- withdrawalTxId = res.txId;
242
-
243
- const parsedTransaction = await this.bitcoinRpc.parseTransaction(res.raw);
244
- const withdrawalData = await spvVaultContract.getWithdrawalData(parsedTransaction);
245
-
246
- if(withdrawalData.getSpentVaultUtxo()!==vault.getLatestUtxo()) {
247
- throw new Error("Latest vault UTXO already spent! Please try again later.");
248
- }
249
- (withdrawalData as any).sending = true;
250
- vault.addWithdrawal(withdrawalData);
251
- await this.saveVault(vault);
252
-
253
- try {
254
- await this.bitcoin.sendRawTransaction(res.raw);
255
- (withdrawalData as any).sending = false;
256
- } catch (e) {
257
- (withdrawalData as any).sending = false;
258
- vault.removeWithdrawal(withdrawalData);
259
- await this.saveVault(vault);
260
- throw e;
261
- }
262
- });
263
-
264
- return withdrawalTxId;
265
- }
266
-
267
- /**
268
- * Call this to check whether some of the previously replaced transactions got re-introduced to the mempool
269
- *
270
- * @param vault
271
- * @param save
272
- */
273
- async checkVaultReplacedTransactions(vault: SpvVault, save?: boolean): Promise<boolean> {
274
- const {spvVaultContract} = this.chains.chains[vault.chainId];
275
-
276
- const initialVaultWithdrawalCount = vault.data.getWithdrawalCount();
277
-
278
- let latestWithdrawalIndex = initialVaultWithdrawalCount;
279
- const newPendingTxns: SpvWithdrawalTransactionData[] = [];
280
- const reintroducedTxIds: Set<string> = new Set();
281
- for(let [withdrawalIndex, replacedWithdrawalGroup] of vault.replacedWithdrawals) {
282
- if(withdrawalIndex<=latestWithdrawalIndex) continue; //Don't check txns that should already be included
283
-
284
- for(let replacedWithdrawal of replacedWithdrawalGroup) {
285
- if(reintroducedTxIds.has(replacedWithdrawal.getTxId())) continue;
286
- const tx = await this.bitcoinRpc.getTransaction(replacedWithdrawal.getTxId());
287
- if(tx==null) continue;
288
-
289
- //Re-introduce transaction to the pending withdrawals list
290
- if(withdrawalIndex>latestWithdrawalIndex) {
291
- const txChain: SpvWithdrawalTransactionData[] = [replacedWithdrawal];
292
- withdrawalIndex--;
293
- while(withdrawalIndex>latestWithdrawalIndex) {
294
- const tx = await this.bitcoinRpc.getTransaction(txChain[0].getSpentVaultUtxo().split(":")[0]);
295
- if(tx==null) break;
296
- reintroducedTxIds.add(tx.txid);
297
- txChain.unshift(await spvVaultContract.getWithdrawalData(tx));
298
- withdrawalIndex--;
299
- }
300
- if(withdrawalIndex>latestWithdrawalIndex) {
301
- this.logger.warn(`checkVaultReplacedTransactions(${vault.getIdentifier()}): Tried to re-introduce previously replaced TX, but one of txns in the chain not found!`);
302
- continue;
303
- }
304
- newPendingTxns.push(...txChain);
305
- latestWithdrawalIndex += txChain.length;
306
- break; //Don't check other txns at the same withdrawal index
307
- } else {
308
- this.logger.warn(`checkVaultReplacedTransactions(${vault.getIdentifier()}): Tried to re-introduce previously replaced TX, but vault has already processed such withdrawal!`);
309
- }
310
- }
311
- }
312
-
313
- if(newPendingTxns.length===0) return false;
314
-
315
- if(initialVaultWithdrawalCount!==vault.data.getWithdrawalCount()) {
316
- this.logger.warn(`checkVaultReplacedTransactions(${vault.getIdentifier()}): Not saving vault after checking replaced transactions, due to withdrawal count changed!`);
317
- return false;
318
- }
319
-
320
- const backup = vault.pendingWithdrawals.splice(0, newPendingTxns.length);
321
- const txsToAddOnTop = vault.pendingWithdrawals.splice(0, vault.pendingWithdrawals.length);
322
-
323
- try {
324
- newPendingTxns.forEach(val => vault.addWithdrawal(val));
325
- txsToAddOnTop.forEach(val => vault.addWithdrawal(val));
326
- for(let i=0;i<newPendingTxns.length;i++) {
327
- const withdrawalIndex = initialVaultWithdrawalCount+i+1;
328
- const arr = vault.replacedWithdrawals.get(withdrawalIndex);
329
- if(arr==null) continue;
330
- const index = arr.indexOf(newPendingTxns[i]);
331
- if(index===-1) {
332
- this.logger.warn(`checkVaultReplacedTransactions(${vault.getIdentifier()}): Cannot remove re-introduced tx ${newPendingTxns[i].getTxId()}, not found in the respective array!`);
333
- continue;
334
- }
335
- arr.splice(index, 1);
336
- if(arr.length===0) vault.replacedWithdrawals.delete(withdrawalIndex);
337
- }
338
- this.logger.info(`checkVaultReplacedTransactions(${vault.getIdentifier()}): Re-introduced back ${newPendingTxns.length} txns that were re-added to the mempool!`);
339
- if(save) await this.saveVault(vault);
340
- return true;
341
- } catch (e) {
342
- this.logger.error(`checkVaultReplacedTransactions(${vault.getIdentifier()}): Failed to update the vault with new pending txns (rolling back): `, e);
343
- //Rollback the pending withdrawals
344
- vault.pendingWithdrawals.push(...backup, ...txsToAddOnTop);
345
- return false;
346
- }
347
- }
348
-
349
- async checkVaults() {
350
- const vaults = Object.keys(this.vaultStorage.data).map(key => this.vaultStorage.data[key]);
351
-
352
- const claimWithdrawals: {vault: SpvVault, withdrawals: SpvWithdrawalTransactionData[]}[] = [];
353
-
354
- let promises: Promise<void>[] = [];
355
-
356
- for(let vault of vaults) {
357
- const chainData = this.chains.chains[vault.chainId];
358
- if(chainData==null) continue;
359
- const {signer, spvVaultContract, chainInterface} = chainData;
360
- if(vault.data.getOwner()!==signer.getAddress()) continue;
361
-
362
- if(vault.state===SpvVaultState.BTC_INITIATED) {
363
- //Check if btc tx confirmed
364
- const txId = vault.initialUtxo.split(":")[0];
365
- const btcTx = await this.bitcoinRpc.getTransaction(txId);
366
- if(btcTx.confirmations >= VAULT_INIT_CONFIRMATIONS) {
367
- //Double-check the state here to prevent race condition
368
- if(vault.state===SpvVaultState.BTC_INITIATED) {
369
- vault.state = SpvVaultState.BTC_CONFIRMED;
370
- await this.saveVault(vault);
371
- }
372
- this.logger.info("checkVaults(): Vault ID "+vault.data.getVaultId().toString(10)+" confirmed on bitcoin, opening vault on "+vault.chainId);
373
- }
374
- }
375
-
376
- if(vault.state===SpvVaultState.BTC_CONFIRMED) {
377
- //Check if open txs were sent already
378
- if(vault.scOpenTxs!=null) {
379
- //Check if confirmed
380
- let _continue = false;
381
- for(let txId in vault.scOpenTxs) {
382
- const tx = vault.scOpenTxs[txId];
383
- const status = await chainInterface.getTxStatus(tx);
384
- if(status==="pending") {
385
- _continue = true;
386
- break;
387
- }
388
- if(status==="success") {
389
- vault.state = SpvVaultState.OPENED;
390
- await this.saveVault(vault);
391
- _continue = true;
392
- break;
393
- }
394
- }
395
- if(_continue) continue;
396
- }
397
-
398
- const txs = await spvVaultContract.txsOpen(signer.getAddress(), vault.data);
399
- let numTx = 0;
400
- promises.push(
401
- chainInterface.sendAndConfirm(
402
- signer, txs, true, undefined, true,
403
- async (txId: string, rawTx: string) => {
404
- numTx++;
405
- if(numTx===txs.length) {
406
- //Final tx
407
- vault.scOpenTxs = {[txId]: rawTx};
408
- await this.saveVault(vault);
409
- }
410
- }
411
- ).then(txIds => {
412
- this.logger.info("checkVaults(): Vault ID "+vault.data.getVaultId().toString(10)+" opened on "+vault.chainId+" txId: "+txIds.join(", "));
413
-
414
- vault.state = SpvVaultState.OPENED;
415
- return this.saveVault(vault);
416
- })
417
- );
418
- if(promises.length>=MAX_PARALLEL_VAULTS_OPENING) {
419
- await Promise.all(promises);
420
- promises = [];
421
- }
422
- continue;
423
- }
424
-
425
- if(vault.state===SpvVaultState.OPENED) {
426
- let changed = await this.checkVaultReplacedTransactions(vault);
427
-
428
- //Check if some of the pendingWithdrawals got confirmed
429
- let latestOwnWithdrawalIndex = -1;
430
- let latestConfirmedWithdrawalIndex = -1;
431
- for(let i = vault.pendingWithdrawals.length-1; i>=0; i--) {
432
- const pendingWithdrawal = vault.pendingWithdrawals[i];
433
- if(pendingWithdrawal.sending) continue;
434
-
435
- //Check all the pending withdrawals that were not finalized yet
436
- const btcTx = await checkTransactionReplaced(pendingWithdrawal.btcTx.txid, pendingWithdrawal.btcTx.raw, this.bitcoinRpc);
437
- if(btcTx==null) {
438
- //Probable double-spend, remove from pending withdrawals
439
- if(!vault.doubleSpendPendingWithdrawal(pendingWithdrawal)) {
440
- this.logger.warn("checkVaults(): Tried to remove pending withdrawal txId: "+pendingWithdrawal.btcTx.txid+", but doesn't exist anymore!");
441
- } else {
442
- this.logger.info("checkVaults(): Successfully removed withdrawal txId: "+pendingWithdrawal.btcTx.txid+", due to being replaced in the mempool!");
443
- }
444
- changed = true;
445
- } else {
446
- //Update confirmations count
447
- if(
448
- pendingWithdrawal.btcTx.confirmations !== btcTx.confirmations ||
449
- pendingWithdrawal.btcTx.blockhash !== btcTx.blockhash
450
- ) {
451
- pendingWithdrawal.btcTx.confirmations = btcTx.confirmations;
452
- pendingWithdrawal.btcTx.blockhash = btcTx.blockhash;
453
- changed = true;
454
- }
455
- }
456
-
457
- //Check it has enough confirmations
458
- if(pendingWithdrawal.btcTx.confirmations >= vault.data.getConfirmations()) {
459
- latestConfirmedWithdrawalIndex = i;
460
- //Check if the pending withdrawals contain a withdrawal to our own address
461
- if (pendingWithdrawal.isRecipient(signer.getAddress())) {
462
- latestOwnWithdrawalIndex = i;
463
- }
464
- }
465
- }
466
- if(changed) {
467
- await this.saveVault(vault);
468
- }
469
- if(this.config.maxUnclaimedWithdrawals!=null && latestConfirmedWithdrawalIndex+1 >= this.config.maxUnclaimedWithdrawals) {
470
- this.logger.info("checkVaults(): Processing withdrawals by self, because a lot of them are unclaimed!");
471
- claimWithdrawals.push({vault, withdrawals: vault.pendingWithdrawals.slice(0, latestConfirmedWithdrawalIndex+1)});
472
- } else if(latestOwnWithdrawalIndex!==-1) {
473
- claimWithdrawals.push({vault, withdrawals: vault.pendingWithdrawals.slice(0, latestOwnWithdrawalIndex+1)});
474
- }
475
- }
476
- }
477
-
478
- for(let {vault, withdrawals} of claimWithdrawals) {
479
- if(!await this.claimWithdrawals(vault, withdrawals)) {
480
- this.logger.error("checkVaults(): Cannot process withdrawals "+withdrawals.map(val => val.btcTx.txid).join(", ")+" for vault: "+vault.data.getVaultId());
481
- break;
482
- }
483
- }
484
- }
485
-
486
- async claimWithdrawals(vault: SpvVault, withdrawal: SpvWithdrawalTransactionData[]): Promise<boolean> {
487
- const {signer, spvVaultContract} = this.chains.chains[vault.chainId];
488
-
489
- try {
490
- const txId = await spvVaultContract.claim(signer, vault.data, withdrawal.map(tx => {
491
- return {tx};
492
- }), undefined, true, {waitForConfirmation: true});
493
- this.logger.info("claimWithdrawal(): Successfully claimed withdrawals, btcTxIds: "+withdrawal.map(val => val.btcTx.txid).join(", ")+" smartChainTxId: "+txId);
494
- return true;
495
- } catch (e) {
496
- this.logger.error("claimWithdrawal(): Tried to claim but got error: ", e);
497
- return false;
498
- }
499
- }
500
-
501
- async getVault(chainId: string, owner: string, vaultId: bigint) {
502
- return this.vaultStorage.data[chainId+"_"+owner+"_"+vaultId.toString(10)];
503
- }
504
-
505
- /**
506
- * Returns a ready-to-use vault for a specific request
507
- *
508
- * @param chainIdentifier
509
- * @param totalSats
510
- * @param token
511
- * @param amount
512
- * @param gasToken
513
- * @param gasTokenAmount
514
- * @protected
515
- */
516
- async findVaultForSwap(chainIdentifier: string, totalSats: bigint, token: string, amount: bigint, gasToken: string, gasTokenAmount: bigint): Promise<SpvVault | null> {
517
- const {signer} = this.chains.chains[chainIdentifier];
518
-
519
- const pluginResponse = await PluginManager.onVaultSelection(
520
- chainIdentifier, totalSats, {token, amount}, {token: gasToken, amount: gasTokenAmount}
521
- );
522
- if(pluginResponse!=null) {
523
- AmountAssertions.handlePluginErrorResponses(pluginResponse);
524
- return pluginResponse as SpvVault;
525
- }
526
-
527
- const candidates = Object.keys(this.vaultStorage.data)
528
- .map(key => this.vaultStorage.data[key])
529
- .filter(vault =>
530
- vault.chainId===chainIdentifier && vault.data.getOwner()===signer.getAddress() && vault.isReady()
531
- )
532
- .filter(vault => {
533
- const token0 = vault.balances[0];
534
- if(token0.token!==token || token0.scaledAmount < amount) return false;
535
- if(gasToken!=null && gasTokenAmount!==0n) {
536
- const token1 = vault.balances[1];
537
- if(token1.token!==gasToken || token1.scaledAmount < gasTokenAmount) return false;
538
- }
539
- return true;
540
- });
541
-
542
- candidates.sort((a, b) => bigIntSorter(a.balances[0].scaledAmount, b.balances[0].scaledAmount));
543
-
544
- const result = candidates[0];
545
-
546
- if(result==null) throw {
547
- code: 20301,
548
- msg: "No suitable swap vault found, try again later!"
549
- };
550
-
551
- return result;
552
- }
553
-
554
- saveVault(vault: SpvVault) {
555
- return this.vaultStorage.saveData(vault.getIdentifier(), vault);
556
- }
557
-
558
- async startVaultsWatchdog() {
559
- let rerun: () => Promise<void>;
560
- rerun = async () => {
561
- await this.checkVaults().catch( e => this.logger.error("startVaultsWatchdog(): Error when periodically checking SPV vaults: ", e));
562
- setTimeout(rerun, this.config.vaultsCheckInterval);
563
- };
564
- await rerun();
565
- }
566
-
567
- async init() {
568
- const vaults = await this.vaultStorage.loadData(SpvVault);
569
- }
570
-
571
- /**
572
- * Recovers already created vaults for a given chain from on-chain data. Requires initialized BTC wallet to
573
- * fetch wallet transactions
574
- *
575
- * @param chainId
576
- */
577
- async recoverVaults(chainId: string): Promise<SpvVault[]> {
578
- const chain = this.chains.chains[chainId];
579
- if(chainId==null) throw new Error(`Chain ${chainId} not found in known chains!`);
580
- const vaults = await chain.spvVaultContract.getAllVaults(chain.signer.getAddress());
581
-
582
- const recoveredVaults: SpvVault[] = [];
583
- let minimumBlockheight = null;
584
-
585
- for(let vaultData of vaults) {
586
- const vaultIdentifier = SpvVault._getIdentifier(chainId, vaultData);
587
- if(this.vaultStorage.data[vaultIdentifier]!=null) {
588
- this.logger.info(`recoverVaults(${chainId}): Skipping vault ${vaultIdentifier}, because it is already known!`);
589
- continue;
590
- }
591
- const [txId, voutStr] = vaultData.getUtxo().split(":");
592
- const btcTx = await this.bitcoinRpc.getTransaction(txId);
593
- const btcTxOutput = btcTx.outs[parseInt(voutStr)];
594
- const vaultAddress = this.bitcoin.fromOutputScript(Buffer.from(btcTxOutput.scriptPubKey.hex, "hex"));
595
- const vault = new SpvVault(chainId, vaultData, vaultAddress);
596
- vault.state = vaultData.isOpened() ? SpvVaultState.OPENED : SpvVaultState.CLOSED;
597
- recoveredVaults.push(vault);
598
- if(vaultData.isOpened() && await this.bitcoinRpc.isSpent(vaultData.getUtxo())) {
599
- if(!this.bitcoin.isReady())
600
- throw new Error("Bitcoin wallet is not ready, but is required to check wallet transactions!");
601
-
602
- //The latest smart chain UTXO is spent, we need to check if we have some further transactions
603
- // spending the vault UTXO in our wallet history
604
- const btcTxBlock = await this.bitcoinRpc.getBlockHeader(btcTx.blockhash);
605
- minimumBlockheight = minimumBlockheight==null
606
- ? btcTxBlock.getHeight()
607
- : Math.min(minimumBlockheight, btcTxBlock.getHeight());
608
- }
609
- }
610
-
611
- if(minimumBlockheight!=null) {
612
- const txinMap = new Map<string, BtcTx>();
613
- const txs = await this.bitcoin.getWalletTransactions(minimumBlockheight);
614
- txs.forEach(tx => {
615
- tx.ins.forEach(txin => {
616
- txinMap.set(txin.txid+":"+txin.vout, tx);
617
- })
618
- });
619
-
620
- for(let vault of recoveredVaults) {
621
- let utxo = vault.data.getUtxo();
622
- let btcTx: BtcTx;
623
- do {
624
- btcTx = txinMap.get(utxo);
625
- if(btcTx!=null) {
626
- const withdrawalData = await chain.spvVaultContract.getWithdrawalData(btcTx);
627
- vault.addWithdrawal(withdrawalData);
628
- utxo = withdrawalData.getCreatedVaultUtxo();
629
- }
630
- } while(btcTx!=null);
631
- }
632
- }
633
-
634
- for(let vault of recoveredVaults) {
635
- await this.saveVault(vault);
636
- }
637
-
638
- return recoveredVaults;
639
- }
640
-
1
+ import {SpvVault, SpvVaultState} from "./SpvVault";
2
+ import {
3
+ BitcoinRpc,
4
+ BtcBlock,
5
+ BtcTx,
6
+ IStorageManager,
7
+ SpvVaultClaimEvent,
8
+ SpvVaultCloseEvent,
9
+ SpvVaultDepositEvent,
10
+ SpvVaultOpenEvent,
11
+ SpvWithdrawalTransactionData
12
+ } from "@atomiqlabs/base";
13
+ import {SpvVaultSwap} from "./SpvVaultSwap";
14
+ import {bigIntSorter, getLogger} from "../../utils/Utils";
15
+ import {PluginManager} from "../../plugins/PluginManager";
16
+ import {IBitcoinWallet} from "../../wallets/IBitcoinWallet";
17
+ import {ISpvVaultSigner} from "../../wallets/ISpvVaultSigner";
18
+ import {AmountAssertions} from "../assertions/AmountAssertions";
19
+ import {MultichainData} from "../SwapHandler";
20
+ import {Transaction} from "@scure/btc-signer";
21
+ import {checkTransactionReplaced} from "../../utils/BitcoinUtils";
22
+
23
+ export const VAULT_DUST_AMOUNT = 600;
24
+ const VAULT_INIT_CONFIRMATIONS = 2;
25
+ const MAX_PARALLEL_VAULTS_OPENING = 10;
26
+
27
+ export class SpvVaults {
28
+
29
+ readonly vaultStorage: IStorageManager<SpvVault>;
30
+
31
+ readonly bitcoin: IBitcoinWallet;
32
+ readonly vaultSigner: ISpvVaultSigner;
33
+ readonly bitcoinRpc: BitcoinRpc<BtcBlock>;
34
+ readonly config: {vaultsCheckInterval: number, maxUnclaimedWithdrawals?: number};
35
+ readonly chains: MultichainData;
36
+
37
+ readonly logger = getLogger("SpvVaults: ");
38
+
39
+ constructor(
40
+ vaultStorage: IStorageManager<SpvVault>,
41
+ bitcoin: IBitcoinWallet,
42
+ vaultSigner: ISpvVaultSigner,
43
+ bitcoinRpc: BitcoinRpc<any>,
44
+ chains: MultichainData,
45
+ config: {vaultsCheckInterval: number, maxUnclaimedWithdrawals?: number}
46
+ ) {
47
+ this.vaultStorage = vaultStorage;
48
+ this.bitcoin = bitcoin;
49
+ this.vaultSigner = vaultSigner;
50
+ this.bitcoinRpc = bitcoinRpc;
51
+ this.chains = chains;
52
+ this.config = config;
53
+
54
+ for(let chainId in chains.chains) {
55
+ const {chainInterface} = chains.chains[chainId];
56
+ chainInterface.onBeforeTxReplace(async (oldTx: string, oldTxId: string, newTx: string, newTxId: string) => {
57
+ for(let key in this.vaultStorage.data) {
58
+ const vaultData = this.vaultStorage.data[key];
59
+ if(vaultData.scOpenTxs!=null && vaultData.scOpenTxs[oldTxId]!=null){
60
+ vaultData.scOpenTxs[newTxId] = newTx;
61
+ await this.saveVault(vaultData);
62
+ break;
63
+ }
64
+ }
65
+ });
66
+ }
67
+ }
68
+
69
+ async processDepositEvent(vault: SpvVault, event: SpvVaultDepositEvent): Promise<void> {
70
+ vault.update(event);
71
+ await this.saveVault(vault);
72
+ }
73
+
74
+ async processOpenEvent(vault: SpvVault, event: SpvVaultOpenEvent): Promise<void> {
75
+ if(vault.state===SpvVaultState.BTC_CONFIRMED) {
76
+ vault.state = SpvVaultState.OPENED;
77
+ }
78
+ vault.update(event);
79
+ await this.saveVault(vault);
80
+ }
81
+
82
+ async processCloseEvent(vault: SpvVault, event: SpvVaultCloseEvent): Promise<void> {
83
+ if(vault.state===SpvVaultState.OPENED) {
84
+ vault.state = SpvVaultState.CLOSED;
85
+ }
86
+ vault.update(event);
87
+ await this.saveVault(vault);
88
+ }
89
+
90
+ async processClaimEvent(vault: SpvVault, swap: SpvVaultSwap | null, event: SpvVaultClaimEvent): Promise<void> {
91
+ //Update vault
92
+ const foundPendingWithdrawal = vault.pendingWithdrawals.findIndex(val => val.btcTx.txid===event.btcTxId);
93
+ if(foundPendingWithdrawal!==-1) vault.pendingWithdrawals.splice(foundPendingWithdrawal, 1);
94
+ vault.update(event);
95
+ await this.saveVault(vault);
96
+ }
97
+
98
+ async createVaults(chainId: string, count: number, token: string, confirmations: number = 2, feeRate?: number): Promise<{vaultsCreated: bigint[], btcTxId: string}> {
99
+ const {signer, chainInterface, tokenMultipliers, spvVaultContract} = this.chains.chains[chainId];
100
+
101
+ const signerAddress = signer.getAddress();
102
+
103
+ //Check vaultId of the latest saved vault
104
+ let latestVaultId: bigint = -1n;
105
+ for(let key in this.vaultStorage.data) {
106
+ const vault = this.vaultStorage.data[key];
107
+ if(vault.chainId!==chainId) continue;
108
+ if(vault.data.getOwner()!==signerAddress) continue;
109
+ if(vault.data.getVaultId() > latestVaultId) latestVaultId = vault.data.getVaultId();
110
+ }
111
+
112
+ latestVaultId++;
113
+
114
+ const vaultAddreses: {vaultId: bigint, address: string}[] = [];
115
+ for(let i=0;i<count;i++) {
116
+ const vaultId = latestVaultId + BigInt(i);
117
+ const address = await this.vaultSigner.getAddress(chainId, vaultId);
118
+ vaultAddreses.push({vaultId, address});
119
+ }
120
+
121
+ const nativeToken = chainInterface.getNativeCurrencyAddress();
122
+
123
+ let txId: string = null;
124
+ let vaults: SpvVault[] = null;
125
+ await this.bitcoin.execute(async () => {
126
+ //Construct transaction
127
+ const txResult = await this.bitcoin.getSignedMultiTransaction(vaultAddreses.map(val => {
128
+ return {address: val.address, amount: VAULT_DUST_AMOUNT}
129
+ }), feeRate);
130
+
131
+ vaults = await Promise.all(vaultAddreses.map(async (val, index) => {
132
+ const vaultData = await spvVaultContract.createVaultData(signerAddress, val.vaultId, txResult.txId+":"+index, confirmations, [
133
+ {token, multiplier: tokenMultipliers?.[token] ?? 1n},
134
+ {token: nativeToken, multiplier: tokenMultipliers?.[nativeToken] ?? 1n}
135
+ ]);
136
+ return new SpvVault(chainId, vaultData, val.address);
137
+ }));
138
+
139
+ //Save vaults
140
+ if(this.vaultStorage.saveDataArr!=null) {
141
+ await this.vaultStorage.saveDataArr(vaults.map(val => {
142
+ return {id: val.getIdentifier(), object: val}
143
+ }));
144
+ } else {
145
+ for(let vault of vaults) {
146
+ await this.vaultStorage.saveData(vault.getIdentifier(), vault);
147
+ }
148
+ }
149
+
150
+ //Send bitcoin tx
151
+ await this.bitcoin.sendRawTransaction(txResult.raw);
152
+
153
+ txId = txResult.txId;
154
+ });
155
+
156
+ this.logger.info("createVaults(): Funding "+count+" vaults, bitcoin txId: "+txId);
157
+
158
+ return {
159
+ vaultsCreated: vaults.map(val => val.data.getVaultId()),
160
+ btcTxId: txId
161
+ };
162
+ }
163
+
164
+ async listVaults(chainId?: string, token?: string) {
165
+ return Object.keys(this.vaultStorage.data)
166
+ .map(key => this.vaultStorage.data[key])
167
+ .filter(val => chainId==null ? true : val.chainId===chainId)
168
+ .filter(val => this.chains.chains[val.chainId]!=null && val.data.getOwner()===this.chains.chains[val.chainId]?.signer?.getAddress())
169
+ .filter(val => token==null ? true : val.data.getTokenData()[0].token===token);
170
+ }
171
+
172
+ async fundVault(vault: SpvVault, tokenAmounts: bigint[]): Promise<string> {
173
+ if(vault.state!==SpvVaultState.OPENED) throw new Error("Vault not opened!");
174
+
175
+ this.logger.info("fundVault(): Depositing tokens to the vault "+vault.data.getVaultId().toString(10)+", amounts: "+tokenAmounts.map(val => val.toString(10)).join(", "));
176
+
177
+ const {signer, spvVaultContract} = this.chains.chains[vault.chainId];
178
+
179
+ const txId = await spvVaultContract.deposit(signer, vault.data, tokenAmounts, {waitForConfirmation: true});
180
+
181
+ this.logger.info("fundVault(): Tokens deposited to vault "+vault.data.getVaultId().toString(10)+", amounts: "+tokenAmounts.map(val => val.toString(10)).join(", ")+", txId: "+txId);
182
+
183
+ return txId;
184
+ }
185
+
186
+ async withdrawFromVault(vault: SpvVault, tokenAmounts: bigint[], feeRate?: number): Promise<string> {
187
+ tokenAmounts.forEach((rawAmount, index) => {
188
+ if(vault.balances[index]==null) throw new Error("Token not found in the vault");
189
+ if(vault.balances[index].rawAmount<rawAmount) throw new Error("Not enough balance in the vault");
190
+ });
191
+
192
+ if(!vault.isReady()) throw new Error("Vault not ready, wait for the latest swap to get at least 1 confirmation!");
193
+
194
+ const {signer, spvVaultContract} = this.chains.chains[vault.chainId];
195
+
196
+ const latestUtxo = vault.getLatestUtxo();
197
+ const [txId, voutStr] = latestUtxo.split(":");
198
+
199
+ const opReturnData = spvVaultContract.toOpReturnData(signer.getAddress(), tokenAmounts);
200
+ let opReturnScript: Buffer;
201
+ if(opReturnData.length<76) {
202
+ opReturnScript = Buffer.concat([
203
+ Buffer.from([0x6a, opReturnData.length]),
204
+ opReturnData
205
+ ]);
206
+ } else {
207
+ opReturnScript = Buffer.concat([
208
+ Buffer.from([0x6a, 0x4c, opReturnData.length]),
209
+ opReturnData
210
+ ]);
211
+ }
212
+
213
+ let psbt = new Transaction({
214
+ allowUnknownOutputs: true
215
+ });
216
+ psbt.addInput({
217
+ txid: txId,
218
+ index: parseInt(voutStr),
219
+ witnessUtxo: {
220
+ amount: BigInt(VAULT_DUST_AMOUNT),
221
+ script: this.bitcoin.toOutputScript(vault.btcAddress)
222
+ }
223
+ });
224
+ psbt.addOutput({
225
+ amount: BigInt(VAULT_DUST_AMOUNT),
226
+ script: this.bitcoin.toOutputScript(vault.btcAddress)
227
+ });
228
+ psbt.addOutput({
229
+ amount: 0n,
230
+ script: opReturnScript
231
+ });
232
+
233
+ let withdrawalTxId: string = null;
234
+ await this.bitcoin.execute(async () => {
235
+ psbt = await this.bitcoin.fundPsbt(psbt, feeRate);
236
+ if(psbt.inputsLength<2) throw new Error("PSBT needs at least 2 inputs!");
237
+ psbt.updateInput(0, {sequence: 0x80000000});
238
+ psbt.updateInput(1, {sequence: 0x80000000});
239
+ psbt = await this.vaultSigner.signPsbt(vault.chainId, vault.data.getVaultId(), psbt, [0]);
240
+ const res = await this.bitcoin.signPsbt(psbt);
241
+ withdrawalTxId = res.txId;
242
+
243
+ const parsedTransaction = await this.bitcoinRpc.parseTransaction(res.raw);
244
+ const withdrawalData = await spvVaultContract.getWithdrawalData(parsedTransaction);
245
+
246
+ if(withdrawalData.getSpentVaultUtxo()!==vault.getLatestUtxo()) {
247
+ throw new Error("Latest vault UTXO already spent! Please try again later.");
248
+ }
249
+ (withdrawalData as any).sending = true;
250
+ vault.addWithdrawal(withdrawalData);
251
+ await this.saveVault(vault);
252
+
253
+ try {
254
+ await this.bitcoin.sendRawTransaction(res.raw);
255
+ (withdrawalData as any).sending = false;
256
+ } catch (e) {
257
+ (withdrawalData as any).sending = false;
258
+ vault.removeWithdrawal(withdrawalData);
259
+ await this.saveVault(vault);
260
+ throw e;
261
+ }
262
+ });
263
+
264
+ return withdrawalTxId;
265
+ }
266
+
267
+ /**
268
+ * Call this to check whether some of the previously replaced transactions got re-introduced to the mempool
269
+ *
270
+ * @param vault
271
+ * @param save
272
+ */
273
+ async checkVaultReplacedTransactions(vault: SpvVault, save?: boolean): Promise<boolean> {
274
+ const {spvVaultContract} = this.chains.chains[vault.chainId];
275
+
276
+ const initialVaultWithdrawalCount = vault.data.getWithdrawalCount();
277
+
278
+ let latestWithdrawalIndex = initialVaultWithdrawalCount;
279
+ const newPendingTxns: SpvWithdrawalTransactionData[] = [];
280
+ const reintroducedTxIds: Set<string> = new Set();
281
+ for(let [withdrawalIndex, replacedWithdrawalGroup] of vault.replacedWithdrawals) {
282
+ if(withdrawalIndex<=latestWithdrawalIndex) continue; //Don't check txns that should already be included
283
+
284
+ for(let replacedWithdrawal of replacedWithdrawalGroup) {
285
+ if(reintroducedTxIds.has(replacedWithdrawal.getTxId())) continue;
286
+ const tx = await this.bitcoinRpc.getTransaction(replacedWithdrawal.getTxId());
287
+ if(tx==null) continue;
288
+
289
+ //Re-introduce transaction to the pending withdrawals list
290
+ if(withdrawalIndex>latestWithdrawalIndex) {
291
+ const txChain: SpvWithdrawalTransactionData[] = [replacedWithdrawal];
292
+ withdrawalIndex--;
293
+ while(withdrawalIndex>latestWithdrawalIndex) {
294
+ const tx = await this.bitcoinRpc.getTransaction(txChain[0].getSpentVaultUtxo().split(":")[0]);
295
+ if(tx==null) break;
296
+ reintroducedTxIds.add(tx.txid);
297
+ txChain.unshift(await spvVaultContract.getWithdrawalData(tx));
298
+ withdrawalIndex--;
299
+ }
300
+ if(withdrawalIndex>latestWithdrawalIndex) {
301
+ this.logger.warn(`checkVaultReplacedTransactions(${vault.getIdentifier()}): Tried to re-introduce previously replaced TX, but one of txns in the chain not found!`);
302
+ continue;
303
+ }
304
+ newPendingTxns.push(...txChain);
305
+ latestWithdrawalIndex += txChain.length;
306
+ break; //Don't check other txns at the same withdrawal index
307
+ } else {
308
+ this.logger.warn(`checkVaultReplacedTransactions(${vault.getIdentifier()}): Tried to re-introduce previously replaced TX, but vault has already processed such withdrawal!`);
309
+ }
310
+ }
311
+ }
312
+
313
+ if(newPendingTxns.length===0) return false;
314
+
315
+ if(initialVaultWithdrawalCount!==vault.data.getWithdrawalCount()) {
316
+ this.logger.warn(`checkVaultReplacedTransactions(${vault.getIdentifier()}): Not saving vault after checking replaced transactions, due to withdrawal count changed!`);
317
+ return false;
318
+ }
319
+
320
+ const backup = vault.pendingWithdrawals.splice(0, newPendingTxns.length);
321
+ const txsToAddOnTop = vault.pendingWithdrawals.splice(0, vault.pendingWithdrawals.length);
322
+
323
+ try {
324
+ newPendingTxns.forEach(val => vault.addWithdrawal(val));
325
+ txsToAddOnTop.forEach(val => vault.addWithdrawal(val));
326
+ for(let i=0;i<newPendingTxns.length;i++) {
327
+ const withdrawalIndex = initialVaultWithdrawalCount+i+1;
328
+ const arr = vault.replacedWithdrawals.get(withdrawalIndex);
329
+ if(arr==null) continue;
330
+ const index = arr.indexOf(newPendingTxns[i]);
331
+ if(index===-1) {
332
+ this.logger.warn(`checkVaultReplacedTransactions(${vault.getIdentifier()}): Cannot remove re-introduced tx ${newPendingTxns[i].getTxId()}, not found in the respective array!`);
333
+ continue;
334
+ }
335
+ arr.splice(index, 1);
336
+ if(arr.length===0) vault.replacedWithdrawals.delete(withdrawalIndex);
337
+ }
338
+ this.logger.info(`checkVaultReplacedTransactions(${vault.getIdentifier()}): Re-introduced back ${newPendingTxns.length} txns that were re-added to the mempool!`);
339
+ if(save) await this.saveVault(vault);
340
+ return true;
341
+ } catch (e) {
342
+ this.logger.error(`checkVaultReplacedTransactions(${vault.getIdentifier()}): Failed to update the vault with new pending txns (rolling back): `, e);
343
+ //Rollback the pending withdrawals
344
+ vault.pendingWithdrawals.push(...backup, ...txsToAddOnTop);
345
+ return false;
346
+ }
347
+ }
348
+
349
+ async checkVaults() {
350
+ const vaults = Object.keys(this.vaultStorage.data).map(key => this.vaultStorage.data[key]);
351
+
352
+ const claimWithdrawals: {vault: SpvVault, withdrawals: SpvWithdrawalTransactionData[]}[] = [];
353
+
354
+ let promises: Promise<void>[] = [];
355
+
356
+ for(let vault of vaults) {
357
+ const chainData = this.chains.chains[vault.chainId];
358
+ if(chainData==null) continue;
359
+ const {signer, spvVaultContract, chainInterface} = chainData;
360
+ if(vault.data.getOwner()!==signer.getAddress()) continue;
361
+
362
+ if(vault.state===SpvVaultState.BTC_INITIATED) {
363
+ //Check if btc tx confirmed
364
+ const txId = vault.initialUtxo.split(":")[0];
365
+ const btcTx = await this.bitcoinRpc.getTransaction(txId);
366
+ if(btcTx.confirmations >= VAULT_INIT_CONFIRMATIONS) {
367
+ //Double-check the state here to prevent race condition
368
+ if(vault.state===SpvVaultState.BTC_INITIATED) {
369
+ vault.state = SpvVaultState.BTC_CONFIRMED;
370
+ await this.saveVault(vault);
371
+ }
372
+ this.logger.info("checkVaults(): Vault ID "+vault.data.getVaultId().toString(10)+" confirmed on bitcoin, opening vault on "+vault.chainId);
373
+ }
374
+ }
375
+
376
+ if(vault.state===SpvVaultState.BTC_CONFIRMED) {
377
+ //Check if open txs were sent already
378
+ if(vault.scOpenTxs!=null) {
379
+ //Check if confirmed
380
+ let _continue = false;
381
+ for(let txId in vault.scOpenTxs) {
382
+ const tx = vault.scOpenTxs[txId];
383
+ const status = await chainInterface.getTxStatus(tx);
384
+ if(status==="pending") {
385
+ _continue = true;
386
+ break;
387
+ }
388
+ if(status==="success") {
389
+ vault.state = SpvVaultState.OPENED;
390
+ await this.saveVault(vault);
391
+ _continue = true;
392
+ break;
393
+ }
394
+ }
395
+ if(_continue) continue;
396
+ }
397
+
398
+ const txs = await spvVaultContract.txsOpen(signer.getAddress(), vault.data);
399
+ let numTx = 0;
400
+ promises.push(
401
+ chainInterface.sendAndConfirm(
402
+ signer, txs, true, undefined, true,
403
+ async (txId: string, rawTx: string) => {
404
+ numTx++;
405
+ if(numTx===txs.length) {
406
+ //Final tx
407
+ vault.scOpenTxs = {[txId]: rawTx};
408
+ await this.saveVault(vault);
409
+ }
410
+ }
411
+ ).then(txIds => {
412
+ this.logger.info("checkVaults(): Vault ID "+vault.data.getVaultId().toString(10)+" opened on "+vault.chainId+" txId: "+txIds.join(", "));
413
+
414
+ vault.state = SpvVaultState.OPENED;
415
+ return this.saveVault(vault);
416
+ })
417
+ );
418
+ if(promises.length>=MAX_PARALLEL_VAULTS_OPENING) {
419
+ await Promise.all(promises);
420
+ promises = [];
421
+ }
422
+ continue;
423
+ }
424
+
425
+ if(vault.state===SpvVaultState.OPENED) {
426
+ let changed = await this.checkVaultReplacedTransactions(vault);
427
+
428
+ //Check if some of the pendingWithdrawals got confirmed
429
+ let latestOwnWithdrawalIndex = -1;
430
+ let latestConfirmedWithdrawalIndex = -1;
431
+ for(let i = vault.pendingWithdrawals.length-1; i>=0; i--) {
432
+ const pendingWithdrawal = vault.pendingWithdrawals[i];
433
+ if(pendingWithdrawal.sending) continue;
434
+
435
+ //Check all the pending withdrawals that were not finalized yet
436
+ const btcTx = await checkTransactionReplaced(pendingWithdrawal.btcTx.txid, pendingWithdrawal.btcTx.raw, this.bitcoinRpc);
437
+ if(btcTx==null) {
438
+ //Probable double-spend, remove from pending withdrawals
439
+ if(!vault.doubleSpendPendingWithdrawal(pendingWithdrawal)) {
440
+ this.logger.warn("checkVaults(): Tried to remove pending withdrawal txId: "+pendingWithdrawal.btcTx.txid+", but doesn't exist anymore!");
441
+ } else {
442
+ this.logger.info("checkVaults(): Successfully removed withdrawal txId: "+pendingWithdrawal.btcTx.txid+", due to being replaced in the mempool!");
443
+ }
444
+ changed = true;
445
+ } else {
446
+ //Update confirmations count
447
+ if(
448
+ pendingWithdrawal.btcTx.confirmations !== btcTx.confirmations ||
449
+ pendingWithdrawal.btcTx.blockhash !== btcTx.blockhash
450
+ ) {
451
+ pendingWithdrawal.btcTx.confirmations = btcTx.confirmations;
452
+ pendingWithdrawal.btcTx.blockhash = btcTx.blockhash;
453
+ changed = true;
454
+ }
455
+ }
456
+
457
+ //Check it has enough confirmations
458
+ if(pendingWithdrawal.btcTx.confirmations >= vault.data.getConfirmations()) {
459
+ latestConfirmedWithdrawalIndex = i;
460
+ //Check if the pending withdrawals contain a withdrawal to our own address
461
+ if (pendingWithdrawal.isRecipient(signer.getAddress())) {
462
+ latestOwnWithdrawalIndex = i;
463
+ }
464
+ }
465
+ }
466
+ if(changed) {
467
+ await this.saveVault(vault);
468
+ }
469
+ if(this.config.maxUnclaimedWithdrawals!=null && latestConfirmedWithdrawalIndex+1 >= this.config.maxUnclaimedWithdrawals) {
470
+ this.logger.info("checkVaults(): Processing withdrawals by self, because a lot of them are unclaimed!");
471
+ claimWithdrawals.push({vault, withdrawals: vault.pendingWithdrawals.slice(0, latestConfirmedWithdrawalIndex+1)});
472
+ } else if(latestOwnWithdrawalIndex!==-1) {
473
+ claimWithdrawals.push({vault, withdrawals: vault.pendingWithdrawals.slice(0, latestOwnWithdrawalIndex+1)});
474
+ }
475
+ }
476
+ }
477
+
478
+ for(let {vault, withdrawals} of claimWithdrawals) {
479
+ if(!await this.claimWithdrawals(vault, withdrawals)) {
480
+ this.logger.error("checkVaults(): Cannot process withdrawals "+withdrawals.map(val => val.btcTx.txid).join(", ")+" for vault: "+vault.data.getVaultId());
481
+ break;
482
+ }
483
+ }
484
+ }
485
+
486
+ async claimWithdrawals(vault: SpvVault, withdrawal: SpvWithdrawalTransactionData[]): Promise<boolean> {
487
+ const {signer, spvVaultContract} = this.chains.chains[vault.chainId];
488
+
489
+ try {
490
+ const txId = await spvVaultContract.claim(signer, vault.data, withdrawal.map(tx => {
491
+ return {tx};
492
+ }), undefined, true, {waitForConfirmation: true});
493
+ this.logger.info("claimWithdrawal(): Successfully claimed withdrawals, btcTxIds: "+withdrawal.map(val => val.btcTx.txid).join(", ")+" smartChainTxId: "+txId);
494
+ return true;
495
+ } catch (e) {
496
+ this.logger.error("claimWithdrawal(): Tried to claim but got error: ", e);
497
+ return false;
498
+ }
499
+ }
500
+
501
+ async getVault(chainId: string, owner: string, vaultId: bigint) {
502
+ return this.vaultStorage.data[chainId+"_"+owner+"_"+vaultId.toString(10)];
503
+ }
504
+
505
+ /**
506
+ * Returns a ready-to-use vault for a specific request
507
+ *
508
+ * @param chainIdentifier
509
+ * @param totalSats
510
+ * @param token
511
+ * @param amount
512
+ * @param gasToken
513
+ * @param gasTokenAmount
514
+ * @protected
515
+ */
516
+ async findVaultForSwap(chainIdentifier: string, totalSats: bigint, token: string, amount: bigint, gasToken: string, gasTokenAmount: bigint): Promise<SpvVault | null> {
517
+ const {signer} = this.chains.chains[chainIdentifier];
518
+
519
+ const pluginResponse = await PluginManager.onVaultSelection(
520
+ chainIdentifier, totalSats, {token, amount}, {token: gasToken, amount: gasTokenAmount}
521
+ );
522
+ if(pluginResponse!=null) {
523
+ AmountAssertions.handlePluginErrorResponses(pluginResponse);
524
+ return pluginResponse as SpvVault;
525
+ }
526
+
527
+ const candidates = Object.keys(this.vaultStorage.data)
528
+ .map(key => this.vaultStorage.data[key])
529
+ .filter(vault =>
530
+ vault.chainId===chainIdentifier && vault.data.getOwner()===signer.getAddress() && vault.isReady()
531
+ )
532
+ .filter(vault => {
533
+ const token0 = vault.balances[0];
534
+ if(token0.token!==token || token0.scaledAmount < amount) return false;
535
+ if(gasToken!=null && gasTokenAmount!==0n) {
536
+ const token1 = vault.balances[1];
537
+ if(token1.token!==gasToken || token1.scaledAmount < gasTokenAmount) return false;
538
+ }
539
+ return true;
540
+ });
541
+
542
+ candidates.sort((a, b) => bigIntSorter(a.balances[0].scaledAmount, b.balances[0].scaledAmount));
543
+
544
+ const result = candidates[0];
545
+
546
+ if(result==null) throw {
547
+ code: 20301,
548
+ msg: "No suitable swap vault found, try again later!"
549
+ };
550
+
551
+ return result;
552
+ }
553
+
554
+ saveVault(vault: SpvVault) {
555
+ return this.vaultStorage.saveData(vault.getIdentifier(), vault);
556
+ }
557
+
558
+ async startVaultsWatchdog() {
559
+ let rerun: () => Promise<void>;
560
+ rerun = async () => {
561
+ await this.checkVaults().catch( e => this.logger.error("startVaultsWatchdog(): Error when periodically checking SPV vaults: ", e));
562
+ setTimeout(rerun, this.config.vaultsCheckInterval);
563
+ };
564
+ await rerun();
565
+ }
566
+
567
+ async init() {
568
+ const vaults = await this.vaultStorage.loadData(SpvVault);
569
+ }
570
+
571
+ /**
572
+ * Recovers already created vaults for a given chain from on-chain data. Requires initialized BTC wallet to
573
+ * fetch wallet transactions
574
+ *
575
+ * @param chainId
576
+ */
577
+ async recoverVaults(chainId: string): Promise<SpvVault[]> {
578
+ const chain = this.chains.chains[chainId];
579
+ if(chainId==null) throw new Error(`Chain ${chainId} not found in known chains!`);
580
+ const vaults = await chain.spvVaultContract.getAllVaults(chain.signer.getAddress());
581
+
582
+ const recoveredVaults: SpvVault[] = [];
583
+ let minimumBlockheight = null;
584
+
585
+ for(let vaultData of vaults) {
586
+ const vaultIdentifier = SpvVault._getIdentifier(chainId, vaultData);
587
+ if(this.vaultStorage.data[vaultIdentifier]!=null) {
588
+ this.logger.info(`recoverVaults(${chainId}): Skipping vault ${vaultIdentifier}, because it is already known!`);
589
+ continue;
590
+ }
591
+ const [txId, voutStr] = vaultData.getUtxo().split(":");
592
+ const btcTx = await this.bitcoinRpc.getTransaction(txId);
593
+ const btcTxOutput = btcTx.outs[parseInt(voutStr)];
594
+ const vaultAddress = this.bitcoin.fromOutputScript(Buffer.from(btcTxOutput.scriptPubKey.hex, "hex"));
595
+ const vault = new SpvVault(chainId, vaultData, vaultAddress);
596
+ vault.state = vaultData.isOpened() ? SpvVaultState.OPENED : SpvVaultState.CLOSED;
597
+ recoveredVaults.push(vault);
598
+ if(vaultData.isOpened() && await this.bitcoinRpc.isSpent(vaultData.getUtxo())) {
599
+ if(!this.bitcoin.isReady())
600
+ throw new Error("Bitcoin wallet is not ready, but is required to check wallet transactions!");
601
+
602
+ //The latest smart chain UTXO is spent, we need to check if we have some further transactions
603
+ // spending the vault UTXO in our wallet history
604
+ const btcTxBlock = await this.bitcoinRpc.getBlockHeader(btcTx.blockhash);
605
+ minimumBlockheight = minimumBlockheight==null
606
+ ? btcTxBlock.getHeight()
607
+ : Math.min(minimumBlockheight, btcTxBlock.getHeight());
608
+ }
609
+ }
610
+
611
+ if(minimumBlockheight!=null) {
612
+ const txinMap = new Map<string, BtcTx>();
613
+ const txs = await this.bitcoin.getWalletTransactions(minimumBlockheight);
614
+ txs.forEach(tx => {
615
+ tx.ins.forEach(txin => {
616
+ txinMap.set(txin.txid+":"+txin.vout, tx);
617
+ })
618
+ });
619
+
620
+ for(let vault of recoveredVaults) {
621
+ let utxo = vault.data.getUtxo();
622
+ let btcTx: BtcTx;
623
+ do {
624
+ btcTx = txinMap.get(utxo);
625
+ if(btcTx!=null) {
626
+ const withdrawalData = await chain.spvVaultContract.getWithdrawalData(btcTx);
627
+ vault.addWithdrawal(withdrawalData);
628
+ utxo = withdrawalData.getCreatedVaultUtxo();
629
+ }
630
+ } while(btcTx!=null);
631
+ }
632
+ }
633
+
634
+ for(let vault of recoveredVaults) {
635
+ await this.saveVault(vault);
636
+ }
637
+
638
+ return recoveredVaults;
639
+ }
640
+
641
641
  }