@atomiqlabs/lp-lib 15.0.7 → 15.0.8

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.
@@ -12,6 +12,7 @@ export declare class SpvVault<D extends SpvWithdrawalTransactionData = SpvWithdr
12
12
  readonly initialUtxo: string;
13
13
  readonly btcAddress: string;
14
14
  readonly pendingWithdrawals: D[];
15
+ readonly replacedWithdrawals: Map<number, D[]>;
15
16
  data: T;
16
17
  state: SpvVaultState;
17
18
  balances: SpvVaultTokenBalance[];
@@ -24,6 +25,7 @@ export declare class SpvVault<D extends SpvWithdrawalTransactionData = SpvWithdr
24
25
  update(event: SpvVaultOpenEvent | SpvVaultDepositEvent | SpvVaultCloseEvent | SpvVaultClaimEvent): void;
25
26
  addWithdrawal(withdrawalData: D): void;
26
27
  removeWithdrawal(withdrawalData: D): boolean;
28
+ doubleSpendPendingWithdrawal(withdrawalData: D): boolean;
27
29
  toRawAmounts(amounts: bigint[]): bigint[];
28
30
  fromRawAmounts(rawAmounts: bigint[]): bigint[];
29
31
  /**
@@ -19,6 +19,7 @@ class SpvVault extends base_1.Lockable {
19
19
  this.initialUtxo = vault.getUtxo();
20
20
  this.btcAddress = btcAddress;
21
21
  this.pendingWithdrawals = [];
22
+ this.replacedWithdrawals = new Map();
22
23
  }
23
24
  else {
24
25
  this.state = chainIdOrObj.state;
@@ -28,6 +29,12 @@ class SpvVault extends base_1.Lockable {
28
29
  this.btcAddress = chainIdOrObj.btcAddress;
29
30
  this.pendingWithdrawals = chainIdOrObj.pendingWithdrawals.map((base_1.SpvWithdrawalTransactionData.deserialize));
30
31
  this.scOpenTx = chainIdOrObj.scOpenTx;
32
+ this.replacedWithdrawals = new Map();
33
+ if (chainIdOrObj.replacedWithdrawals != null) {
34
+ chainIdOrObj.replacedWithdrawals.forEach((val) => {
35
+ this.replacedWithdrawals.set(val[0], val[1].map((base_1.SpvWithdrawalTransactionData.deserialize)));
36
+ });
37
+ }
31
38
  }
32
39
  this.balances = this.data.calculateStateAfter(this.pendingWithdrawals).balances;
33
40
  }
@@ -36,6 +43,15 @@ class SpvVault extends base_1.Lockable {
36
43
  const processedWithdrawalIndex = this.pendingWithdrawals.findIndex(val => val.btcTx.txid === event.btcTxId);
37
44
  if (processedWithdrawalIndex !== -1)
38
45
  this.pendingWithdrawals.splice(0, processedWithdrawalIndex + 1);
46
+ if (event instanceof base_1.SpvVaultClaimEvent) {
47
+ for (let key of this.replacedWithdrawals.keys()) {
48
+ if (key <= event.withdrawCount)
49
+ this.replacedWithdrawals.delete(key);
50
+ }
51
+ }
52
+ if (event instanceof base_1.SpvVaultCloseEvent) {
53
+ this.replacedWithdrawals.clear();
54
+ }
39
55
  }
40
56
  this.data.updateState(event);
41
57
  this.balances = this.data.calculateStateAfter(this.pendingWithdrawals).balances;
@@ -53,6 +69,19 @@ class SpvVault extends base_1.Lockable {
53
69
  this.balances = this.data.calculateStateAfter(this.pendingWithdrawals).balances;
54
70
  return true;
55
71
  }
72
+ doubleSpendPendingWithdrawal(withdrawalData) {
73
+ const index = this.pendingWithdrawals.indexOf(withdrawalData);
74
+ if (index === -1)
75
+ return false;
76
+ this.pendingWithdrawals.splice(index, 1);
77
+ this.balances = this.data.calculateStateAfter(this.pendingWithdrawals).balances;
78
+ const withdrawalIndex = this.data.getWithdrawalCount() + index + 1;
79
+ let arr = this.replacedWithdrawals.get(withdrawalIndex);
80
+ if (arr == null)
81
+ this.replacedWithdrawals.set(withdrawalIndex, arr = []);
82
+ arr.push(withdrawalData);
83
+ return true;
84
+ }
56
85
  toRawAmounts(amounts) {
57
86
  return amounts.map((amt, index) => {
58
87
  const tokenData = this.data.getTokenData()[index];
@@ -76,6 +105,10 @@ class SpvVault extends base_1.Lockable {
76
105
  return this.data.calculateStateAfter(this.pendingWithdrawals.filter(val => val.btcTx.confirmations >= 1)).balances;
77
106
  }
78
107
  serialize() {
108
+ const replacedWithdrawals = [];
109
+ this.replacedWithdrawals.forEach((value, key) => {
110
+ replacedWithdrawals.push([key, value.map(val => val.serialize())]);
111
+ });
79
112
  return {
80
113
  state: this.state,
81
114
  chainId: this.chainId,
@@ -83,6 +116,7 @@ class SpvVault extends base_1.Lockable {
83
116
  initialUtxo: this.initialUtxo,
84
117
  btcAddress: this.btcAddress,
85
118
  pendingWithdrawals: this.pendingWithdrawals.map(val => val.serialize()),
119
+ replacedWithdrawals,
86
120
  scOpenTx: this.scOpenTx
87
121
  };
88
122
  }
@@ -256,7 +256,11 @@ class SpvVaultSwapHandler extends SwapHandler_1.SwapHandler {
256
256
  metadata.times.priceCalculated = Date.now();
257
257
  const totalBtcOutput = amountBD + amountBDgas;
258
258
  //Check if we have enough funds to honor the request
259
- const vault = await this.Vaults.findVaultForSwap(chainIdentifier, totalBtcOutput, useToken, totalInToken, gasToken, totalInGasToken);
259
+ let vault;
260
+ do {
261
+ vault = await this.Vaults.findVaultForSwap(chainIdentifier, totalBtcOutput, useToken, totalInToken, gasToken, totalInGasToken);
262
+ } while (await this.Vaults.checkVaultReplacedTransactions(vault, true));
263
+ abortController.signal.throwIfAborted();
260
264
  metadata.times.vaultPicked = Date.now();
261
265
  //Create swap receive bitcoin address
262
266
  const btcFeeRate = await this.bitcoin.getFeeRate();
@@ -38,6 +38,13 @@ export declare class SpvVaults {
38
38
  }, import("@atomiqlabs/base").SpvVaultData<SpvWithdrawalTransactionData>>[]>;
39
39
  fundVault(vault: SpvVault, tokenAmounts: bigint[]): Promise<string>;
40
40
  withdrawFromVault(vault: SpvVault, tokenAmounts: bigint[], feeRate?: number): Promise<string>;
41
+ /**
42
+ * Call this to check whether some of the previously replaced transactions got re-introduced to the mempool
43
+ *
44
+ * @param vault
45
+ * @param save
46
+ */
47
+ checkVaultReplacedTransactions(vault: SpvVault, save?: boolean): Promise<boolean>;
41
48
  checkVaults(): Promise<void>;
