@atomiqlabs/lp-lib 17.6.0 → 17.6.1

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.
@@ -36,7 +36,7 @@ class FromBtcAbs extends FromBtcBaseSwapHandler_1.FromBtcBaseSwapHandler {
36
36
  getHash(chainIdentifier, address, amount) {
37
37
  const parsedOutputScript = this.bitcoin.toOutputScript(address);
38
38
  const { swapContract } = this.getChain(chainIdentifier);
39
- return swapContract.getHashForOnchain(parsedOutputScript, amount, this.config.confirmations, 0n);
39
+ return swapContract.getHashForOnchain(parsedOutputScript, amount, this.config.confirmations);
40
40
  }
41
41
  /**
42
42
  * Processes past swap
@@ -51,13 +51,18 @@ class FromBtcAbs extends FromBtcBaseSwapHandler_1.FromBtcBaseSwapHandler {
51
51
  if (swap.state === FromBtcSwapAbs_1.FromBtcSwapState.CREATED) {
52
52
  if (!await swapContract.isInitAuthorizationExpired(swap.data, swap))
53
53
  return false;
54
- const isCommited = await swapContract.isCommited(swap.data);
55
- if (isCommited) {
54
+ const commitState = await swapContract.getCommitStatus(signer.getAddress(), swap.data);
55
+ if (commitState.type === base_1.SwapCommitStateType.COMMITED || commitState.type === base_1.SwapCommitStateType.REFUNDABLE) {
56
56
  this.swapLogger.info(swap, "processPastSwap(state=CREATED): swap was commited, but processed from watchdog, address: " + swap.address);
57
57
  await swap.setState(FromBtcSwapAbs_1.FromBtcSwapState.COMMITED);
58
58
  await this.saveSwapData(swap);
59
59
  return false;
60
60
  }
61
+ if (commitState.type === base_1.SwapCommitStateType.PAID) {
62
+ this.swapLogger.info(swap, "processPastSwap(state=CREATED): swap was claimed, but processed from watchdog, address: " + swap.address);
63
+ await this.removeSwapData(swap, FromBtcSwapAbs_1.FromBtcSwapState.CLAIMED);
64
+ return false;
65
+ }
61
66
  this.swapLogger.info(swap, "processPastSwap(state=CREATED): removing past swap due to authorization expiry, address: " + swap.address);
62
67
  await this.removeSwapData(swap, FromBtcSwapAbs_1.FromBtcSwapState.CANCELED);
63
68
  await this.bitcoin.addUnusedAddress(swap.address);
@@ -67,11 +72,16 @@ class FromBtcAbs extends FromBtcBaseSwapHandler_1.FromBtcBaseSwapHandler {
67
72
  if (swap.state === FromBtcSwapAbs_1.FromBtcSwapState.COMMITED) {
68
73
  if (!await swapContract.isExpired(signer.getAddress(), swap.data))
69
74
  return false;
70
- const isCommited = await swapContract.isCommited(swap.data);
71
- if (isCommited) {
75
+ const commitState = await swapContract.getCommitStatus(signer.getAddress(), swap.data);
76
+ if (commitState.type === base_1.SwapCommitStateType.COMMITED || commitState.type === base_1.SwapCommitStateType.REFUNDABLE) {
72
77
  this.swapLogger.info(swap, "processPastSwap(state=COMMITED): swap expired, will refund, address: " + swap.address);
73
78
  return true;
74
79
  }
80
+ if (commitState.type === base_1.SwapCommitStateType.PAID) {
81
+ this.swapLogger.info(swap, "processPastSwap(state=COMMITED): swap was claimed, but processed from watchdog, address: " + swap.address);
82
+ await this.removeSwapData(swap, FromBtcSwapAbs_1.FromBtcSwapState.CLAIMED);
83
+ return false;
84
+ }
75
85
  this.swapLogger.warn(swap, "processPastSwap(state=COMMITED): commited swap expired and not committed anymore (already refunded?), address: " + swap.address);
76
86
  await this.removeSwapData(swap, FromBtcSwapAbs_1.FromBtcSwapState.CANCELED);
77
87
  return false;
@@ -157,11 +167,11 @@ class FromBtcAbs extends FromBtcBaseSwapHandler_1.FromBtcBaseSwapHandler {
157
167
  async getClaimerBounty(req, expiry, signal) {
158
168
  const parsedClaimerBounty = await req.paramReader.getParams({
159
169
  claimerBounty: {
160
- feePerBlock: SchemaVerifier_1.FieldTypeEnum.BigInt,
161
- safetyFactor: SchemaVerifier_1.FieldTypeEnum.BigInt,
162
- startTimestamp: SchemaVerifier_1.FieldTypeEnum.BigInt,
163
- addBlock: SchemaVerifier_1.FieldTypeEnum.BigInt,
164
- addFee: SchemaVerifier_1.FieldTypeEnum.BigInt,
170
+ feePerBlock: SchemaVerifier_1.FieldTypeEnum.BigIntNotNegative,
171
+ safetyFactor: SchemaVerifier_1.FieldTypeEnum.BigIntNotNegative,
172
+ startTimestamp: SchemaVerifier_1.FieldTypeEnum.BigIntNotNegative,
173
+ addBlock: SchemaVerifier_1.FieldTypeEnum.BigIntNotNegative,
174
+ addFee: SchemaVerifier_1.FieldTypeEnum.BigIntNotNegative,
165
175
  },
166
176
  }).catch(e => null);
167
177
  signal.throwIfAborted();
@@ -172,6 +182,12 @@ class FromBtcAbs extends FromBtcBaseSwapHandler_1.FromBtcBaseSwapHandler {
172
182
  };
173
183
  }
174
184
  const tsDelta = expiry - parsedClaimerBounty.claimerBounty.startTimestamp;
185
+ if (tsDelta < 0n) {
186
+ throw {
187
+ code: 20043,
188
+ msg: "Invalid claimerBounty (ts delta < 0)"
189
+ };
190
+ }
175
191
  const blocksDelta = tsDelta / this.config.bitcoinBlocktime * parsedClaimerBounty.claimerBounty.safetyFactor;
176
192
  const totalBlock = blocksDelta + parsedClaimerBounty.claimerBounty.addBlock;
177
193
  return parsedClaimerBounty.claimerBounty.addFee + (totalBlock * parsedClaimerBounty.claimerBounty.feePerBlock);
@@ -179,7 +195,7 @@ class FromBtcAbs extends FromBtcBaseSwapHandler_1.FromBtcBaseSwapHandler {
179
195
  getDummySwapData(chainIdentifier, useToken, address) {
180
196
  const { swapContract, signer } = this.getChain(chainIdentifier);
181
197
  const dummyAmount = BigInt(Math.floor(Math.random() * 0x1000000));
182
- return swapContract.createSwapData(base_1.ChainSwapType.CHAIN, signer.getAddress(), address, useToken, dummyAmount, swapContract.getHashForOnchain((0, crypto_1.randomBytes)(32), dummyAmount, 3, null).toString("hex"), base_1.BigIntBufferUtils.fromBuffer((0, crypto_1.randomBytes)(8)), BigInt(Math.floor(Date.now() / 1000)) + this.config.swapTsCsvDelta, false, true, BigInt(Math.floor(Math.random() * 0x10000)), BigInt(Math.floor(Math.random() * 0x10000)));
198
+ return swapContract.createSwapData(base_1.ChainSwapType.CHAIN, signer.getAddress(), address, useToken, dummyAmount, swapContract.getHashForOnchain((0, crypto_1.randomBytes)(32), dummyAmount, 3).toString("hex"), base_1.BigIntBufferUtils.fromBuffer((0, crypto_1.randomBytes)(8)), BigInt(Math.floor(Date.now() / 1000)) + this.config.swapTsCsvDelta, false, true, BigInt(Math.floor(Math.random() * 0x10000)), BigInt(Math.floor(Math.random() * 0x10000)));
183
199
  }
184
200
  /**
185
201
  * Sets up required listeners for the REST server
@@ -215,11 +231,11 @@ class FromBtcAbs extends FromBtcBaseSwapHandler_1.FromBtcBaseSwapHandler {
215
231
  address: (val) => val != null &&
216
232
  typeof (val) === "string" &&
217
233
  chainInterface.isValidAddress(val, true) ? val : null,
218
- amount: SchemaVerifier_1.FieldTypeEnum.BigInt,
234
+ amount: SchemaVerifier_1.FieldTypeEnum.BigIntPositive,
219
235
  token: (val) => val != null &&
220
236
  typeof (val) === "string" &&
221
237
  this.isTokenSupported(chainIdentifier, val) ? val : null,
222
- sequence: SchemaVerifier_1.FieldTypeEnum.BigInt,
238
+ sequence: SchemaVerifier_1.FieldTypeEnum.BigIntNotNegative,
223
239
  exactOut: SchemaVerifier_1.FieldTypeEnum.BooleanOptional
224
240
  });
225
241
  if (parsedBody == null)
@@ -520,7 +520,7 @@ class FromBtcLnAbs extends FromBtcBaseSwapHandler_1.FromBtcBaseSwapHandler {
520
520
  typeof (val) === "string" &&
521
521
  val.length === 64 &&
522
522
  Utils_1.HEX_REGEX.test(val) ? val : null,
523
- amount: SchemaVerifier_1.FieldTypeEnum.BigInt,
523
+ amount: SchemaVerifier_1.FieldTypeEnum.BigIntPositive,
524
524
  token: (val) => val != null &&
525
525
  typeof (val) === "string" &&
526
526
  this.isTokenSupported(chainIdentifier, val) ? val : null,
@@ -553,7 +553,7 @@ class FromBtcLnAuto extends FromBtcBaseSwapHandler_1.FromBtcBaseSwapHandler {
553
553
  typeof (val) === "string" &&
554
554
  val.length === 64 &&
555
555
  Utils_1.HEX_REGEX.test(val) ? val : null,
556
- amount: SchemaVerifier_1.FieldTypeEnum.BigInt,
556
+ amount: SchemaVerifier_1.FieldTypeEnum.BigIntPositive,
557
557
  token: (val) => val != null &&
558
558
  typeof (val) === "string" &&
559
559
  this.isTokenSupported(chainIdentifier, val) ? val : null,
@@ -562,8 +562,8 @@ class FromBtcLnAuto extends FromBtcBaseSwapHandler_1.FromBtcBaseSwapHandler {
562
562
  gasToken: (val) => val != null &&
563
563
  typeof (val) === "string" &&
564
564
  chainInterface.isValidToken(val) ? val : null,
565
- gasAmount: SchemaVerifier_1.FieldTypeEnum.BigInt,
566
- claimerBounty: SchemaVerifier_1.FieldTypeEnum.BigInt
565
+ gasAmount: SchemaVerifier_1.FieldTypeEnum.BigIntNotNegative,
566
+ claimerBounty: SchemaVerifier_1.FieldTypeEnum.BigIntNotNegative
567
567
  });
568
568
  if (parsedBody == null)
569
569
  throw {
@@ -438,7 +438,7 @@ class ToBtcAbs extends ToBtcBaseSwapHandler_1.ToBtcBaseSwapHandler {
438
438
  * @throws {DefinedRuntimeError} will throw an error if the nonce is invalid
439
439
  */
440
440
  checkNonceValid(nonce) {
441
- if (nonce < 0 || nonce >= (2n ** 64n))
441
+ if (nonce < 1 || nonce >= (2n ** 64n))
442
442
  throw {
443
443
  code: 20021,
444
444
  msg: "Invalid request body (nonce - cannot be parsed)"
@@ -458,6 +458,11 @@ class ToBtcAbs extends ToBtcBaseSwapHandler_1.ToBtcBaseSwapHandler {
458
458
  * @throws {DefinedRuntimeError} will throw an error if the confirmationTarget is out of bounds
459
459
  */
460
460
  checkConfirmationTarget(confirmationTarget) {
461
+ if (!Number.isFinite(confirmationTarget) || !Number.isSafeInteger(confirmationTarget))
462
+ throw {
463
+ code: 20028,
464
+ msg: "Invalid request body (confirmationTarget - not whole number)"
465
+ };
461
466
  if (confirmationTarget > this.config.maxConfTarget)
462
467
  throw {
463
468
  code: 20023,
@@ -476,6 +481,11 @@ class ToBtcAbs extends ToBtcBaseSwapHandler_1.ToBtcBaseSwapHandler {
476
481
  * @throws {DefinedRuntimeError} will throw an error if the confirmations are out of bounds
477
482
  */
478
483
  checkRequiredConfirmations(confirmations) {
484
+ if (!Number.isFinite(confirmations) || !Number.isSafeInteger(confirmations))
485
+ throw {
486
+ code: 20027,
487
+ msg: "Invalid request body (confirmations - not whole number)"
488
+ };
479
489
  if (confirmations > this.config.maxConfirmations)
480
490
  throw {
481
491
  code: 20025,
@@ -569,10 +579,10 @@ class ToBtcAbs extends ToBtcBaseSwapHandler_1.ToBtcBaseSwapHandler {
569
579
  */
570
580
  const parsedBody = await req.paramReader.getParams({
571
581
  address: SchemaVerifier_1.FieldTypeEnum.String,
572
- amount: SchemaVerifier_1.FieldTypeEnum.BigInt,
573
- confirmationTarget: SchemaVerifier_1.FieldTypeEnum.Number,
574
- confirmations: SchemaVerifier_1.FieldTypeEnum.Number,
575
- nonce: SchemaVerifier_1.FieldTypeEnum.BigInt,
582
+ amount: SchemaVerifier_1.FieldTypeEnum.BigIntPositive,
583
+ confirmationTarget: SchemaVerifier_1.FieldTypeEnum.NumberPositive,
584
+ confirmations: SchemaVerifier_1.FieldTypeEnum.NumberPositive,
585
+ nonce: SchemaVerifier_1.FieldTypeEnum.BigIntNotNegative,
576
586
  token: (val) => val != null &&
577
587
  typeof (val) === "string" &&
578
588
  this.isTokenSupported(chainIdentifier, val) ? val : null,
@@ -649,7 +659,8 @@ class ToBtcAbs extends ToBtcBaseSwapHandler_1.ToBtcBaseSwapHandler {
649
659
  data: {
650
660
  amount: amountBD.toString(10),
651
661
  address: signer.getAddress(),
652
- satsPervByte: networkFeeData.satsPerVbyte.toString(10),
662
+ satsPervByte: Math.floor(networkFeeData.satsPerVbyte).toString(10),
663
+ satsPervByteNumber: networkFeeData.satsPerVbyte,
653
664
  networkFee: networkFeeInToken.toString(10),
654
665
  swapFee: swapFeeInToken.toString(10),
655
666
  totalFee: (swapFeeInToken + networkFeeInToken).toString(10),
@@ -68,6 +68,7 @@ export declare class ToBtcLnAbs extends ToBtcBaseSwapHandler<ToBtcLnSwapAbs, ToB
68
68
  };
69
69
  readonly lightning: ILightningWallet;
70
70
  readonly LightningAssertions: LightningAssertions;
71
+ readonly cltvDeltaLowerBound: bigint;
71
72
  constructor(storageDirectory: IIntermediaryStorage<ToBtcLnSwapAbs>, path: string, chainData: MultichainData, lightning: ILightningWallet, swapPricing: ISwapPrice, config: ToBtcLnConfig);
72
73
  /**
73
74
  * Cleans up exactIn authorization that are already past their expiry
@@ -33,9 +33,10 @@ class ToBtcLnAbs extends ToBtcBaseSwapHandler_1.ToBtcBaseSwapHandler {
33
33
  this.config.minLnBaseFee = this.config.minLnBaseFee || 5n;
34
34
  this.config.exactInExpiry = this.config.exactInExpiry || 10 * 1000;
35
35
  this.config.lnSendBitcoinBlockTimeSafetyFactorPPM = this.config.lnSendBitcoinBlockTimeSafetyFactorPPM ?? (this.config.safetyFactor * 1000000n);
36
- if (this.config.lnSendBitcoinBlockTimeSafetyFactorPPM <= 1100000n) {
37
- throw new Error("Lightning network send block safety factor set below 1.1, this is insecure!");
36
+ if (this.config.lnSendBitcoinBlockTimeSafetyFactorPPM <= 1250000n) {
37
+ throw new Error("Lightning network send block safety factor set below 1.25, this is insecure!");
38
38
  }
39
+ this.cltvDeltaLowerBound = (0, Utils_1.getMinSafeBlockWindowSlow)(Number(this.config.lnSendBitcoinBlockTimeSafetyFactorPPM) / 1000000);
39
40
  }
40
41
  /**
41
42
  * Cleans up exactIn authorization that are already past their expiry
@@ -229,6 +230,11 @@ class ToBtcLnAbs extends ToBtcBaseSwapHandler_1.ToBtcBaseSwapHandler {
229
230
  const maxFee = swap.quotedNetworkFee;
230
231
  const maxUsableCLTVdelta = (expiryTimestamp - currentTimestamp - this.config.gracePeriod)
231
232
  / (this.config.bitcoinBlocktime * this.config.lnSendBitcoinBlockTimeSafetyFactorPPM / 1000000n);
233
+ if (maxUsableCLTVdelta < this.cltvDeltaLowerBound)
234
+ throw {
235
+ code: 90008,
236
+ msg: "Calculated CLTV delta is too low for current safety factor!"
237
+ };
232
238
  //Initiate payment
233
239
  this.swapLogger.info(swap, "sendLightningPayment(): paying lightning network invoice," +
234
240
  " cltvDelta: " + maxUsableCLTVdelta.toString(10) +
@@ -476,6 +482,11 @@ class ToBtcLnAbs extends ToBtcBaseSwapHandler_1.ToBtcBaseSwapHandler {
476
482
  async checkAndGetNetworkFee(amountBD, maxFee, expiryTimestamp, currentTimestamp, pr, metadata, abortSignal) {
477
483
  const maxUsableCLTV = (expiryTimestamp - currentTimestamp - this.config.gracePeriod)
478
484
  / (this.config.bitcoinBlocktime * this.config.lnSendBitcoinBlockTimeSafetyFactorPPM / 1000000n);
485
+ if (maxUsableCLTV < this.cltvDeltaLowerBound)
486
+ throw {
487
+ code: 20002,
488
+ msg: "Cannot route the payment (calculated CLTV delta too short - increase timeout)!"
489
+ };
479
490
  const blockHeight = await this.lightning.getBlockheight();
480
491
  abortSignal.throwIfAborted();
481
492
  metadata.times.blockheightFetched = Date.now();
@@ -682,8 +693,8 @@ class ToBtcLnAbs extends ToBtcBaseSwapHandler_1.ToBtcBaseSwapHandler {
682
693
  */
683
694
  const parsedBody = await req.paramReader.getParams({
684
695
  pr: SchemaVerifier_1.FieldTypeEnum.String,
685
- maxFee: SchemaVerifier_1.FieldTypeEnum.BigInt,
686
- expiryTimestamp: SchemaVerifier_1.FieldTypeEnum.BigInt,
696
+ maxFee: SchemaVerifier_1.FieldTypeEnum.BigIntPositive,
697
+ expiryTimestamp: SchemaVerifier_1.FieldTypeEnum.BigIntPositive,
687
698
  token: (val) => val != null &&
688
699
  typeof (val) === "string" &&
689
700
  this.isTokenSupported(chainIdentifier, val) ? val : null,
@@ -691,7 +702,7 @@ class ToBtcLnAbs extends ToBtcBaseSwapHandler_1.ToBtcBaseSwapHandler {
691
702
  typeof (val) === "string" &&
692
703
  chainInterface.isValidAddress(val, true) ? val : null,
693
704
  exactIn: SchemaVerifier_1.FieldTypeEnum.BooleanOptional,
694
- amount: SchemaVerifier_1.FieldTypeEnum.BigIntOptional
705
+ amount: SchemaVerifier_1.FieldTypeEnum.BigIntPositiveOptional
695
706
  });
696
707
  if (parsedBody == null) {
697
708
  throw {
@@ -174,13 +174,21 @@ class SpvVaultSwapHandler extends SwapHandler_1.SwapHandler {
174
174
  const vault = await this.Vaults.getVault(swap.chainIdentifier, swap.vaultOwner, swap.vaultId);
175
175
  const foundWithdrawal = vault.pendingWithdrawals.find(val => val.btcTx.txid === swap.btcTxId);
176
176
  let tx = foundWithdrawal?.btcTx;
177
- if (tx == null)
177
+ if (tx == null) {
178
178
  tx = await this.bitcoinRpc.getTransaction(swap.btcTxId);
179
+ }
180
+ else {
181
+ tx = await (0, BitcoinUtils_1.checkTransactionReplaced)(tx.txid, tx.raw, this.bitcoinRpc);
182
+ }
179
183
  if (tx == null) {
180
184
  await this.removeSwapData(swap, SpvVaultSwap_1.SpvVaultSwapState.FAILED);
185
+ if (foundWithdrawal != null) {
186
+ vault.removeWithdrawal(foundWithdrawal);
187
+ await this.Vaults.saveVault(vault);
188
+ }
181
189
  return;
182
190
  }
183
- else if (tx.confirmations === 0) {
191
+ else if (tx.confirmations == null || tx.confirmations === 0) {
184
192
  await swap.setState(SpvVaultSwap_1.SpvVaultSwapState.SENT);
185
193
  await this.saveSwapData(swap);
186
194
  return;
@@ -315,11 +323,11 @@ class SpvVaultSwapHandler extends SwapHandler_1.SwapHandler {
315
323
  * frontingFeeRate: string Fronting fee (in output token) to assign to the swap
316
324
  */
317
325
  const actualParsedBody = await req.paramReader.getParams({
318
- amount: SchemaVerifier_1.FieldTypeEnum.BigInt,
319
- gasAmount: SchemaVerifier_1.FieldTypeEnum.BigInt,
326
+ amount: SchemaVerifier_1.FieldTypeEnum.BigIntPositive,
327
+ gasAmount: SchemaVerifier_1.FieldTypeEnum.BigIntNotNegative,
320
328
  exactOut: SchemaVerifier_1.FieldTypeEnum.BooleanOptional,
321
- callerFeeRate: SchemaVerifier_1.FieldTypeEnum.BigInt,
322
- frontingFeeRate: SchemaVerifier_1.FieldTypeEnum.BigInt,
329
+ callerFeeRate: SchemaVerifier_1.FieldTypeEnum.BigIntNotNegative,
330
+ frontingFeeRate: SchemaVerifier_1.FieldTypeEnum.BigIntNotNegative,
323
331
  });
324
332
  abortController.signal.throwIfAborted();
325
333
  if (actualParsedBody == null)
@@ -329,7 +337,7 @@ class SpvVaultSwapHandler extends SwapHandler_1.SwapHandler {
329
337
  };
330
338
  const inputAmountAdjustments = req.paramReader.getExistingParamsOrNull({
331
339
  amountUtxos: SchemaVerifier_1.FieldTypeEnum.AnyOptional,
332
- amountFeeRate: SchemaVerifier_1.FieldTypeEnum.NumberOptional
340
+ amountFeeRate: SchemaVerifier_1.FieldTypeEnum.NumberPositiveOptional
333
341
  });
334
342
  if (inputAmountAdjustments == null)
335
343
  throw {
@@ -630,25 +638,26 @@ class SpvVaultSwapHandler extends SwapHandler_1.SwapHandler {
630
638
  msg: "Vault UTXO already spent, please get another quote and try again!"
631
639
  };
632
640
  }
641
+ //Double-check the state to prevent race condition
642
+ if (swap.state !== SpvVaultSwap_1.SpvVaultSwapState.CREATED)
643
+ throw {
644
+ code: 20505,
645
+ msg: "Invalid quote ID, not found or expired!"
646
+ }; //Continues here is only synchronous code until the state change to SIGNED
633
647
  const unlock = swap.lock(120);
634
648
  if (!unlock)
635
649
  throw {
636
650
  code: 20517,
637
651
  msg: "Bitcoin transaction submission already in progress, please retry later!"
638
652
  };
653
+ const txId = signedTx.id;
639
654
  let swapSendingSet = false;
640
655
  let dataSendingSet = false;
641
656
  try {
642
657
  const btcRawTx = Buffer.from(signedTx.toBytes(true, true)).toString("hex");
643
- //Double-check the state to prevent race condition
644
- if (swap.state !== SpvVaultSwap_1.SpvVaultSwapState.CREATED)
645
- throw {
646
- code: 20505,
647
- msg: "Invalid quote ID, not found or expired!"
648
- };
649
658
  //Double check in-flight swap count
650
659
  this.checkTooManyInflightSwaps();
651
- swap.btcTxId = signedTx.id;
660
+ swap.btcTxId = txId;
652
661
  swap.state = SpvVaultSwap_1.SpvVaultSwapState.SIGNED;
653
662
  swap.sending = true;
654
663
  swapSendingSet = true;
@@ -658,7 +667,7 @@ class SpvVaultSwapHandler extends SwapHandler_1.SwapHandler {
658
667
  vault.addWithdrawal(data);
659
668
  dataSendingSet = true;
660
669
  await this.Vaults.saveVault(vault);
661
- this.swapLogger.info(swap, "REST: /postQuote: BTC transaction signed, txId: " + swap.btcTxId);
670
+ this.swapLogger.info(swap, "REST: /postQuote: BTC transaction signed, txId: " + txId);
662
671
  try {
663
672
  await this.bitcoin.sendRawTransaction(btcRawTx);
664
673
  await swap.setState(SpvVaultSwap_1.SpvVaultSwapState.SENT);
@@ -676,21 +685,45 @@ class SpvVaultSwapHandler extends SwapHandler_1.SwapHandler {
676
685
  catch (e) {
677
686
  if (swapSendingSet)
678
687
  swap.sending = false;
679
- if (dataSendingSet) {
688
+ if (dataSendingSet)
680
689
  data.sending = false;
681
- vault.removeWithdrawal(data);
682
- await this.Vaults.saveVault(vault);
683
- }
684
- //Check if the error is only because the state has already changed
685
- if (!(0, Utils_1.isDefinedRuntimeError)(e) || e.code !== 20505) {
686
- //We only make the swap failed if the error happened in CREATED or SIGNED states
687
- if (swap.state === SpvVaultSwap_1.SpvVaultSwapState.CREATED || swap.state === SpvVaultSwap_1.SpvVaultSwapState.SIGNED) {
688
- if ((0, Utils_1.isDefinedRuntimeError)(e) && swap.metadata != null)
689
- swap.metadata.postQuoteError = e;
690
- await this.removeSwapData(swap, SpvVaultSwap_1.SpvVaultSwapState.FAILED);
690
+ //We only make the swap failed if the error happened in CREATED or SIGNED states
691
+ if (swap.state === SpvVaultSwap_1.SpvVaultSwapState.CREATED || swap.state === SpvVaultSwap_1.SpvVaultSwapState.SIGNED) {
692
+ try {
693
+ const fetchedBtcTx = await this.bitcoin.getWalletTransaction(txId);
694
+ if (fetchedBtcTx == null) {
695
+ if (dataSendingSet) {
696
+ vault.removeWithdrawal(data);
697
+ await this.Vaults.saveVault(vault);
698
+ }
699
+ if ((0, Utils_1.isDefinedRuntimeError)(e) && swap.metadata != null)
700
+ swap.metadata.postQuoteError = e;
701
+ await this.removeSwapData(swap, SpvVaultSwap_1.SpvVaultSwapState.FAILED);
702
+ throw e; //This will get caught locally and throw the post quote error
703
+ }
704
+ else {
705
+ //Transaction not-null set state to sent and fall through without throwing
706
+ await swap.setState(SpvVaultSwap_1.SpvVaultSwapState.SENT);
707
+ }
691
708
  }
709
+ catch (getTxErr) {
710
+ if (getTxErr === e)
711
+ throw e;
712
+ //Cannot determine whether the bitcoin transaction was broadcasted or not
713
+ // do nothing here and let the watchdog handle this!
714
+ throw {
715
+ _httpStatus: 200,
716
+ code: 20001,
717
+ msg: "Transaction status unknown",
718
+ data: {
719
+ txId: swap.btcTxId
720
+ }
721
+ };
722
+ }
723
+ }
724
+ else {
725
+ throw e;
692
726
  }
693
- throw e;
694
727
  }
695
728
  finally {
696
729
  unlock();
@@ -211,13 +211,9 @@ class FromBtcLnTrusted extends SwapHandler_1.SwapHandler {
211
211
  }
212
212
  }).catch(e => this.swapLogger.error(invoiceData, "htlcReceived(): Error sending transfer txns", e));
213
213
  if (result == null) {
214
- //Cancel invoice
215
- await invoiceData.setState(FromBtcLnTrustedSwap_1.FromBtcLnTrustedSwapState.REFUNDED);
216
- await this.storageManager.saveData(invoice.id, null, invoiceData);
217
- await this.lightning.cancelHodlInvoice(invoice.id);
218
- this.unsubscribeInvoice(invoice.id);
219
- await this.removeSwapData(invoiceData);
220
- this.swapLogger.info(invoiceData, "htlcReceived(): transaction sending failed, refunding lightning: ", invoiceData.pr);
214
+ this.swapLogger.info(invoiceData, "htlcReceived(): transaction sending failed: ", invoiceData.pr);
215
+ //Let the swap watchdog handle this case
216
+ unlock();
221
217
  throw {
222
218
  code: 20002,
223
219
  msg: "Transaction sending failed"
@@ -235,14 +231,7 @@ class FromBtcLnTrusted extends SwapHandler_1.SwapHandler {
235
231
  if (invoiceData.isLocked())
236
232
  return;
237
233
  const txStatus = await chainInterface.getTxStatus(invoiceData.scRawTx);
238
- if (txStatus === "not_found") {
239
- //Retry
240
- invoiceData.txIds = { init: null };
241
- invoiceData.scRawTx = null;
242
- await invoiceData.setState(FromBtcLnTrustedSwap_1.FromBtcLnTrustedSwapState.RECEIVED);
243
- await this.storageManager.saveData(invoice.id, null, invoiceData);
244
- }
245
- if (txStatus === "reverted") {
234
+ if (txStatus === "reverted" || txStatus === "not_found") {
246
235
  //Cancel invoice
247
236
  await invoiceData.setState(FromBtcLnTrustedSwap_1.FromBtcLnTrustedSwapState.REFUNDED);
248
237
  await this.storageManager.saveData(invoice.id, null, invoiceData);
@@ -118,51 +118,8 @@ class ParamDecoder {
118
118
  const resultSchema = {};
119
119
  for (let fieldName in schema) {
120
120
  const val = await this.getParam(fieldName);
121
- const type = schema[fieldName];
122
- if (typeof (type) === "function") {
123
- const result = type(val);
124
- if (result == null)
125
- return null;
126
- resultSchema[fieldName] = result;
127
- continue;
128
- }
129
- if (val == null && type >= 100) {
130
- resultSchema[fieldName] = null;
131
- continue;
132
- }
133
- if (type === SchemaVerifier_1.FieldTypeEnum.Any || type === SchemaVerifier_1.FieldTypeEnum.AnyOptional) {
134
- resultSchema[fieldName] = val;
135
- }
136
- else if (type === SchemaVerifier_1.FieldTypeEnum.Boolean || type === SchemaVerifier_1.FieldTypeEnum.BooleanOptional) {
137
- if (typeof (val) !== "boolean")
138
- return null;
139
- resultSchema[fieldName] = val;
140
- }
141
- else if (type === SchemaVerifier_1.FieldTypeEnum.Number || type === SchemaVerifier_1.FieldTypeEnum.NumberOptional) {
142
- if (typeof (val) !== "number")
143
- return null;
144
- if (isNaN(val))
145
- return null;
146
- resultSchema[fieldName] = val;
147
- }
148
- else if (type === SchemaVerifier_1.FieldTypeEnum.BigInt || type === SchemaVerifier_1.FieldTypeEnum.BigIntOptional) {
149
- const result = (0, SchemaVerifier_1.parseBigInt)(val);
150
- if (result == null)
151
- return null;
152
- resultSchema[fieldName] = result;
153
- }
154
- else if (type === SchemaVerifier_1.FieldTypeEnum.String || type === SchemaVerifier_1.FieldTypeEnum.StringOptional) {
155
- if (typeof (val) !== "string")
156
- return null;
157
- resultSchema[fieldName] = val;
158
- }
159
- else {
160
- //Probably another request schema
161
- const result = (0, SchemaVerifier_1.verifySchema)(val, type);
162
- if (result == null)
163
- return null;
164
- resultSchema[fieldName] = result;
165
- }
121
+ if (!(0, SchemaVerifier_1.verifySchemaField)(val, schema[fieldName], fieldName, resultSchema))
122
+ return null;
166
123
  }
167
124
  return resultSchema;
168
125
  }
@@ -174,47 +131,8 @@ class ParamDecoder {
174
131
  resultSchema[fieldName] = null;
175
132
  continue;
176
133
  }
177
- const type = schema[fieldName];
178
- if (typeof (type) === "function") {
179
- const result = type(val);
180
- if (result == null)
181
- return null;
182
- resultSchema[fieldName] = result;
183
- continue;
184
- }
185
- if (type === SchemaVerifier_1.FieldTypeEnum.Any || type === SchemaVerifier_1.FieldTypeEnum.AnyOptional) {
186
- resultSchema[fieldName] = val;
187
- }
188
- else if (type === SchemaVerifier_1.FieldTypeEnum.Boolean || type === SchemaVerifier_1.FieldTypeEnum.BooleanOptional) {
189
- if (typeof (val) !== "boolean")
190
- return null;
191
- resultSchema[fieldName] = val;
192
- }
193
- else if (type === SchemaVerifier_1.FieldTypeEnum.Number || type === SchemaVerifier_1.FieldTypeEnum.NumberOptional) {
194
- if (typeof (val) !== "number")
195
- return null;
196
- if (isNaN(val))
197
- return null;
198
- resultSchema[fieldName] = val;
199
- }
200
- else if (type === SchemaVerifier_1.FieldTypeEnum.BigInt || type === SchemaVerifier_1.FieldTypeEnum.BigIntOptional) {
201
- const result = (0, SchemaVerifier_1.parseBigInt)(val);
202
- if (result == null)
203
- return null;
204
- resultSchema[fieldName] = result;
205
- }
206
- else if (type === SchemaVerifier_1.FieldTypeEnum.String || type === SchemaVerifier_1.FieldTypeEnum.StringOptional) {
207
- if (typeof (val) !== "string")
208
- return null;
209
- resultSchema[fieldName] = val;
210
- }
211
- else {
212
- //Probably another request schema
213
- const result = (0, SchemaVerifier_1.verifySchema)(val, type);
214
- if (result == null)
215
- return null;
216
- resultSchema[fieldName] = result;
217
- }
134
+ if (!(0, SchemaVerifier_1.verifySchemaField)(val, schema[fieldName], fieldName, resultSchema))
135
+ return null;
218
136
  }
219
137
  return resultSchema;
220
138
  }
@@ -5,17 +5,26 @@ export declare enum FieldTypeEnum {
5
5
  Number = 2,
6
6
  BigInt = 3,
7
7
  Any = 4,
8
+ BigIntPositive = 5,
9
+ BigIntNotNegative = 6,
10
+ NumberPositive = 7,
11
+ NumberNotNegative = 8,
8
12
  StringOptional = 100,
9
13
  BooleanOptional = 101,
10
14
  NumberOptional = 102,
11
15
  BigIntOptional = 103,
12
- AnyOptional = 104
16
+ AnyOptional = 104,
17
+ BigIntPositiveOptional = 105,
18
+ BigIntNotNegativeOptional = 106,
19
+ NumberPositiveOptional = 107,
20
+ NumberNotNegativeOptional = 108
13
21
  }
14
- export type FieldType<T extends FieldTypeEnum | RequestSchema | ((val: any) => (string | boolean | number | bigint | any))> = T extends FieldTypeEnum.String ? string : T extends FieldTypeEnum.Boolean ? boolean : T extends FieldTypeEnum.Number ? number : T extends FieldTypeEnum.BigInt ? bigint : T extends FieldTypeEnum.Any ? any : T extends FieldTypeEnum.StringOptional ? string : T extends FieldTypeEnum.BooleanOptional ? boolean : T extends FieldTypeEnum.NumberOptional ? number : T extends FieldTypeEnum.BigIntOptional ? bigint : T extends FieldTypeEnum.AnyOptional ? any : T extends RequestSchema ? RequestSchemaResult<T> : T extends ((val: any) => string) ? string : T extends ((val: any) => boolean) ? boolean : T extends ((val: any) => number) ? number : T extends ((val: any) => bigint) ? bigint : T extends ((val: any) => any) ? any : never;
22
+ export type FieldType<T extends FieldTypeEnum | RequestSchema | ((val: any) => (string | boolean | number | bigint | any))> = T extends FieldTypeEnum.String ? string : T extends FieldTypeEnum.Boolean ? boolean : T extends FieldTypeEnum.Number ? number : T extends FieldTypeEnum.NumberPositive ? number : T extends FieldTypeEnum.NumberNotNegative ? number : T extends FieldTypeEnum.BigInt ? bigint : T extends FieldTypeEnum.Any ? any : T extends FieldTypeEnum.BigIntPositive ? bigint : T extends FieldTypeEnum.BigIntNotNegative ? bigint : T extends FieldTypeEnum.StringOptional ? string : T extends FieldTypeEnum.BooleanOptional ? boolean : T extends FieldTypeEnum.NumberOptional ? number : T extends FieldTypeEnum.NumberPositiveOptional ? number : T extends FieldTypeEnum.NumberNotNegativeOptional ? number : T extends FieldTypeEnum.BigIntOptional ? bigint : T extends FieldTypeEnum.AnyOptional ? any : T extends FieldTypeEnum.BigIntPositiveOptional ? bigint : T extends FieldTypeEnum.BigIntNotNegativeOptional ? bigint : T extends RequestSchema ? RequestSchemaResult<T> : T extends ((val: any) => string) ? string : T extends ((val: any) => boolean) ? boolean : T extends ((val: any) => number) ? number : T extends ((val: any) => bigint) ? bigint : T extends ((val: any) => any) ? any : never;
15
23
  export type RequestSchemaResult<T extends RequestSchema> = {
16
24
  [key in keyof T]: FieldType<T[key]>;
17
25
  };
18
26
  export type RequestSchema = {
19
27
  [fieldName: string]: FieldTypeEnum | RequestSchema | ((val: any) => any);
20
28
  };
29
+ export declare function verifySchemaField(val: any, type: FieldTypeEnum | RequestSchema | ((val: any) => any), fieldName: string, resultSchema: any): boolean;
21
30
  export declare function verifySchema<T extends RequestSchema>(req: any, schema: T): RequestSchemaResult<T>;