@atomiqlabs/lp-lib 17.5.3 → 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.
- package/dist/swaps/assertions/FromBtcAmountAssertions.js +2 -2
- package/dist/swaps/escrow/frombtc_abstract/FromBtcAbs.js +45 -19
- package/dist/swaps/escrow/frombtcln_abstract/FromBtcLnAbs.d.ts +2 -2
- package/dist/swaps/escrow/frombtcln_abstract/FromBtcLnAbs.js +35 -25
- package/dist/swaps/escrow/frombtcln_autoinit/FromBtcLnAuto.d.ts +2 -2
- package/dist/swaps/escrow/frombtcln_autoinit/FromBtcLnAuto.js +33 -22
- package/dist/swaps/escrow/tobtc_abstract/ToBtcAbs.d.ts +1 -1
- package/dist/swaps/escrow/tobtc_abstract/ToBtcAbs.js +29 -11
- package/dist/swaps/escrow/tobtc_abstract/ToBtcSwapAbs.d.ts +2 -2
- package/dist/swaps/escrow/tobtc_abstract/ToBtcSwapAbs.js +1 -1
- package/dist/swaps/escrow/tobtcln_abstract/ToBtcLnAbs.d.ts +1 -0
- package/dist/swaps/escrow/tobtcln_abstract/ToBtcLnAbs.js +54 -9
- package/dist/swaps/spv_vault_swap/SpvVaultSwapHandler.js +83 -30
- package/dist/swaps/trusted/frombtc_trusted/FromBtcTrusted.js +2 -2
- package/dist/swaps/trusted/frombtcln_trusted/FromBtcLnTrusted.js +11 -17
- package/dist/utils/Utils.d.ts +33 -0
- package/dist/utils/Utils.js +51 -1
- package/dist/utils/paramcoders/ParamDecoder.js +4 -86
- package/dist/utils/paramcoders/SchemaVerifier.d.ts +11 -2
- package/dist/utils/paramcoders/SchemaVerifier.js +93 -47
- package/package.json +1 -1
- package/src/swaps/assertions/FromBtcAmountAssertions.ts +2 -2
- package/src/swaps/escrow/frombtc_abstract/FromBtcAbs.ts +44 -19
- package/src/swaps/escrow/frombtcln_abstract/FromBtcLnAbs.ts +47 -30
- package/src/swaps/escrow/frombtcln_autoinit/FromBtcLnAuto.ts +45 -27
- package/src/swaps/escrow/tobtc_abstract/ToBtcAbs.ts +28 -13
- package/src/swaps/escrow/tobtc_abstract/ToBtcSwapAbs.ts +4 -4
- package/src/swaps/escrow/tobtcln_abstract/ToBtcLnAbs.ts +63 -13
- package/src/swaps/spv_vault_swap/SpvVaultSwapHandler.ts +79 -33
- package/src/swaps/trusted/frombtc_trusted/FromBtcTrusted.ts +25 -2
- package/src/swaps/trusted/frombtcln_trusted/FromBtcLnTrusted.ts +10 -16
- package/src/utils/Utils.ts +48 -0
- package/src/utils/paramcoders/ParamDecoder.ts +10 -66
- package/src/utils/paramcoders/SchemaVerifier.ts +80 -37
|
@@ -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 <=
|
|
37
|
-
throw new Error("Lightning network send block safety factor set below 1.
|
|
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
|
|
@@ -100,7 +101,8 @@ class ToBtcLnAbs extends ToBtcBaseSwapHandler_1.ToBtcBaseSwapHandler {
|
|
|
100
101
|
}
|
|
101
102
|
]);
|
|
102
103
|
for (let { obj: swap } of queriedData) {
|
|
103
|
-
await this.processPastSwap(swap)
|
|
104
|
+
await this.processPastSwap(swap)
|
|
105
|
+
.catch(e => this.swapLogger.error(swap, "processPastSwap(): Error executing watchdog function: ", e));
|
|
104
106
|
}
|
|
105
107
|
}
|
|
106
108
|
/**
|
|
@@ -198,6 +200,10 @@ class ToBtcLnAbs extends ToBtcBaseSwapHandler_1.ToBtcBaseSwapHandler {
|
|
|
198
200
|
this.swapLogger.info(invoiceData, "subscribeToPayment(): result callback, outcome: " + result.status + " invoice: " + invoiceData.pr);
|
|
199
201
|
this.processPaymentResult(invoiceData, result).catch(e => this.swapLogger.error(invoiceData, "subscribeToPayment(): process payment result", e));
|
|
200
202
|
this.activeSubscriptions.delete(paymentHash);
|
|
203
|
+
}).catch(e => {
|
|
204
|
+
this.swapLogger.error(invoiceData, "subscribeToPayment(): error while waiting for invoice payment: ", e);
|
|
205
|
+
//Delete from active subscription, the watchdog will re-subscribe later
|
|
206
|
+
this.activeSubscriptions.delete(paymentHash);
|
|
201
207
|
});
|
|
202
208
|
this.swapLogger.info(invoiceData, "subscribeToPayment(): subscribe to payment outcome, invoice: " + invoiceData.pr);
|
|
203
209
|
this.activeSubscriptions.add(paymentHash);
|
|
@@ -224,6 +230,11 @@ class ToBtcLnAbs extends ToBtcBaseSwapHandler_1.ToBtcBaseSwapHandler {
|
|
|
224
230
|
const maxFee = swap.quotedNetworkFee;
|
|
225
231
|
const maxUsableCLTVdelta = (expiryTimestamp - currentTimestamp - this.config.gracePeriod)
|
|
226
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
|
+
};
|
|
227
238
|
//Initiate payment
|
|
228
239
|
this.swapLogger.info(swap, "sendLightningPayment(): paying lightning network invoice," +
|
|
229
240
|
" cltvDelta: " + maxUsableCLTVdelta.toString(10) +
|
|
@@ -311,6 +322,9 @@ class ToBtcLnAbs extends ToBtcBaseSwapHandler_1.ToBtcBaseSwapHandler {
|
|
|
311
322
|
else
|
|
312
323
|
throw e;
|
|
313
324
|
}
|
|
325
|
+
const unlock = swap.lock(120);
|
|
326
|
+
if (unlock == null)
|
|
327
|
+
return;
|
|
314
328
|
await swap.setState(ToBtcLnSwapAbs_1.ToBtcLnSwapState.COMMITED);
|
|
315
329
|
await this.saveSwapData(swap);
|
|
316
330
|
try {
|
|
@@ -322,13 +336,29 @@ class ToBtcLnAbs extends ToBtcBaseSwapHandler_1.ToBtcBaseSwapHandler {
|
|
|
322
336
|
if ((0, Utils_1.isDefinedRuntimeError)(e)) {
|
|
323
337
|
if (swap.metadata != null)
|
|
324
338
|
swap.metadata.payError = e;
|
|
325
|
-
await
|
|
326
|
-
|
|
327
|
-
|
|
339
|
+
const payment = await this.lightning.getPayment(swap.lnPaymentHash);
|
|
340
|
+
if (!unlock())
|
|
341
|
+
return;
|
|
342
|
+
if (payment == null || payment.status === "failed") {
|
|
343
|
+
if (swap.state === ToBtcLnSwapAbs_1.ToBtcLnSwapState.COMMITED)
|
|
344
|
+
await swap.setState(ToBtcLnSwapAbs_1.ToBtcLnSwapState.NON_PAYABLE);
|
|
345
|
+
await this.saveSwapData(swap);
|
|
346
|
+
return;
|
|
347
|
+
}
|
|
348
|
+
else if (payment.status === "confirmed") {
|
|
349
|
+
await this.processPaymentResult(swap, payment);
|
|
350
|
+
return;
|
|
351
|
+
}
|
|
352
|
+
else {
|
|
353
|
+
//Fall-through to subscribe
|
|
354
|
+
}
|
|
328
355
|
}
|
|
329
356
|
else
|
|
330
357
|
throw e;
|
|
331
358
|
}
|
|
359
|
+
finally {
|
|
360
|
+
unlock();
|
|
361
|
+
}
|
|
332
362
|
this.subscribeToPayment(swap);
|
|
333
363
|
return;
|
|
334
364
|
}
|
|
@@ -452,6 +482,11 @@ class ToBtcLnAbs extends ToBtcBaseSwapHandler_1.ToBtcBaseSwapHandler {
|
|
|
452
482
|
async checkAndGetNetworkFee(amountBD, maxFee, expiryTimestamp, currentTimestamp, pr, metadata, abortSignal) {
|
|
453
483
|
const maxUsableCLTV = (expiryTimestamp - currentTimestamp - this.config.gracePeriod)
|
|
454
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
|
+
};
|
|
455
490
|
const blockHeight = await this.lightning.getBlockheight();
|
|
456
491
|
abortSignal.throwIfAborted();
|
|
457
492
|
metadata.times.blockheightFetched = Date.now();
|
|
@@ -658,8 +693,8 @@ class ToBtcLnAbs extends ToBtcBaseSwapHandler_1.ToBtcBaseSwapHandler {
|
|
|
658
693
|
*/
|
|
659
694
|
const parsedBody = await req.paramReader.getParams({
|
|
660
695
|
pr: SchemaVerifier_1.FieldTypeEnum.String,
|
|
661
|
-
maxFee: SchemaVerifier_1.FieldTypeEnum.
|
|
662
|
-
expiryTimestamp: SchemaVerifier_1.FieldTypeEnum.
|
|
696
|
+
maxFee: SchemaVerifier_1.FieldTypeEnum.BigIntPositive,
|
|
697
|
+
expiryTimestamp: SchemaVerifier_1.FieldTypeEnum.BigIntPositive,
|
|
663
698
|
token: (val) => val != null &&
|
|
664
699
|
typeof (val) === "string" &&
|
|
665
700
|
this.isTokenSupported(chainIdentifier, val) ? val : null,
|
|
@@ -667,7 +702,7 @@ class ToBtcLnAbs extends ToBtcBaseSwapHandler_1.ToBtcBaseSwapHandler {
|
|
|
667
702
|
typeof (val) === "string" &&
|
|
668
703
|
chainInterface.isValidAddress(val, true) ? val : null,
|
|
669
704
|
exactIn: SchemaVerifier_1.FieldTypeEnum.BooleanOptional,
|
|
670
|
-
amount: SchemaVerifier_1.FieldTypeEnum.
|
|
705
|
+
amount: SchemaVerifier_1.FieldTypeEnum.BigIntPositiveOptional
|
|
671
706
|
});
|
|
672
707
|
if (parsedBody == null) {
|
|
673
708
|
throw {
|
|
@@ -832,6 +867,16 @@ class ToBtcLnAbs extends ToBtcBaseSwapHandler_1.ToBtcBaseSwapHandler {
|
|
|
832
867
|
msg: "Payment expired"
|
|
833
868
|
};
|
|
834
869
|
if (data.state === ToBtcLnSwapAbs_1.ToBtcLnSwapState.NON_PAYABLE) {
|
|
870
|
+
if (data.payInitiated) {
|
|
871
|
+
//Check that the ln payment is indeed non-existent or failed
|
|
872
|
+
const lnPayment = await this.lightning.getPayment(data.lnPaymentHash);
|
|
873
|
+
if (lnPayment != null && lnPayment.status !== "failed")
|
|
874
|
+
throw {
|
|
875
|
+
_httpStatus: 200,
|
|
876
|
+
code: 20008,
|
|
877
|
+
msg: "Payment in-flight"
|
|
878
|
+
};
|
|
879
|
+
}
|
|
835
880
|
const refundSigData = await swapContract.getRefundSignature(signer, data.data, this.config.refundAuthorizationTimeout);
|
|
836
881
|
//Double check the state after promise result
|
|
837
882
|
if (data.state !== ToBtcLnSwapAbs_1.ToBtcLnSwapState.NON_PAYABLE)
|
|
@@ -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;
|
|
@@ -223,7 +231,8 @@ class SpvVaultSwapHandler extends SwapHandler_1.SwapHandler {
|
|
|
223
231
|
}
|
|
224
232
|
]);
|
|
225
233
|
for (let { obj: swap } of swaps) {
|
|
226
|
-
await this.processPastSwap(swap)
|
|
234
|
+
await this.processPastSwap(swap)
|
|
235
|
+
.catch(e => this.swapLogger.error(swap, "processPastSwap(): Error executing watchdog function: ", e));
|
|
227
236
|
}
|
|
228
237
|
}
|
|
229
238
|
getPricePrefetches(chainIdentifier, token, gasToken, abortController) {
|
|
@@ -314,11 +323,11 @@ class SpvVaultSwapHandler extends SwapHandler_1.SwapHandler {
|
|
|
314
323
|
* frontingFeeRate: string Fronting fee (in output token) to assign to the swap
|
|
315
324
|
*/
|
|
316
325
|
const actualParsedBody = await req.paramReader.getParams({
|
|
317
|
-
amount: SchemaVerifier_1.FieldTypeEnum.
|
|
318
|
-
gasAmount: SchemaVerifier_1.FieldTypeEnum.
|
|
326
|
+
amount: SchemaVerifier_1.FieldTypeEnum.BigIntPositive,
|
|
327
|
+
gasAmount: SchemaVerifier_1.FieldTypeEnum.BigIntNotNegative,
|
|
319
328
|
exactOut: SchemaVerifier_1.FieldTypeEnum.BooleanOptional,
|
|
320
|
-
callerFeeRate: SchemaVerifier_1.FieldTypeEnum.
|
|
321
|
-
frontingFeeRate: SchemaVerifier_1.FieldTypeEnum.
|
|
329
|
+
callerFeeRate: SchemaVerifier_1.FieldTypeEnum.BigIntNotNegative,
|
|
330
|
+
frontingFeeRate: SchemaVerifier_1.FieldTypeEnum.BigIntNotNegative,
|
|
322
331
|
});
|
|
323
332
|
abortController.signal.throwIfAborted();
|
|
324
333
|
if (actualParsedBody == null)
|
|
@@ -328,7 +337,7 @@ class SpvVaultSwapHandler extends SwapHandler_1.SwapHandler {
|
|
|
328
337
|
};
|
|
329
338
|
const inputAmountAdjustments = req.paramReader.getExistingParamsOrNull({
|
|
330
339
|
amountUtxos: SchemaVerifier_1.FieldTypeEnum.AnyOptional,
|
|
331
|
-
amountFeeRate: SchemaVerifier_1.FieldTypeEnum.
|
|
340
|
+
amountFeeRate: SchemaVerifier_1.FieldTypeEnum.NumberPositiveOptional
|
|
332
341
|
});
|
|
333
342
|
if (inputAmountAdjustments == null)
|
|
334
343
|
throw {
|
|
@@ -615,14 +624,12 @@ class SpvVaultSwapHandler extends SwapHandler_1.SwapHandler {
|
|
|
615
624
|
};
|
|
616
625
|
const pluginCheckResult = await PluginManager_1.PluginManager.onHandlePreFromBtcExecute(SwapHandler_1.SwapHandlerType.FROM_BTC_SPV, swap);
|
|
617
626
|
if ((0, IPlugin_1.isQuoteThrow)(pluginCheckResult)) {
|
|
618
|
-
|
|
627
|
+
if (swap.state === SpvVaultSwap_1.SpvVaultSwapState.CREATED)
|
|
628
|
+
await this.removeSwapData(swap, SpvVaultSwap_1.SpvVaultSwapState.FAILED);
|
|
629
|
+
throw {
|
|
619
630
|
code: 29999,
|
|
620
631
|
msg: pluginCheckResult.message
|
|
621
632
|
};
|
|
622
|
-
if (swap.metadata != null)
|
|
623
|
-
swap.metadata.postQuoteError = error;
|
|
624
|
-
await this.removeSwapData(swap, SpvVaultSwap_1.SpvVaultSwapState.FAILED);
|
|
625
|
-
throw error;
|
|
626
633
|
}
|
|
627
634
|
await this.Vaults.checkVaultReplacedTransactions(vault, true);
|
|
628
635
|
if (swap.vaultUtxo !== vault.getLatestUtxo()) {
|
|
@@ -631,26 +638,36 @@ class SpvVaultSwapHandler extends SwapHandler_1.SwapHandler {
|
|
|
631
638
|
msg: "Vault UTXO already spent, please get another quote and try again!"
|
|
632
639
|
};
|
|
633
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
|
|
647
|
+
const unlock = swap.lock(120);
|
|
648
|
+
if (!unlock)
|
|
649
|
+
throw {
|
|
650
|
+
code: 20517,
|
|
651
|
+
msg: "Bitcoin transaction submission already in progress, please retry later!"
|
|
652
|
+
};
|
|
653
|
+
const txId = signedTx.id;
|
|
654
|
+
let swapSendingSet = false;
|
|
655
|
+
let dataSendingSet = false;
|
|
634
656
|
try {
|
|
635
657
|
const btcRawTx = Buffer.from(signedTx.toBytes(true, true)).toString("hex");
|
|
636
|
-
//Double-check the state to prevent race condition
|
|
637
|
-
if (swap.state !== SpvVaultSwap_1.SpvVaultSwapState.CREATED) {
|
|
638
|
-
throw {
|
|
639
|
-
code: 20505,
|
|
640
|
-
msg: "Invalid quote ID, not found or expired!"
|
|
641
|
-
};
|
|
642
|
-
}
|
|
643
658
|
//Double check in-flight swap count
|
|
644
659
|
this.checkTooManyInflightSwaps();
|
|
645
|
-
swap.btcTxId =
|
|
660
|
+
swap.btcTxId = txId;
|
|
646
661
|
swap.state = SpvVaultSwap_1.SpvVaultSwapState.SIGNED;
|
|
647
662
|
swap.sending = true;
|
|
663
|
+
swapSendingSet = true;
|
|
648
664
|
await this.saveSwapData(swap);
|
|
649
665
|
data.btcTx.raw = btcRawTx;
|
|
650
666
|
data.sending = true;
|
|
651
667
|
vault.addWithdrawal(data);
|
|
668
|
+
dataSendingSet = true;
|
|
652
669
|
await this.Vaults.saveVault(vault);
|
|
653
|
-
this.swapLogger.info(swap, "REST: /postQuote: BTC transaction signed, txId: " +
|
|
670
|
+
this.swapLogger.info(swap, "REST: /postQuote: BTC transaction signed, txId: " + txId);
|
|
654
671
|
try {
|
|
655
672
|
await this.bitcoin.sendRawTransaction(btcRawTx);
|
|
656
673
|
await swap.setState(SpvVaultSwap_1.SpvVaultSwapState.SENT);
|
|
@@ -666,14 +683,50 @@ class SpvVaultSwapHandler extends SwapHandler_1.SwapHandler {
|
|
|
666
683
|
}
|
|
667
684
|
}
|
|
668
685
|
catch (e) {
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
686
|
+
if (swapSendingSet)
|
|
687
|
+
swap.sending = false;
|
|
688
|
+
if (dataSendingSet)
|
|
689
|
+
data.sending = false;
|
|
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
|
+
}
|
|
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;
|
|
726
|
+
}
|
|
727
|
+
}
|
|
728
|
+
finally {
|
|
729
|
+
unlock();
|
|
677
730
|
}
|
|
678
731
|
await responseStream.writeParamsAndEnd({
|
|
679
732
|
code: 20000,
|
|
@@ -246,7 +246,7 @@ class FromBtcTrusted extends SwapHandler_1.SwapHandler {
|
|
|
246
246
|
if (swap.state !== FromBtcTrustedSwap_1.FromBtcTrustedSwapState.BTC_CONFIRMED)
|
|
247
247
|
return;
|
|
248
248
|
const txns = await chainInterface.txsTransfer(signer.getAddress(), swap.token, swap.adjustedOutput, swap.dstAddress);
|
|
249
|
-
let unlock = swap.lock(30
|
|
249
|
+
let unlock = swap.lock(30);
|
|
250
250
|
if (unlock == null)
|
|
251
251
|
return;
|
|
252
252
|
const pluginCheckResult = await PluginManager_1.PluginManager.onHandlePreFromBtcExecute(SwapHandler_1.SwapHandlerType.FROM_BTC_TRUSTED, swap);
|
|
@@ -341,7 +341,7 @@ class FromBtcTrusted extends SwapHandler_1.SwapHandler {
|
|
|
341
341
|
await this.processPastSwap(swap, txs[0]?.tx, txs[0]?.vout);
|
|
342
342
|
}
|
|
343
343
|
catch (e) {
|
|
344
|
-
this.swapLogger.error(swap, "
|
|
344
|
+
this.swapLogger.error(swap, "processPastSwap(): Error executing watchdog function: ", e);
|
|
345
345
|
}
|
|
346
346
|
}
|
|
347
347
|
}
|
|
@@ -130,8 +130,13 @@ class FromBtcLnTrusted extends SwapHandler_1.SwapHandler {
|
|
|
130
130
|
}
|
|
131
131
|
]);
|
|
132
132
|
for (let { obj: swap } of queriedData) {
|
|
133
|
-
|
|
134
|
-
|
|
133
|
+
try {
|
|
134
|
+
if (await this.processPastSwap(swap))
|
|
135
|
+
cancelInvoices.push(swap);
|
|
136
|
+
}
|
|
137
|
+
catch (e) {
|
|
138
|
+
this.swapLogger.error(swap, "processPastSwap(): Error executing watchdog function: ", e);
|
|
139
|
+
}
|
|
135
140
|
}
|
|
136
141
|
await this.cancelInvoices(cancelInvoices);
|
|
137
142
|
}
|
|
@@ -206,13 +211,9 @@ class FromBtcLnTrusted extends SwapHandler_1.SwapHandler {
|
|
|
206
211
|
}
|
|
207
212
|
}).catch(e => this.swapLogger.error(invoiceData, "htlcReceived(): Error sending transfer txns", e));
|
|
208
213
|
if (result == null) {
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
await this.lightning.cancelHodlInvoice(invoice.id);
|
|
213
|
-
this.unsubscribeInvoice(invoice.id);
|
|
214
|
-
await this.removeSwapData(invoiceData);
|
|
215
|
-
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();
|
|
216
217
|
throw {
|
|
217
218
|
code: 20002,
|
|
218
219
|
msg: "Transaction sending failed"
|
|
@@ -230,14 +231,7 @@ class FromBtcLnTrusted extends SwapHandler_1.SwapHandler {
|
|
|
230
231
|
if (invoiceData.isLocked())
|
|
231
232
|
return;
|
|
232
233
|
const txStatus = await chainInterface.getTxStatus(invoiceData.scRawTx);
|
|
233
|
-
if (txStatus === "not_found") {
|
|
234
|
-
//Retry
|
|
235
|
-
invoiceData.txIds = { init: null };
|
|
236
|
-
invoiceData.scRawTx = null;
|
|
237
|
-
await invoiceData.setState(FromBtcLnTrustedSwap_1.FromBtcLnTrustedSwapState.RECEIVED);
|
|
238
|
-
await this.storageManager.saveData(invoice.id, null, invoiceData);
|
|
239
|
-
}
|
|
240
|
-
if (txStatus === "reverted") {
|
|
234
|
+
if (txStatus === "reverted" || txStatus === "not_found") {
|
|
241
235
|
//Cancel invoice
|
|
242
236
|
await invoiceData.setState(FromBtcLnTrustedSwap_1.FromBtcLnTrustedSwapState.REFUNDED);
|
|
243
237
|
await this.storageManager.saveData(invoice.id, null, invoiceData);
|
package/dist/utils/Utils.d.ts
CHANGED
|
@@ -30,3 +30,36 @@ export declare function bigIntSorter(a: bigint, b: bigint): -1 | 0 | 1;
|
|
|
30
30
|
*/
|
|
31
31
|
export declare function getAbortController(responseStream: ServerParamEncoder): AbortController;
|
|
32
32
|
export declare function parsePsbt(btcTx: Transaction): BtcTx;
|
|
33
|
+
/**
|
|
34
|
+
* Returns the minimum Bitcoin block window (in blocks) for which a "fast blocks" safety
|
|
35
|
+
* factor is genuinely usable: the probability that N consecutive blocks are produced
|
|
36
|
+
* with an average interval below (blocktime / safetyFactor) is less than `probability`.
|
|
37
|
+
*
|
|
38
|
+
* Model: block arrivals are a Poisson process (i.i.d. exponential intervals), so the
|
|
39
|
+
* production time of N blocks is Erlang/Gamma(shape=N). A Chernoff (Cramer) bound on
|
|
40
|
+
* the lower tail gives the per-block large-deviation rate I(S) = ln(S) + 1/S - 1, i.e.
|
|
41
|
+
* P(N blocks faster than blocktime/S on average) <= exp(-N * I(S))
|
|
42
|
+
* which this function inverts for N. Conservative: the exact Erlang CDF already
|
|
43
|
+
* satisfies the bound ~25% earlier. Use for the fast-block tail (e.g. incoming LN
|
|
44
|
+
* HTLC expiry racing an escrow claim window).
|
|
45
|
+
*
|
|
46
|
+
* @param {number|bigint} safetyFactor Assumed max block-speed multiplier (must be > 1)
|
|
47
|
+
* @param {number} [probability=0.0001] Tolerated failure probability (default 0.01%)
|
|
48
|
+
* @returns {number} Minimum safe block window in blocks
|
|
49
|
+
*/
|
|
50
|
+
export declare function getMinSafeBlockWindowFast(safetyFactor: number | bigint, probability?: number): bigint;
|
|
51
|
+
/**
|
|
52
|
+
* Slow-tail mirror of getMinSafeBlockWindowFast(): returns the minimum block window
|
|
53
|
+
* (in blocks) for which a "slow blocks" safety factor is genuinely usable: the
|
|
54
|
+
* probability that N consecutive blocks take LONGER than N * blocktime * safetyFactor
|
|
55
|
+
* is less than `probability`. Chernoff bound on the upper Erlang tail gives the rate
|
|
56
|
+
* I(S) = S - 1 - ln(S). Use when converting a wall-clock deadline into a block count
|
|
57
|
+
* (e.g. payout/claim timeouts that must expire before some real-world time even if
|
|
58
|
+
* blocks come slowly).
|
|
59
|
+
*
|
|
60
|
+
* @param {number|bigint} safetyFactor Assumed max block-slowness multiplier (must be > 1)
|
|
61
|
+
* @param {number} [probability=0.0001] Tolerated failure probability (default 0.01%)
|
|
62
|
+
* @returns {number} Minimum safe block window in blocks
|
|
63
|
+
*/
|
|
64
|
+
export declare function getMinSafeBlockWindowSlow(safetyFactor: bigint | number, probability?: number): bigint;
|
|
65
|
+
export declare function bigIntMax(a: bigint, b: bigint): bigint;
|
package/dist/utils/Utils.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.parsePsbt = exports.getAbortController = exports.bigIntSorter = exports.deserializeBN = exports.serializeBN = exports.HEX_REGEX = exports.expressHandlerWrapper = exports.isDefinedRuntimeError = exports.getLogger = void 0;
|
|
3
|
+
exports.bigIntMax = exports.getMinSafeBlockWindowSlow = exports.getMinSafeBlockWindowFast = exports.parsePsbt = exports.getAbortController = exports.bigIntSorter = exports.deserializeBN = exports.serializeBN = exports.HEX_REGEX = exports.expressHandlerWrapper = exports.isDefinedRuntimeError = exports.getLogger = void 0;
|
|
4
4
|
const crypto_1 = require("crypto");
|
|
5
5
|
const btc_signer_1 = require("@scure/btc-signer");
|
|
6
6
|
function getLogger(prefix) {
|
|
@@ -128,3 +128,53 @@ function parsePsbt(btcTx) {
|
|
|
128
128
|
};
|
|
129
129
|
}
|
|
130
130
|
exports.parsePsbt = parsePsbt;
|
|
131
|
+
/**
|
|
132
|
+
* Returns the minimum Bitcoin block window (in blocks) for which a "fast blocks" safety
|
|
133
|
+
* factor is genuinely usable: the probability that N consecutive blocks are produced
|
|
134
|
+
* with an average interval below (blocktime / safetyFactor) is less than `probability`.
|
|
135
|
+
*
|
|
136
|
+
* Model: block arrivals are a Poisson process (i.i.d. exponential intervals), so the
|
|
137
|
+
* production time of N blocks is Erlang/Gamma(shape=N). A Chernoff (Cramer) bound on
|
|
138
|
+
* the lower tail gives the per-block large-deviation rate I(S) = ln(S) + 1/S - 1, i.e.
|
|
139
|
+
* P(N blocks faster than blocktime/S on average) <= exp(-N * I(S))
|
|
140
|
+
* which this function inverts for N. Conservative: the exact Erlang CDF already
|
|
141
|
+
* satisfies the bound ~25% earlier. Use for the fast-block tail (e.g. incoming LN
|
|
142
|
+
* HTLC expiry racing an escrow claim window).
|
|
143
|
+
*
|
|
144
|
+
* @param {number|bigint} safetyFactor Assumed max block-speed multiplier (must be > 1)
|
|
145
|
+
* @param {number} [probability=0.0001] Tolerated failure probability (default 0.01%)
|
|
146
|
+
* @returns {number} Minimum safe block window in blocks
|
|
147
|
+
*/
|
|
148
|
+
function getMinSafeBlockWindowFast(safetyFactor, probability = 0.0001) {
|
|
149
|
+
const S = Number(safetyFactor);
|
|
150
|
+
const rate = Math.log(S) + 1 / S - 1;
|
|
151
|
+
if (!(rate > 0))
|
|
152
|
+
throw new RangeError("safetyFactor must be > 1");
|
|
153
|
+
return BigInt(Math.ceil(Math.log(1 / probability) / rate));
|
|
154
|
+
}
|
|
155
|
+
exports.getMinSafeBlockWindowFast = getMinSafeBlockWindowFast;
|
|
156
|
+
/**
|
|
157
|
+
* Slow-tail mirror of getMinSafeBlockWindowFast(): returns the minimum block window
|
|
158
|
+
* (in blocks) for which a "slow blocks" safety factor is genuinely usable: the
|
|
159
|
+
* probability that N consecutive blocks take LONGER than N * blocktime * safetyFactor
|
|
160
|
+
* is less than `probability`. Chernoff bound on the upper Erlang tail gives the rate
|
|
161
|
+
* I(S) = S - 1 - ln(S). Use when converting a wall-clock deadline into a block count
|
|
162
|
+
* (e.g. payout/claim timeouts that must expire before some real-world time even if
|
|
163
|
+
* blocks come slowly).
|
|
164
|
+
*
|
|
165
|
+
* @param {number|bigint} safetyFactor Assumed max block-slowness multiplier (must be > 1)
|
|
166
|
+
* @param {number} [probability=0.0001] Tolerated failure probability (default 0.01%)
|
|
167
|
+
* @returns {number} Minimum safe block window in blocks
|
|
168
|
+
*/
|
|
169
|
+
function getMinSafeBlockWindowSlow(safetyFactor, probability = 0.0001) {
|
|
170
|
+
const S = Number(safetyFactor);
|
|
171
|
+
const rate = S - 1 - Math.log(S);
|
|
172
|
+
if (!(rate > 0))
|
|
173
|
+
throw new RangeError("safetyFactor must be > 1");
|
|
174
|
+
return BigInt(Math.ceil(Math.log(1 / probability) / rate));
|
|
175
|
+
}
|
|
176
|
+
exports.getMinSafeBlockWindowSlow = getMinSafeBlockWindowSlow;
|
|
177
|
+
function bigIntMax(a, b) {
|
|
178
|
+
return a > b ? a : b;
|
|
179
|
+
}
|
|
180
|
+
exports.bigIntMax = bigIntMax;
|
|
@@ -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
|
-
|
|
122
|
-
|
|
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
|
-
|
|
178
|
-
|
|
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>;
|