42
49
  claimWithdrawals(vault: SpvVault, withdrawal: SpvWithdrawalTransactionData[]): Promise<boolean>;
43
50
  getVault(chainId: string, owner: string, vaultId: bigint): Promise<SpvVault<SpvWithdrawalTransactionData & {
@@ -190,6 +190,89 @@ class SpvVaults {
190
190
  }
191
191
  return res.txId;
192
192
  }
193
+ /**
194
+ * Call this to check whether some of the previously replaced transactions got re-introduced to the mempool
195
+ *
196
+ * @param vault
197
+ * @param save
198
+ */
199
+ async checkVaultReplacedTransactions(vault, save) {
200
+ const { spvVaultContract } = this.getChain(vault.chainId);
201
+ const initialVaultWithdrawalCount = vault.data.getWithdrawalCount();
202
+ let latestWithdrawalIndex = initialVaultWithdrawalCount;
203
+ const newPendingTxns = [];
204
+ const reintroducedTxIds = new Set();
205
+ for (let [withdrawalIndex, replacedWithdrawalGroup] of vault.replacedWithdrawals) {
206
+ if (withdrawalIndex <= latestWithdrawalIndex)
207
+ continue; //Don't check txns that should already be included
208
+ for (let replacedWithdrawal of replacedWithdrawalGroup) {
209
+ if (reintroducedTxIds.has(replacedWithdrawal.getTxId()))
210
+ continue;
211
+ const tx = await this.bitcoinRpc.getTransaction(replacedWithdrawal.getTxId());
212
+ if (tx == null)
213
+ continue;
214
+ //Re-introduce transaction to the pending withdrawals list
215
+ if (withdrawalIndex > latestWithdrawalIndex) {
216
+ const txChain = [replacedWithdrawal];
217
+ withdrawalIndex--;
218
+ while (withdrawalIndex > latestWithdrawalIndex) {
219
+ const tx = await this.bitcoinRpc.getTransaction(txChain[0].getSpentVaultUtxo().split(":")[0]);
220
+ if (tx == null)
221
+ break;
222
+ reintroducedTxIds.add(tx.txid);
223
+ txChain.unshift(await spvVaultContract.getWithdrawalData(tx));
224
+ withdrawalIndex--;
225
+ }
226
+ if (withdrawalIndex > latestWithdrawalIndex) {
227
+ this.logger.warn(`checkVaultReplacedTransactions(${vault.getIdentifier()}): Tried to re-introduce previously replaced TX, but one of txns in the chain not found!`);
228
+ continue;
229
+ }
230
+ newPendingTxns.push(...txChain);
231
+ latestWithdrawalIndex += txChain.length;
232
+ break; //Don't check other txns at the same withdrawal index
233
+ }
234
+ else {
235
+ this.logger.warn(`checkVaultReplacedTransactions(${vault.getIdentifier()}): Tried to re-introduce previously replaced TX, but vault has already processed such withdrawal!`);
236
+ }
237
+ }
238
+ }
239
+ if (newPendingTxns.length === 0)
240
+ return false;
241
+ if (initialVaultWithdrawalCount !== vault.data.getWithdrawalCount()) {
242
+ this.logger.warn(`checkVaultReplacedTransactions(${vault.getIdentifier()}): Not saving vault after checking replaced transactions, due to withdrawal count changed!`);
243
+ return false;
244
+ }
245
+ const backup = vault.pendingWithdrawals.splice(0, newPendingTxns.length);
246
+ const txsToAddOnTop = vault.pendingWithdrawals.splice(0, vault.pendingWithdrawals.length);
247
+ try {
248
+ newPendingTxns.forEach(val => vault.addWithdrawal(val));
249
+ txsToAddOnTop.forEach(val => vault.addWithdrawal(val));
250
+ for (let i = 0; i < newPendingTxns.length; i++) {
251
+ const withdrawalIndex = initialVaultWithdrawalCount + i + 1;
252
+ const arr = vault.replacedWithdrawals.get(withdrawalIndex);
253
+ if (arr == null)
254
+ continue;
255
+ const index = arr.indexOf(newPendingTxns[i]);
256
+ if (index === -1) {
257
+ this.logger.warn(`checkVaultReplacedTransactions(${vault.getIdentifier()}): Cannot remove re-introduced tx ${newPendingTxns[i].getTxId()}, not found in the respective array!`);
258
+ continue;
259
+ }
260
+ arr.splice(index, 1);
261
+ if (arr.length === 0)
262
+ vault.replacedWithdrawals.delete(withdrawalIndex);
263
+ }
264
+ this.logger.info(`checkVaultReplacedTransactions(${vault.getIdentifier()}): Re-introduced back ${newPendingTxns.length} txns that were re-added to the mempool!`);
265
+ if (save)
266
+ await this.saveVault(vault);
267
+ return true;
268
+ }
269
+ catch (e) {
270
+ this.logger.error(`checkVaultReplacedTransactions(${vault.getIdentifier()}): Failed to update the vault with new pending txns (rolling back): `, e);
271
+ //Rollback the pending withdrawals
272
+ vault.pendingWithdrawals.push(...backup, ...txsToAddOnTop);
273
+ return false;
274
+ }
275
+ }
193
276
  async checkVaults() {
194
277
  const vaults = Object.keys(this.vaultStorage.data).map(key => this.vaultStorage.data[key]);
195
278
  const claimWithdrawals = [];
@@ -238,11 +321,11 @@ class SpvVaults {
238
321
  await this.saveVault(vault);
239
322
  }
240
323
  if (vault.state === SpvVault_1.SpvVaultState.OPENED) {
241
- let changed = false;
324
+ let changed = await this.checkVaultReplacedTransactions(vault);
242
325
  //Check if some of the pendingWithdrawals got confirmed
243
326
  let latestOwnWithdrawalIndex = -1;
244
327
  let latestConfirmedWithdrawalIndex = -1;
245
- for (let i = 0; i < vault.pendingWithdrawals.length; i++) {
328
+ for (let i = vault.pendingWithdrawals.length - 1; i >= 0; i--) {
246
329
  const pendingWithdrawal = vault.pendingWithdrawals[i];
247
330
  if (pendingWithdrawal.sending)
248
331
  continue;
@@ -250,9 +333,12 @@ class SpvVaults {
250
333
  const btcTx = await (0, BitcoinUtils_1.checkTransactionReplacedRpc)(pendingWithdrawal.btcTx.txid, pendingWithdrawal.btcTx.raw, this.bitcoinRpc);
251
334
  if (btcTx == null) {
252
335
  //Probable double-spend, remove from pending withdrawals
253
- if (!vault.removeWithdrawal(pendingWithdrawal)) {
336
+ if (!vault.doubleSpendPendingWithdrawal(pendingWithdrawal)) {
254
337
  this.logger.warn("checkVaults(): Tried to remove pending withdrawal txId: " + pendingWithdrawal.btcTx.txid + ", but doesn't exist anymore!");
255
338
  }
339
+ else {
340
+ this.logger.info("checkVaults(): Successfully removed withdrawal txId: " + pendingWithdrawal.btcTx.txid + ", due to being replaced in the mempool!");
341
+ }
256
342
  changed = true;
257
343
  }
258
344
  else {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@atomiqlabs/lp-lib",
3
- "version": "15.0.7",
3
+ "version": "15.0.8",
4
4
  "description": "Main functionality implementation for atomiq LP node",
5
5
  "main": "./dist/index.js",
6
6
  "types:": "./dist/index.d.ts",
@@ -28,6 +28,7 @@ export class SpvVault<
28
28
  readonly btcAddress: string;
29
29
 
30
30
  readonly pendingWithdrawals: D[];
31
+ readonly replacedWithdrawals: Map<number, D[]>;
31
32
  data: T;
32
33
 
33
34
  state: SpvVaultState;
@@ -47,6 +48,7 @@ export class SpvVault<
47
48
  this.initialUtxo = vault.getUtxo();
48
49
  this.btcAddress = btcAddress;
49
50
  this.pendingWithdrawals = [];
51
+ this.replacedWithdrawals = new Map();
50
52
  } else {
51
53
  this.state = chainIdOrObj.state;
52
54
  this.chainId = chainIdOrObj.chainId;
@@ -55,6 +57,12 @@ export class SpvVault<
55
57
  this.btcAddress = chainIdOrObj.btcAddress;
56
58
  this.pendingWithdrawals = chainIdOrObj.pendingWithdrawals.map(SpvWithdrawalTransactionData.deserialize<D>);
57
59
  this.scOpenTx = chainIdOrObj.scOpenTx;
60
+ this.replacedWithdrawals = new Map();
61
+ if(chainIdOrObj.replacedWithdrawals!=null) {
62
+ chainIdOrObj.replacedWithdrawals.forEach((val: [number, any[]]) => {
63
+ this.replacedWithdrawals.set(val[0], val[1].map(SpvWithdrawalTransactionData.deserialize<D>));
64
+ });
65
+ }
58
66
  }
59
67
  this.balances = this.data.calculateStateAfter(this.pendingWithdrawals).balances;
60
68
  }
@@ -63,6 +71,14 @@ export class SpvVault<
63
71
  if(event instanceof SpvVaultClaimEvent || event instanceof SpvVaultCloseEvent) {
64
72
  const processedWithdrawalIndex = this.pendingWithdrawals.findIndex(val => val.btcTx.txid === event.btcTxId);
65
73
  if(processedWithdrawalIndex!==-1) this.pendingWithdrawals.splice(0, processedWithdrawalIndex + 1);
74
+ if(event instanceof SpvVaultClaimEvent) {
75
+ for(let key of this.replacedWithdrawals.keys()) {
76
+ if(key<=event.withdrawCount) this.replacedWithdrawals.delete(key);
77
+ }
78
+ }
79
+ if(event instanceof SpvVaultCloseEvent) {
80
+ this.replacedWithdrawals.clear();
81
+ }
66
82
  }
67
83
  this.data.updateState(event);
68
84
  this.balances = this.data.calculateStateAfter(this.pendingWithdrawals).balances;
@@ -82,6 +98,19 @@ export class SpvVault<
82
98
  return true;
83
99
  }
84
100
 
101
+ doubleSpendPendingWithdrawal(withdrawalData: D): boolean {
102
+ const index = this.pendingWithdrawals.indexOf(withdrawalData);
103
+ if(index===-1) return false;
104
+ this.pendingWithdrawals.splice(index, 1);
105
+ this.balances = this.data.calculateStateAfter(this.pendingWithdrawals).balances;
106
+
107
+ const withdrawalIndex = this.data.getWithdrawalCount()+index+1;
108
+ let arr = this.replacedWithdrawals.get(withdrawalIndex);
109
+ if(arr==null) this.replacedWithdrawals.set(withdrawalIndex, arr = []);
110
+ arr.push(withdrawalData);
111
+ return true;
112
+ }
113
+
85
114
  toRawAmounts(amounts: bigint[]): bigint[] {
86
115
  return amounts.map((amt, index) => {
87
116
  const tokenData = this.data.getTokenData()[index];
@@ -106,6 +135,11 @@ export class SpvVault<
106
135
  }
107
136
 
108
137
  serialize(): any {
138
+ const replacedWithdrawals: [number, any[]][] = [];
139
+ this.replacedWithdrawals.forEach((value, key) => {
140
+ replacedWithdrawals.push([key, value.map(val => val.serialize())])
141
+ });
142
+
109
143
  return {
110
144
  state: this.state,
111
145
  chainId: this.chainId,
@@ -113,6 +147,7 @@ export class SpvVault<
113
147
  initialUtxo: this.initialUtxo,
114
148
  btcAddress: this.btcAddress,
115
149
  pendingWithdrawals: this.pendingWithdrawals.map(val => val.serialize()),
150
+ replacedWithdrawals,
116
151
  scOpenTx: this.scOpenTx
117
152
  }
118
153
  }
@@ -352,7 +352,11 @@ export class SpvVaultSwapHandler extends SwapHandler<SpvVaultSwap, SpvVaultSwapS
352
352
  const totalBtcOutput = amountBD + amountBDgas;
353
353
 
354
354
  //Check if we have enough funds to honor the request
355
- const vault = await this.Vaults.findVaultForSwap(chainIdentifier, totalBtcOutput, useToken, totalInToken, gasToken, totalInGasToken);
355
+ let vault: SpvVault;
356
+ do {
357
+ vault = await this.Vaults.findVaultForSwap(chainIdentifier, totalBtcOutput, useToken, totalInToken, gasToken, totalInGasToken);
358
+ } while (await this.Vaults.checkVaultReplacedTransactions(vault, true));
359
+ abortController.signal.throwIfAborted();
356
360
  metadata.times.vaultPicked = Date.now();
357
361
 
358
362
  //Create swap receive bitcoin address
@@ -241,6 +241,88 @@ export class SpvVaults {
241
241
  return res.txId;
242
242
  }
243
243
 
244
+ /**
245
+ * Call this to check whether some of the previously replaced transactions got re-introduced to the mempool
246
+ *
247
+ * @param vault
248
+ * @param save
249
+ */
250
+ async checkVaultReplacedTransactions(vault: SpvVault, save?: boolean): Promise<boolean> {
251
+ const {spvVaultContract} = this.getChain(vault.chainId);
252
+
253
+ const initialVaultWithdrawalCount = vault.data.getWithdrawalCount();
254
+
255
+ let latestWithdrawalIndex = initialVaultWithdrawalCount;
256
+ const newPendingTxns: SpvWithdrawalTransactionData[] = [];
257
+ const reintroducedTxIds: Set<string> = new Set();
258
+ for(let [withdrawalIndex, replacedWithdrawalGroup] of vault.replacedWithdrawals) {
259
+ if(withdrawalIndex<=latestWithdrawalIndex) continue; //Don't check txns that should already be included
260
+
261
+ for(let replacedWithdrawal of replacedWithdrawalGroup) {
262
+ if(reintroducedTxIds.has(replacedWithdrawal.getTxId())) continue;
263
+ const tx = await this.bitcoinRpc.getTransaction(replacedWithdrawal.getTxId());
264
+ if(tx==null) continue;
265
+
266
+ //Re-introduce transaction to the pending withdrawals list
267
+ if(withdrawalIndex>latestWithdrawalIndex) {
268
+ const txChain: SpvWithdrawalTransactionData[] = [replacedWithdrawal];
269
+ withdrawalIndex--;
270
+ while(withdrawalIndex>latestWithdrawalIndex) {
271
+ const tx = await this.bitcoinRpc.getTransaction(txChain[0].getSpentVaultUtxo().split(":")[0]);
272
+ if(tx==null) break;
273
+ reintroducedTxIds.add(tx.txid);
274
+ txChain.unshift(await spvVaultContract.getWithdrawalData(tx));
275
+ withdrawalIndex--;
276
+ }
277
+ if(withdrawalIndex>latestWithdrawalIndex) {
278
+ this.logger.warn(`checkVaultReplacedTransactions(${vault.getIdentifier()}): Tried to re-introduce previously replaced TX, but one of txns in the chain not found!`);
279
+ continue;
280
+ }
281
+ newPendingTxns.push(...txChain);
282
+ latestWithdrawalIndex += txChain.length;
283
+ break; //Don't check other txns at the same withdrawal index
284
+ } else {
285
+ this.logger.warn(`checkVaultReplacedTransactions(${vault.getIdentifier()}): Tried to re-introduce previously replaced TX, but vault has already processed such withdrawal!`);
286
+ }
287
+ }
288
+ }
289
+
290
+ if(newPendingTxns.length===0) return false;
291
+
292
+ if(initialVaultWithdrawalCount!==vault.data.getWithdrawalCount()) {
293
+ this.logger.warn(`checkVaultReplacedTransactions(${vault.getIdentifier()}): Not saving vault after checking replaced transactions, due to withdrawal count changed!`);
294
+ return false;
295
+ }
296
+
297
+ const backup = vault.pendingWithdrawals.splice(0, newPendingTxns.length);
298
+ const txsToAddOnTop = vault.pendingWithdrawals.splice(0, vault.pendingWithdrawals.length);
299
+
300
+ try {
301
+ newPendingTxns.forEach(val => vault.addWithdrawal(val));
302
+ txsToAddOnTop.forEach(val => vault.addWithdrawal(val));
303
+ for(let i=0;i<newPendingTxns.length;i++) {
304
+ const withdrawalIndex = initialVaultWithdrawalCount+i+1;
305
+ const arr = vault.replacedWithdrawals.get(withdrawalIndex);
306
+ if(arr==null) continue;
307
+ const index = arr.indexOf(newPendingTxns[i]);
308
+ if(index===-1) {
309
+ this.logger.warn(`checkVaultReplacedTransactions(${vault.getIdentifier()}): Cannot remove re-introduced tx ${newPendingTxns[i].getTxId()}, not found in the respective array!`);
310
+ continue;
311
+ }
312
+ arr.splice(index, 1);
313
+ if(arr.length===0) vault.replacedWithdrawals.delete(withdrawalIndex);
314
+ }
315
+ this.logger.info(`checkVaultReplacedTransactions(${vault.getIdentifier()}): Re-introduced back ${newPendingTxns.length} txns that were re-added to the mempool!`);
316
+ if(save) await this.saveVault(vault);
317
+ return true;
318
+ } catch (e) {
319
+ this.logger.error(`checkVaultReplacedTransactions(${vault.getIdentifier()}): Failed to update the vault with new pending txns (rolling back): `, e);
320
+ //Rollback the pending withdrawals
321
+ vault.pendingWithdrawals.push(...backup, ...txsToAddOnTop);
322
+ return false;
323
+ }
324
+ }
325
+
244
326
  async checkVaults() {
245
327
  const vaults = Object.keys(this.vaultStorage.data).map(key => this.vaultStorage.data[key]);
246
328
 
@@ -297,11 +379,12 @@ export class SpvVaults {
297
379
  }
298
380
 
299
381
  if(vault.state===SpvVaultState.OPENED) {
300
- let changed = false;
382
+ let changed = await this.checkVaultReplacedTransactions(vault);
383
+
301
384
  //Check if some of the pendingWithdrawals got confirmed
302
385
  let latestOwnWithdrawalIndex = -1;
303
386
  let latestConfirmedWithdrawalIndex = -1;
304
- for(let i=0; i<vault.pendingWithdrawals.length; i++) {
387
+ for(let i = vault.pendingWithdrawals.length-1; i>=0; i--) {
305
388
  const pendingWithdrawal = vault.pendingWithdrawals[i];
306
389
  if(pendingWithdrawal.sending) continue;
307
390
 
@@ -309,8 +392,10 @@ export class SpvVaults {
309
392
  const btcTx = await checkTransactionReplacedRpc(pendingWithdrawal.btcTx.txid, pendingWithdrawal.btcTx.raw, this.bitcoinRpc);
310
393
  if(btcTx==null) {
311
394
  //Probable double-spend, remove from pending withdrawals
312
- if(!vault.removeWithdrawal(pendingWithdrawal)) {
313
- this.logger.warn("checkVaults(): Tried to remove pending withdrawal txId: "+pendingWithdrawal.btcTx.txid+", but doesn't exist anymore!")
395
+ if(!vault.doubleSpendPendingWithdrawal(pendingWithdrawal)) {
396
+ this.logger.warn("checkVaults(): Tried to remove pending withdrawal txId: "+pendingWithdrawal.btcTx.txid+", but doesn't exist anymore!");
397
+ } else {
398
+ this.logger.info("checkVaults(): Successfully removed withdrawal txId: "+pendingWithdrawal.btcTx.txid+", due to being replaced in the mempool!");
314
399
  }
315
400
  changed = true;
316
401
  } else {