@atomiqlabs/sdk 8.6.6 → 8.6.7
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/SmartChainAssets.d.ts +181 -181
- package/dist/SmartChainAssets.js +181 -181
- package/dist/bitcoin/coinselect2/accumulative.d.ts +6 -6
- package/dist/bitcoin/coinselect2/accumulative.js +52 -51
- package/dist/bitcoin/coinselect2/blackjack.d.ts +6 -6
- package/dist/bitcoin/coinselect2/blackjack.js +38 -37
- package/dist/bitcoin/coinselect2/index.d.ts +19 -17
- package/dist/bitcoin/coinselect2/index.js +69 -69
- package/dist/bitcoin/coinselect2/utils.d.ts +77 -75
- package/dist/bitcoin/coinselect2/utils.js +123 -123
- package/dist/bitcoin/wallet/BitcoinWallet.d.ts +130 -128
- package/dist/bitcoin/wallet/BitcoinWallet.js +322 -322
- package/dist/bitcoin/wallet/IBitcoinWallet.d.ts +78 -78
- package/dist/bitcoin/wallet/IBitcoinWallet.js +21 -20
- package/dist/bitcoin/wallet/SingleAddressBitcoinWallet.d.ts +101 -99
- package/dist/bitcoin/wallet/SingleAddressBitcoinWallet.js +176 -176
- package/dist/enums/FeeType.d.ts +15 -15
- package/dist/enums/FeeType.js +19 -19
- package/dist/enums/SwapAmountType.d.ts +15 -15
- package/dist/enums/SwapAmountType.js +19 -19
- package/dist/enums/SwapDirection.d.ts +15 -15
- package/dist/enums/SwapDirection.js +19 -19
- package/dist/enums/SwapSide.d.ts +15 -15
- package/dist/enums/SwapSide.js +19 -19
- package/dist/enums/SwapType.d.ts +75 -75
- package/dist/enums/SwapType.js +79 -79
- package/dist/errors/IntermediaryError.d.ts +13 -13
- package/dist/errors/IntermediaryError.js +27 -27
- package/dist/errors/RequestError.d.ts +32 -32
- package/dist/errors/RequestError.js +54 -54
- package/dist/errors/UserError.d.ts +8 -8
- package/dist/errors/UserError.js +16 -16
- package/dist/events/UnifiedSwapEventListener.d.ts +23 -23
- package/dist/events/UnifiedSwapEventListener.js +130 -130
- package/dist/http/HttpUtils.d.ts +27 -27
- package/dist/http/HttpUtils.js +91 -90
- package/dist/http/paramcoders/IParamReader.d.ts +8 -8
- package/dist/http/paramcoders/IParamReader.js +2 -2
- package/dist/http/paramcoders/ParamDecoder.d.ts +44 -42
- package/dist/http/paramcoders/ParamDecoder.js +137 -137
- package/dist/http/paramcoders/ParamEncoder.d.ts +20 -18
- package/dist/http/paramcoders/ParamEncoder.js +36 -36
- package/dist/http/paramcoders/SchemaVerifier.d.ts +26 -26
- package/dist/http/paramcoders/SchemaVerifier.js +145 -145
- package/dist/http/paramcoders/client/ResponseParamDecoder.d.ts +11 -11
- package/dist/http/paramcoders/client/ResponseParamDecoder.js +57 -57
- package/dist/http/paramcoders/client/StreamParamEncoder.d.ts +13 -11
- package/dist/http/paramcoders/client/StreamParamEncoder.js +26 -26
- package/dist/http/paramcoders/client/StreamingFetchPromise.d.ts +16 -16
- package/dist/http/paramcoders/client/StreamingFetchPromise.js +174 -173
- package/dist/index.d.ts +85 -85
- package/dist/index.js +158 -158
- package/dist/intermediaries/Intermediary.d.ts +157 -157
- package/dist/intermediaries/Intermediary.js +142 -142
- package/dist/intermediaries/IntermediaryDiscovery.d.ts +199 -198
- package/dist/intermediaries/IntermediaryDiscovery.js +406 -406
- package/dist/intermediaries/apis/IntermediaryAPI.d.ts +439 -437
- package/dist/intermediaries/apis/IntermediaryAPI.js +603 -603
- package/dist/intermediaries/apis/TrustedIntermediaryAPI.d.ts +155 -155
- package/dist/intermediaries/apis/TrustedIntermediaryAPI.js +137 -137
- package/dist/lnurl/LNURL.d.ts +102 -102
- package/dist/lnurl/LNURL.js +321 -321
- package/dist/prices/RedundantSwapPrice.d.ts +110 -110
- package/dist/prices/RedundantSwapPrice.js +222 -222
- package/dist/prices/SingleSwapPrice.d.ts +34 -34
- package/dist/prices/SingleSwapPrice.js +44 -44
- package/dist/prices/SwapPriceWithChain.d.ts +107 -107
- package/dist/prices/SwapPriceWithChain.js +128 -128
- package/dist/prices/abstract/ICachedSwapPrice.d.ts +28 -28
- package/dist/prices/abstract/ICachedSwapPrice.js +62 -62
- package/dist/prices/abstract/IPriceProvider.d.ts +81 -81
- package/dist/prices/abstract/IPriceProvider.js +74 -74
- package/dist/prices/abstract/ISwapPrice.d.ts +168 -168
- package/dist/prices/abstract/ISwapPrice.js +279 -279
- package/dist/prices/providers/BinancePriceProvider.d.ts +23 -23
- package/dist/prices/providers/BinancePriceProvider.js +30 -30
- package/dist/prices/providers/CoinGeckoPriceProvider.d.ts +23 -23
- package/dist/prices/providers/CoinGeckoPriceProvider.js +29 -29
- package/dist/prices/providers/CoinPaprikaPriceProvider.d.ts +25 -25
- package/dist/prices/providers/CoinPaprikaPriceProvider.js +29 -29
- package/dist/prices/providers/CustomPriceProvider.d.ts +24 -24
- package/dist/prices/providers/CustomPriceProvider.js +35 -35
- package/dist/prices/providers/KrakenPriceProvider.d.ts +38 -38
- package/dist/prices/providers/KrakenPriceProvider.js +45 -45
- package/dist/prices/providers/OKXPriceProvider.d.ts +34 -34
- package/dist/prices/providers/OKXPriceProvider.js +29 -29
- package/dist/prices/providers/abstract/ExchangePriceProvider.d.ts +17 -17
- package/dist/prices/providers/abstract/ExchangePriceProvider.js +21 -21
- package/dist/prices/providers/abstract/HttpPriceProvider.d.ts +7 -7
- package/dist/prices/providers/abstract/HttpPriceProvider.js +12 -12
- package/dist/storage/IUnifiedStorage.d.ts +85 -85
- package/dist/storage/IUnifiedStorage.js +2 -2
- package/dist/storage/UnifiedSwapStorage.d.ts +114 -114
- package/dist/storage/UnifiedSwapStorage.js +116 -116
- package/dist/storage-browser/IndexedDBUnifiedStorage.d.ts +63 -63
- package/dist/storage-browser/IndexedDBUnifiedStorage.js +298 -298
- package/dist/storage-browser/LocalStorageManager.d.ts +49 -49
- package/dist/storage-browser/LocalStorageManager.js +93 -93
- package/dist/swapper/Swapper.d.ts +687 -686
- package/dist/swapper/Swapper.js +1603 -1603
- package/dist/swapper/SwapperFactory.d.ts +135 -135
- package/dist/swapper/SwapperFactory.js +162 -162
- package/dist/swapper/SwapperUtils.d.ts +200 -200
- package/dist/swapper/SwapperUtils.js +467 -467
- package/dist/swapper/SwapperWithChain.d.ts +404 -404
- package/dist/swapper/SwapperWithChain.js +469 -469
- package/dist/swapper/SwapperWithSigner.d.ts +322 -322
- package/dist/swapper/SwapperWithSigner.js +318 -318
- package/dist/swaps/IAddressSwap.d.ts +22 -22
- package/dist/swaps/IAddressSwap.js +14 -13
- package/dist/swaps/IBTCWalletSwap.d.ts +73 -73
- package/dist/swaps/IBTCWalletSwap.js +18 -17
- package/dist/swaps/IClaimableSwap.d.ts +49 -49
- package/dist/swaps/IClaimableSwap.js +15 -14
- package/dist/swaps/IClaimableSwapWrapper.d.ts +15 -15
- package/dist/swaps/IClaimableSwapWrapper.js +2 -2
- package/dist/swaps/IRefundableSwap.d.ts +43 -43
- package/dist/swaps/IRefundableSwap.js +14 -13
- package/dist/swaps/ISwap.d.ts +387 -386
- package/dist/swaps/ISwap.js +346 -346
- package/dist/swaps/ISwapWithGasDrop.d.ts +21 -21
- package/dist/swaps/ISwapWithGasDrop.js +12 -11
- package/dist/swaps/ISwapWrapper.d.ts +285 -283
- package/dist/swaps/ISwapWrapper.js +353 -353
- package/dist/swaps/escrow_swaps/IEscrowSelfInitSwap.d.ts +98 -98
- package/dist/swaps/escrow_swaps/IEscrowSelfInitSwap.js +126 -126
- package/dist/swaps/escrow_swaps/IEscrowSwap.d.ts +135 -133
- package/dist/swaps/escrow_swaps/IEscrowSwap.js +169 -169
- package/dist/swaps/escrow_swaps/IEscrowSwapWrapper.d.ts +115 -114
- package/dist/swaps/escrow_swaps/IEscrowSwapWrapper.js +134 -134
- package/dist/swaps/escrow_swaps/frombtc/IFromBTCLNWrapper.d.ts +101 -98
- package/dist/swaps/escrow_swaps/frombtc/IFromBTCLNWrapper.js +130 -130
- package/dist/swaps/escrow_swaps/frombtc/IFromBTCSelfInitSwap.d.ts +162 -162
- package/dist/swaps/escrow_swaps/frombtc/IFromBTCSelfInitSwap.js +190 -190
- package/dist/swaps/escrow_swaps/frombtc/IFromBTCWrapper.d.ts +58 -58
- package/dist/swaps/escrow_swaps/frombtc/IFromBTCWrapper.js +78 -78
- package/dist/swaps/escrow_swaps/frombtc/ln/FromBTCLNSwap.d.ts +531 -529
- package/dist/swaps/escrow_swaps/frombtc/ln/FromBTCLNSwap.js +1285 -1285
- package/dist/swaps/escrow_swaps/frombtc/ln/FromBTCLNWrapper.d.ts +184 -181
- package/dist/swaps/escrow_swaps/frombtc/ln/FromBTCLNWrapper.js +418 -418
- package/dist/swaps/escrow_swaps/frombtc/ln_auto/FromBTCLNAutoSwap.d.ts +583 -581
- package/dist/swaps/escrow_swaps/frombtc/ln_auto/FromBTCLNAutoSwap.js +1371 -1371
- package/dist/swaps/escrow_swaps/frombtc/ln_auto/FromBTCLNAutoWrapper.d.ts +228 -225
- package/dist/swaps/escrow_swaps/frombtc/ln_auto/FromBTCLNAutoWrapper.js +506 -506
- package/dist/swaps/escrow_swaps/frombtc/onchain/FromBTCSwap.d.ts +458 -458
- package/dist/swaps/escrow_swaps/frombtc/onchain/FromBTCSwap.js +1126 -1126
- package/dist/swaps/escrow_swaps/frombtc/onchain/FromBTCWrapper.d.ts +191 -190
- package/dist/swaps/escrow_swaps/frombtc/onchain/FromBTCWrapper.js +378 -378
- package/dist/swaps/escrow_swaps/tobtc/IToBTCSwap.d.ts +403 -403
- package/dist/swaps/escrow_swaps/tobtc/IToBTCSwap.js +924 -924
- package/dist/swaps/escrow_swaps/tobtc/IToBTCWrapper.d.ts +62 -62
- package/dist/swaps/escrow_swaps/tobtc/IToBTCWrapper.js +112 -112
- package/dist/swaps/escrow_swaps/tobtc/ln/ToBTCLNSwap.d.ts +127 -125
- package/dist/swaps/escrow_swaps/tobtc/ln/ToBTCLNSwap.js +256 -256
- package/dist/swaps/escrow_swaps/tobtc/ln/ToBTCLNWrapper.d.ts +242 -241
- package/dist/swaps/escrow_swaps/tobtc/ln/ToBTCLNWrapper.js +520 -520
- package/dist/swaps/escrow_swaps/tobtc/onchain/ToBTCSwap.d.ts +73 -73
- package/dist/swaps/escrow_swaps/tobtc/onchain/ToBTCSwap.js +155 -155
- package/dist/swaps/escrow_swaps/tobtc/onchain/ToBTCWrapper.d.ts +128 -127
- package/dist/swaps/escrow_swaps/tobtc/onchain/ToBTCWrapper.js +278 -278
- package/dist/swaps/spv_swaps/SpvFromBTCSwap.d.ts +630 -630
- package/dist/swaps/spv_swaps/SpvFromBTCSwap.js +1443 -1443
- package/dist/swaps/spv_swaps/SpvFromBTCWrapper.d.ts +214 -213
- package/dist/swaps/spv_swaps/SpvFromBTCWrapper.js +756 -756
- package/dist/swaps/trusted/ln/LnForGasSwap.d.ts +261 -261
- package/dist/swaps/trusted/ln/LnForGasSwap.js +511 -511
- package/dist/swaps/trusted/ln/LnForGasWrapper.d.ts +40 -40
- package/dist/swaps/trusted/ln/LnForGasWrapper.js +82 -82
- package/dist/swaps/trusted/onchain/OnchainForGasSwap.d.ts +342 -342
- package/dist/swaps/trusted/onchain/OnchainForGasSwap.js +715 -715
- package/dist/swaps/trusted/onchain/OnchainForGasWrapper.d.ts +69 -68
- package/dist/swaps/trusted/onchain/OnchainForGasWrapper.js +92 -92
- package/dist/types/AmountData.d.ts +10 -10
- package/dist/types/AmountData.js +2 -2
- package/dist/types/CustomPriceFunction.d.ts +11 -11
- package/dist/types/CustomPriceFunction.js +2 -2
- package/dist/types/PriceInfoType.d.ts +28 -28
- package/dist/types/PriceInfoType.js +57 -56
- package/dist/types/SwapExecutionAction.d.ts +88 -88
- package/dist/types/SwapExecutionAction.js +2 -2
- package/dist/types/SwapStateInfo.d.ts +5 -5
- package/dist/types/SwapStateInfo.js +2 -2
- package/dist/types/SwapWithSigner.d.ts +17 -17
- package/dist/types/SwapWithSigner.js +43 -42
- package/dist/types/Token.d.ts +99 -99
- package/dist/types/Token.js +76 -76
- package/dist/types/TokenAmount.d.ts +69 -69
- package/dist/types/TokenAmount.js +60 -59
- package/dist/types/fees/Fee.d.ts +50 -50
- package/dist/types/fees/Fee.js +2 -2
- package/dist/types/fees/FeeBreakdown.d.ts +11 -11
- package/dist/types/fees/FeeBreakdown.js +2 -2
- package/dist/types/fees/PercentagePPM.d.ts +17 -17
- package/dist/types/fees/PercentagePPM.js +18 -17
- package/dist/types/lnurl/LNURLPay.d.ts +61 -61
- package/dist/types/lnurl/LNURLPay.js +31 -30
- package/dist/types/lnurl/LNURLWithdraw.d.ts +48 -48
- package/dist/types/lnurl/LNURLWithdraw.js +27 -26
- package/dist/types/wallets/LightningInvoiceCreateService.d.ts +24 -24
- package/dist/types/wallets/LightningInvoiceCreateService.js +15 -14
- package/dist/types/wallets/MinimalBitcoinWalletInterface.d.ts +23 -23
- package/dist/types/wallets/MinimalBitcoinWalletInterface.js +2 -2
- package/dist/types/wallets/MinimalLightningNetworkWalletInterface.d.ts +9 -9
- package/dist/types/wallets/MinimalLightningNetworkWalletInterface.js +2 -2
- package/dist/utils/AutomaticClockDriftCorrection.d.ts +1 -1
- package/dist/utils/AutomaticClockDriftCorrection.js +70 -69
- package/dist/utils/BitcoinUtils.d.ts +14 -12
- package/dist/utils/BitcoinUtils.js +102 -101
- package/dist/utils/BitcoinWalletUtils.d.ts +7 -7
- package/dist/utils/BitcoinWalletUtils.js +14 -13
- package/dist/utils/Logger.d.ts +7 -7
- package/dist/utils/Logger.js +12 -11
- package/dist/utils/RetryUtils.d.ts +22 -22
- package/dist/utils/RetryUtils.js +67 -66
- package/dist/utils/SwapUtils.d.ts +88 -88
- package/dist/utils/SwapUtils.js +72 -72
- package/dist/utils/TimeoutUtils.d.ts +17 -17
- package/dist/utils/TimeoutUtils.js +55 -54
- package/dist/utils/TokenUtils.d.ts +19 -19
- package/dist/utils/TokenUtils.js +37 -36
- package/dist/utils/TypeUtils.d.ts +7 -7
- package/dist/utils/TypeUtils.js +2 -2
- package/dist/utils/Utils.d.ts +58 -56
- package/dist/utils/Utils.js +194 -193
- package/package.json +1 -1
|
@@ -1,756 +1,756 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.SpvFromBTCWrapper = void 0;
|
|
4
|
-
const ISwapWrapper_1 = require("../ISwapWrapper");
|
|
5
|
-
const base_1 = require("@atomiqlabs/base");
|
|
6
|
-
const SpvFromBTCSwap_1 = require("./SpvFromBTCSwap");
|
|
7
|
-
const utils_1 = require("@scure/btc-signer/utils");
|
|
8
|
-
const SwapType_1 = require("../../enums/SwapType");
|
|
9
|
-
const Utils_1 = require("../../utils/Utils");
|
|
10
|
-
const BitcoinUtils_1 = require("../../utils/BitcoinUtils");
|
|
11
|
-
const IntermediaryAPI_1 = require("../../intermediaries/apis/IntermediaryAPI");
|
|
12
|
-
const RequestError_1 = require("../../errors/RequestError");
|
|
13
|
-
const IntermediaryError_1 = require("../../errors/IntermediaryError");
|
|
14
|
-
const btc_signer_1 = require("@scure/btc-signer");
|
|
15
|
-
const RetryUtils_1 = require("../../utils/RetryUtils");
|
|
16
|
-
const UserError_1 = require("../../errors/UserError");
|
|
17
|
-
/**
|
|
18
|
-
* New spv vault (UTXO-controlled vault) based swaps for Bitcoin -> Smart chain swaps not requiring
|
|
19
|
-
* any initiation on the destination chain, and with the added possibility for the user to receive
|
|
20
|
-
* a native token on the destination chain as part of the swap (a "gas drop" feature).
|
|
21
|
-
*
|
|
22
|
-
* @category Swaps/Bitcoin → Smart chain
|
|
23
|
-
*/
|
|
24
|
-
class SpvFromBTCWrapper extends ISwapWrapper_1.ISwapWrapper {
|
|
25
|
-
/**
|
|
26
|
-
* @param chainIdentifier
|
|
27
|
-
* @param unifiedStorage Storage interface for the current environment
|
|
28
|
-
* @param unifiedChainEvents On-chain event listener
|
|
29
|
-
* @param chain
|
|
30
|
-
* @param contract Underlying contract handling the swaps
|
|
31
|
-
* @param prices Pricing to use
|
|
32
|
-
* @param tokens
|
|
33
|
-
* @param spvWithdrawalDataDeserializer Deserializer for SpvVaultWithdrawalData
|
|
34
|
-
* @param btcRelay
|
|
35
|
-
* @param synchronizer Btc relay synchronizer
|
|
36
|
-
* @param btcRpc Bitcoin RPC which also supports getting transactions by txoHash
|
|
37
|
-
* @param options
|
|
38
|
-
* @param events Instance to use for emitting events
|
|
39
|
-
*/
|
|
40
|
-
constructor(chainIdentifier, unifiedStorage, unifiedChainEvents, chain, contract, prices, tokens, spvWithdrawalDataDeserializer, btcRelay, synchronizer, btcRpc, options, events) {
|
|
41
|
-
super(chainIdentifier, unifiedStorage, unifiedChainEvents, chain, prices, tokens, {
|
|
42
|
-
...options,
|
|
43
|
-
bitcoinNetwork: options?.bitcoinNetwork ?? utils_1.TEST_NETWORK,
|
|
44
|
-
maxConfirmations: options?.maxConfirmations ?? 6,
|
|
45
|
-
bitcoinBlocktime: options?.bitcoinBlocktime ?? 10 * 60,
|
|
46
|
-
maxTransactionsDelta: options?.maxTransactionsDelta ?? 3,
|
|
47
|
-
maxRawAmountAdjustmentDifferencePPM: options?.maxRawAmountAdjustmentDifferencePPM ?? 100,
|
|
48
|
-
maxBtcFeeOffset: options?.maxBtcFeeOffset ?? 10,
|
|
49
|
-
maxBtcFeeMultiplier: options?.maxBtcFeeMultiplier ?? 1.5
|
|
50
|
-
}, events);
|
|
51
|
-
this.TYPE = SwapType_1.SwapType.SPV_VAULT_FROM_BTC;
|
|
52
|
-
/**
|
|
53
|
-
* @internal
|
|
54
|
-
*/
|
|
55
|
-
this._claimableSwapStates = [SpvFromBTCSwap_1.SpvFromBTCSwapState.BTC_TX_CONFIRMED];
|
|
56
|
-
/**
|
|
57
|
-
* @internal
|
|
58
|
-
*/
|
|
59
|
-
this._swapDeserializer = SpvFromBTCSwap_1.SpvFromBTCSwap;
|
|
60
|
-
/**
|
|
61
|
-
* @internal
|
|
62
|
-
*/
|
|
63
|
-
this.tickSwapState = [
|
|
64
|
-
SpvFromBTCSwap_1.SpvFromBTCSwapState.CREATED,
|
|
65
|
-
SpvFromBTCSwap_1.SpvFromBTCSwapState.QUOTE_SOFT_EXPIRED,
|
|
66
|
-
SpvFromBTCSwap_1.SpvFromBTCSwapState.SIGNED,
|
|
67
|
-
SpvFromBTCSwap_1.SpvFromBTCSwapState.POSTED,
|
|
68
|
-
SpvFromBTCSwap_1.SpvFromBTCSwapState.BROADCASTED
|
|
69
|
-
];
|
|
70
|
-
/**
|
|
71
|
-
* @internal
|
|
72
|
-
*/
|
|
73
|
-
this._pendingSwapStates = [
|
|
74
|
-
SpvFromBTCSwap_1.SpvFromBTCSwapState.CREATED,
|
|
75
|
-
SpvFromBTCSwap_1.SpvFromBTCSwapState.SIGNED,
|
|
76
|
-
SpvFromBTCSwap_1.SpvFromBTCSwapState.POSTED,
|
|
77
|
-
SpvFromBTCSwap_1.SpvFromBTCSwapState.QUOTE_SOFT_EXPIRED,
|
|
78
|
-
SpvFromBTCSwap_1.SpvFromBTCSwapState.BROADCASTED,
|
|
79
|
-
SpvFromBTCSwap_1.SpvFromBTCSwapState.DECLINED,
|
|
80
|
-
SpvFromBTCSwap_1.SpvFromBTCSwapState.BTC_TX_CONFIRMED
|
|
81
|
-
];
|
|
82
|
-
this._spvWithdrawalDataDeserializer = spvWithdrawalDataDeserializer;
|
|
83
|
-
this._contract = contract;
|
|
84
|
-
this.btcRelay = btcRelay;
|
|
85
|
-
this._synchronizer = synchronizer;
|
|
86
|
-
this._btcRpc = btcRpc;
|
|
87
|
-
}
|
|
88
|
-
async processEventFront(event, swap) {
|
|
89
|
-
if (swap._state === SpvFromBTCSwap_1.SpvFromBTCSwapState.SIGNED || swap._state === SpvFromBTCSwap_1.SpvFromBTCSwapState.POSTED ||
|
|
90
|
-
swap._state === SpvFromBTCSwap_1.SpvFromBTCSwapState.BROADCASTED || swap._state === SpvFromBTCSwap_1.SpvFromBTCSwapState.DECLINED ||
|
|
91
|
-
swap._state === SpvFromBTCSwap_1.SpvFromBTCSwapState.QUOTE_SOFT_EXPIRED || swap._state === SpvFromBTCSwap_1.SpvFromBTCSwapState.BTC_TX_CONFIRMED) {
|
|
92
|
-
swap._state = SpvFromBTCSwap_1.SpvFromBTCSwapState.FRONTED;
|
|
93
|
-
await swap._setBitcoinTxId(event.btcTxId).catch(e => {
|
|
94
|
-
this.logger.warn("processEventFront(): Failed to set bitcoin txId: ", e);
|
|
95
|
-
});
|
|
96
|
-
return true;
|
|
97
|
-
}
|
|
98
|
-
return false;
|
|
99
|
-
}
|
|
100
|
-
async processEventClaim(event, swap) {
|
|
101
|
-
if (swap._state === SpvFromBTCSwap_1.SpvFromBTCSwapState.SIGNED || swap._state === SpvFromBTCSwap_1.SpvFromBTCSwapState.POSTED ||
|
|
102
|
-
swap._state === SpvFromBTCSwap_1.SpvFromBTCSwapState.BROADCASTED || swap._state === SpvFromBTCSwap_1.SpvFromBTCSwapState.DECLINED ||
|
|
103
|
-
swap._state === SpvFromBTCSwap_1.SpvFromBTCSwapState.QUOTE_SOFT_EXPIRED || swap._state === SpvFromBTCSwap_1.SpvFromBTCSwapState.FRONTED ||
|
|
104
|
-
swap._state === SpvFromBTCSwap_1.SpvFromBTCSwapState.BTC_TX_CONFIRMED) {
|
|
105
|
-
swap._state = SpvFromBTCSwap_1.SpvFromBTCSwapState.CLAIMED;
|
|
106
|
-
await swap._setBitcoinTxId(event.btcTxId).catch(e => {
|
|
107
|
-
this.logger.warn("processEventClaim(): Failed to set bitcoin txId: ", e);
|
|
108
|
-
});
|
|
109
|
-
return true;
|
|
110
|
-
}
|
|
111
|
-
return false;
|
|
112
|
-
}
|
|
113
|
-
processEventClose(event, swap) {
|
|
114
|
-
if (swap._state === SpvFromBTCSwap_1.SpvFromBTCSwapState.SIGNED || swap._state === SpvFromBTCSwap_1.SpvFromBTCSwapState.POSTED ||
|
|
115
|
-
swap._state === SpvFromBTCSwap_1.SpvFromBTCSwapState.BROADCASTED || swap._state === SpvFromBTCSwap_1.SpvFromBTCSwapState.DECLINED ||
|
|
116
|
-
swap._state === SpvFromBTCSwap_1.SpvFromBTCSwapState.QUOTE_SOFT_EXPIRED || swap._state === SpvFromBTCSwap_1.SpvFromBTCSwapState.BTC_TX_CONFIRMED) {
|
|
117
|
-
swap._state = SpvFromBTCSwap_1.SpvFromBTCSwapState.CLOSED;
|
|
118
|
-
return Promise.resolve(true);
|
|
119
|
-
}
|
|
120
|
-
return Promise.resolve(false);
|
|
121
|
-
}
|
|
122
|
-
/**
|
|
123
|
-
* @inheritDoc
|
|
124
|
-
* @internal
|
|
125
|
-
*/
|
|
126
|
-
async processEvent(event, swap) {
|
|
127
|
-
if (swap == null)
|
|
128
|
-
return;
|
|
129
|
-
let swapChanged = false;
|
|
130
|
-
if (event instanceof base_1.SpvVaultFrontEvent) {
|
|
131
|
-
swapChanged = await this.processEventFront(event, swap);
|
|
132
|
-
if (event.meta?.txId != null && swap._frontTxId !== event.meta.txId) {
|
|
133
|
-
swap._frontTxId = event.meta.txId;
|
|
134
|
-
swapChanged ||= true;
|
|
135
|
-
}
|
|
136
|
-
}
|
|
137
|
-
if (event instanceof base_1.SpvVaultClaimEvent) {
|
|
138
|
-
swapChanged = await this.processEventClaim(event, swap);
|
|
139
|
-
if (event.meta?.txId != null && swap._claimTxId !== event.meta.txId) {
|
|
140
|
-
swap._claimTxId = event.meta.txId;
|
|
141
|
-
swapChanged ||= true;
|
|
142
|
-
}
|
|
143
|
-
}
|
|
144
|
-
if (event instanceof base_1.SpvVaultCloseEvent) {
|
|
145
|
-
swapChanged = await this.processEventClose(event, swap);
|
|
146
|
-
}
|
|
147
|
-
this.logger.info("processEvents(): " + event.constructor.name + " processed for " + swap.getId() + " swap: ", swap);
|
|
148
|
-
if (swapChanged) {
|
|
149
|
-
await swap._saveAndEmit();
|
|
150
|
-
}
|
|
151
|
-
}
|
|
152
|
-
/**
|
|
153
|
-
* Pre-fetches latest finalized block height of the smart chain
|
|
154
|
-
*
|
|
155
|
-
* @param abortController
|
|
156
|
-
* @private
|
|
157
|
-
*/
|
|
158
|
-
async preFetchFinalizedBlockHeight(abortController) {
|
|
159
|
-
try {
|
|
160
|
-
const block = await this._chain.getFinalizedBlock();
|
|
161
|
-
return block.height;
|
|
162
|
-
}
|
|
163
|
-
catch (e) {
|
|
164
|
-
abortController.abort(e);
|
|
165
|
-
}
|
|
166
|
-
}
|
|
167
|
-
/**
|
|
168
|
-
* Pre-fetches caller (watchtower) bounty data for the swap. Doesn't throw, instead returns null and aborts the
|
|
169
|
-
* provided abortController
|
|
170
|
-
*
|
|
171
|
-
* @param amountData
|
|
172
|
-
* @param options Options as passed to the swap creation function
|
|
173
|
-
* @param pricePrefetch
|
|
174
|
-
* @param nativeTokenPricePrefetch
|
|
175
|
-
* @param abortController
|
|
176
|
-
* @private
|
|
177
|
-
*/
|
|
178
|
-
async preFetchCallerFeeShare(amountData, options, pricePrefetch, nativeTokenPricePrefetch, abortController) {
|
|
179
|
-
if (options.unsafeZeroWatchtowerFee)
|
|
180
|
-
return 0n;
|
|
181
|
-
if (amountData.amount === 0n)
|
|
182
|
-
return 0n;
|
|
183
|
-
try {
|
|
184
|
-
const [feePerBlock, btcRelayData, currentBtcBlock, claimFeeRate, nativeTokenPrice] = await Promise.all([
|
|
185
|
-
this.btcRelay.getFeePerBlock(),
|
|
186
|
-
this.btcRelay.getTipData(),
|
|
187
|
-
this._btcRpc.getTipHeight(),
|
|
188
|
-
this._contract.getClaimFee(this._chain.randomAddress()),
|
|
189
|
-
nativeTokenPricePrefetch ?? (amountData.token === this._chain.getNativeCurrencyAddress() ?
|
|
190
|
-
pricePrefetch :
|
|
191
|
-
this._prices.preFetchPrice(this.chainIdentifier, this._chain.getNativeCurrencyAddress(), abortController.signal))
|
|
192
|
-
]);
|
|
193
|
-
if (btcRelayData == null)
|
|
194
|
-
throw new Error("Btc relay doesn't seem to be initialized!");
|
|
195
|
-
const currentBtcRelayBlock = btcRelayData.blockheight;
|
|
196
|
-
const blockDelta = Math.max(currentBtcBlock - currentBtcRelayBlock + this._options.maxConfirmations, 0);
|
|
197
|
-
const totalFeeInNativeToken = ((BigInt(blockDelta) * feePerBlock) +
|
|
198
|
-
(claimFeeRate * BigInt(this._options.maxTransactionsDelta))) * BigInt(Math.floor(options.feeSafetyFactor * 1000000)) / 1000000n;
|
|
199
|
-
let payoutAmount;
|
|
200
|
-
if (amountData.exactIn) {
|
|
201
|
-
//Convert input amount in BTC to
|
|
202
|
-
const amountInNativeToken = await this._prices.getFromBtcSwapAmount(this.chainIdentifier, amountData.amount, this._chain.getNativeCurrencyAddress(), abortController.signal, nativeTokenPrice);
|
|
203
|
-
payoutAmount = amountInNativeToken - totalFeeInNativeToken;
|
|
204
|
-
}
|
|
205
|
-
else {
|
|
206
|
-
if (amountData.token === this._chain.getNativeCurrencyAddress()) {
|
|
207
|
-
//Both amounts in same currency
|
|
208
|
-
payoutAmount = amountData.amount;
|
|
209
|
-
}
|
|
210
|
-
else {
|
|
211
|
-
//Need to convert both to native currency
|
|
212
|
-
const btcAmount = await this._prices.getToBtcSwapAmount(this.chainIdentifier, amountData.amount, amountData.token, abortController.signal, await pricePrefetch);
|
|
213
|
-
payoutAmount = await this._prices.getFromBtcSwapAmount(this.chainIdentifier, btcAmount, this._chain.getNativeCurrencyAddress(), abortController.signal, nativeTokenPrice);
|
|
214
|
-
}
|
|
215
|
-
}
|
|
216
|
-
this.logger.debug("preFetchCallerFeeShare(): Caller fee in native token: " + totalFeeInNativeToken.toString(10) + " total payout in native token: " + payoutAmount.toString(10));
|
|
217
|
-
const callerFeeShare = ((totalFeeInNativeToken * 100000n) + payoutAmount - 1n) / payoutAmount; //Make sure to round up here
|
|
218
|
-
if (callerFeeShare < 0n)
|
|
219
|
-
return 0n;
|
|
220
|
-
if (callerFeeShare >= 2n ** 20n)
|
|
221
|
-
return 2n ** 20n - 1n;
|
|
222
|
-
return callerFeeShare;
|
|
223
|
-
}
|
|
224
|
-
catch (e) {
|
|
225
|
-
abortController.abort(e);
|
|
226
|
-
}
|
|
227
|
-
}
|
|
228
|
-
/**
|
|
229
|
-
* Verifies response returned from intermediary
|
|
230
|
-
*
|
|
231
|
-
* @param resp Response as returned by the intermediary
|
|
232
|
-
* @param amountData
|
|
233
|
-
* @param lp Intermediary
|
|
234
|
-
* @param options Options as passed to the swap creation function
|
|
235
|
-
* @param callerFeeShare
|
|
236
|
-
* @param bitcoinFeeRatePromise Maximum accepted fee rate from the LPs
|
|
237
|
-
* @param abortSignal
|
|
238
|
-
* @private
|
|
239
|
-
* @throws {IntermediaryError} in case the response is invalid
|
|
240
|
-
*/
|
|
241
|
-
async verifyReturnedData(resp, amountData, lp, options, callerFeeShare, bitcoinFeeRatePromise, abortSignal) {
|
|
242
|
-
const btcFeeRate = await (0, Utils_1.throwIfUndefined)(bitcoinFeeRatePromise, "Bitcoin fee rate promise failed!");
|
|
243
|
-
abortSignal.throwIfAborted();
|
|
244
|
-
if (btcFeeRate != null && resp.btcFeeRate > btcFeeRate)
|
|
245
|
-
throw new IntermediaryError_1.IntermediaryError(`Required bitcoin fee rate returned from the LP is too high! Maximum accepted: ${btcFeeRate} sats/vB, required by LP: ${resp.btcFeeRate} sats/vB`);
|
|
246
|
-
//Vault related
|
|
247
|
-
let vaultScript;
|
|
248
|
-
let vaultAddressType;
|
|
249
|
-
let btcAddressScript;
|
|
250
|
-
//Ensure valid btc addresses returned
|
|
251
|
-
try {
|
|
252
|
-
vaultScript = (0, BitcoinUtils_1.toOutputScript)(this._options.bitcoinNetwork, resp.vaultBtcAddress);
|
|
253
|
-
vaultAddressType = (0, BitcoinUtils_1.toCoinselectAddressType)(vaultScript);
|
|
254
|
-
btcAddressScript = (0, BitcoinUtils_1.toOutputScript)(this._options.bitcoinNetwork, resp.btcAddress);
|
|
255
|
-
}
|
|
256
|
-
catch (e) {
|
|
257
|
-
throw new IntermediaryError_1.IntermediaryError("Invalid btc address data returned", e);
|
|
258
|
-
}
|
|
259
|
-
const decodedUtxo = resp.btcUtxo.split(":");
|
|
260
|
-
if (resp.address !== lp.getAddress(this.chainIdentifier) || //Ensure the LP is indeed the vault owner
|
|
261
|
-
resp.vaultId < 0n || //Ensure vaultId is not negative
|
|
262
|
-
vaultScript == null || //Make sure vault script is parsable and of known type
|
|
263
|
-
btcAddressScript == null || //Make sure btc address script is parsable and of known type
|
|
264
|
-
vaultAddressType === "p2pkh" || vaultAddressType === "p2sh-p2wpkh" || //Constrain the vault script type to witness types
|
|
265
|
-
decodedUtxo.length !== 2 || decodedUtxo[0].length !== 64 || isNaN(parseInt(decodedUtxo[1])) || //Check valid UTXO
|
|
266
|
-
resp.btcFeeRate < 1 || resp.btcFeeRate > 10000 //Sanity check on the returned BTC fee rate
|
|
267
|
-
)
|
|
268
|
-
throw new IntermediaryError_1.IntermediaryError("Invalid vault data returned!");
|
|
269
|
-
//Amounts sanity
|
|
270
|
-
if (resp.btcAmountSwap + resp.btcAmountGas !== resp.btcAmount)
|
|
271
|
-
throw new Error("Btc amount mismatch");
|
|
272
|
-
if (resp.swapFeeBtc + resp.gasSwapFeeBtc !== resp.totalFeeBtc)
|
|
273
|
-
throw new Error("Btc fee mismatch");
|
|
274
|
-
//TODO: For now ensure fees are at 0
|
|
275
|
-
if (resp.callerFeeShare !== callerFeeShare ||
|
|
276
|
-
resp.frontingFeeShare !== 0n ||
|
|
277
|
-
resp.executionFeeShare !== 0n)
|
|
278
|
-
throw new IntermediaryError_1.IntermediaryError("Invalid caller/fronting/execution fee returned");
|
|
279
|
-
//Check expiry
|
|
280
|
-
const timeNowSeconds = Math.floor(Date.now() / 1000);
|
|
281
|
-
if (resp.expiry < timeNowSeconds)
|
|
282
|
-
throw new IntermediaryError_1.IntermediaryError(`Quote already expired, expiry: ${resp.expiry}, systemTime: ${timeNowSeconds}, clockAdjusted: ${Date._now != null}`);
|
|
283
|
-
let utxo = resp.btcUtxo.toLowerCase();
|
|
284
|
-
const [txId, voutStr] = utxo.split(":");
|
|
285
|
-
const abortController = (0, Utils_1.extendAbortController)(abortSignal);
|
|
286
|
-
let [vault, { vaultUtxoValue, btcTx }] = await Promise.all([
|
|
287
|
-
(async () => {
|
|
288
|
-
//Fetch vault data
|
|
289
|
-
let vault;
|
|
290
|
-
try {
|
|
291
|
-
vault = await this._contract.getVaultData(resp.address, resp.vaultId);
|
|
292
|
-
}
|
|
293
|
-
catch (e) {
|
|
294
|
-
this.logger.error("Error getting spv vault (owner: " + resp.address + " vaultId: " + resp.vaultId.toString(10) + "): ", e);
|
|
295
|
-
throw new IntermediaryError_1.IntermediaryError("Spv swap vault not found", e);
|
|
296
|
-
}
|
|
297
|
-
abortController.signal.throwIfAborted();
|
|
298
|
-
//Make sure vault is opened
|
|
299
|
-
if (vault == null || !vault.isOpened())
|
|
300
|
-
throw new IntermediaryError_1.IntermediaryError("Returned spv swap vault is not opened!");
|
|
301
|
-
//Make sure the vault doesn't require insane amount of confirmations
|
|
302
|
-
if (vault.getConfirmations() > this._options.maxConfirmations)
|
|
303
|
-
throw new IntermediaryError_1.IntermediaryError("SPV swap vault needs too many confirmations: " + vault.getConfirmations());
|
|
304
|
-
const tokenData = vault.getTokenData();
|
|
305
|
-
//Amounts - make sure the amounts match
|
|
306
|
-
if (amountData.exactIn) {
|
|
307
|
-
if (resp.btcAmount !== amountData.amount)
|
|
308
|
-
throw new IntermediaryError_1.IntermediaryError("Invalid amount returned");
|
|
309
|
-
}
|
|
310
|
-
else {
|
|
311
|
-
//Check the difference between amount adjusted due to scaling to raw amount
|
|
312
|
-
const adjustedAmount = amountData.amount / tokenData[0].multiplier * tokenData[0].multiplier;
|
|
313
|
-
const adjustmentPPM = (amountData.amount - adjustedAmount) * 1000000n / amountData.amount;
|
|
314
|
-
if (adjustmentPPM > this._options.maxRawAmountAdjustmentDifferencePPM)
|
|
315
|
-
throw new IntermediaryError_1.IntermediaryError("Invalid amount0 multiplier used, rawAmount diff too high");
|
|
316
|
-
if (resp.total !== adjustedAmount)
|
|
317
|
-
throw new IntermediaryError_1.IntermediaryError("Invalid total returned");
|
|
318
|
-
}
|
|
319
|
-
if (options.gasAmount === 0n) {
|
|
320
|
-
if (resp.totalGas !== 0n)
|
|
321
|
-
throw new IntermediaryError_1.IntermediaryError("Invalid gas total returned");
|
|
322
|
-
}
|
|
323
|
-
else {
|
|
324
|
-
//Check the difference between amount adjusted due to scaling to raw amount
|
|
325
|
-
const adjustedGasAmount = options.gasAmount / tokenData[0].multiplier * tokenData[0].multiplier;
|
|
326
|
-
const adjustmentPPM = (options.gasAmount - adjustedGasAmount) * 1000000n / options.gasAmount;
|
|
327
|
-
if (adjustmentPPM > this._options.maxRawAmountAdjustmentDifferencePPM)
|
|
328
|
-
throw new IntermediaryError_1.IntermediaryError("Invalid amount1 multiplier used, rawAmount diff too high");
|
|
329
|
-
if (resp.totalGas !== adjustedGasAmount)
|
|
330
|
-
throw new IntermediaryError_1.IntermediaryError("Invalid gas total returned");
|
|
331
|
-
}
|
|
332
|
-
return vault;
|
|
333
|
-
})(),
|
|
334
|
-
(async () => {
|
|
335
|
-
//Require the vault UTXO to have at least 1 confirmation
|
|
336
|
-
let btcTx = await this._btcRpc.getTransaction(txId);
|
|
337
|
-
if (btcTx == null)
|
|
338
|
-
throw new IntermediaryError_1.IntermediaryError("Invalid UTXO, doesn't exist (txId)");
|
|
339
|
-
abortController.signal.throwIfAborted();
|
|
340
|
-
if (btcTx.confirmations == null || btcTx.confirmations < 1)
|
|
341
|
-
throw new IntermediaryError_1.IntermediaryError("SPV vault UTXO not confirmed");
|
|
342
|
-
const vout = parseInt(voutStr);
|
|
343
|
-
if (btcTx.outs[vout] == null)
|
|
344
|
-
throw new IntermediaryError_1.IntermediaryError("Invalid UTXO, doesn't exist");
|
|
345
|
-
const vaultUtxoValue = btcTx.outs[vout].value;
|
|
346
|
-
return { btcTx, vaultUtxoValue };
|
|
347
|
-
})(),
|
|
348
|
-
(async () => {
|
|
349
|
-
//Require vault UTXO is unspent
|
|
350
|
-
if (await this._btcRpc.isSpent(utxo))
|
|
351
|
-
throw new IntermediaryError_1.IntermediaryError("Returned spv vault UTXO is already spent", null, true);
|
|
352
|
-
abortController.signal.throwIfAborted();
|
|
353
|
-
})()
|
|
354
|
-
]).catch(e => {
|
|
355
|
-
abortController.abort(e);
|
|
356
|
-
throw e;
|
|
357
|
-
});
|
|
358
|
-
this.logger.debug("verifyReturnedData(): Vault UTXO: " + vault.getUtxo() + " current utxo: " + utxo);
|
|
359
|
-
//Trace returned utxo back to what's saved on-chain
|
|
360
|
-
let pendingWithdrawals = [];
|
|
361
|
-
while (vault.getUtxo() !== utxo) {
|
|
362
|
-
const [txId, voutStr] = utxo.split(":");
|
|
363
|
-
//Such that 1st tx isn't fetched twice
|
|
364
|
-
if (btcTx.txid !== txId) {
|
|
365
|
-
const _btcTx = await this._btcRpc.getTransaction(txId);
|
|
366
|
-
if (_btcTx == null)
|
|
367
|
-
throw new IntermediaryError_1.IntermediaryError("Invalid ancestor transaction (not found)");
|
|
368
|
-
btcTx = _btcTx;
|
|
369
|
-
}
|
|
370
|
-
const withdrawalData = await this._contract.getWithdrawalData(btcTx);
|
|
371
|
-
abortSignal.throwIfAborted();
|
|
372
|
-
pendingWithdrawals.unshift(withdrawalData);
|
|
373
|
-
utxo = pendingWithdrawals[0].getSpentVaultUtxo();
|
|
374
|
-
this.logger.debug("verifyReturnedData(): Vault UTXO: " + vault.getUtxo() + " current utxo: " + utxo);
|
|
375
|
-
if (pendingWithdrawals.length >= this._options.maxTransactionsDelta)
|
|
376
|
-
throw new IntermediaryError_1.IntermediaryError("BTC <> SC state difference too deep, maximum: " + this._options.maxTransactionsDelta);
|
|
377
|
-
}
|
|
378
|
-
//Verify that the vault has enough balance after processing all pending withdrawals
|
|
379
|
-
let vaultBalances;
|
|
380
|
-
try {
|
|
381
|
-
vaultBalances = vault.calculateStateAfter(pendingWithdrawals);
|
|
382
|
-
}
|
|
383
|
-
catch (e) {
|
|
384
|
-
this.logger.error("Error calculating spv vault balance (owner: " + resp.address + " vaultId: " + resp.vaultId.toString(10) + "): ", e);
|
|
385
|
-
throw new IntermediaryError_1.IntermediaryError("Spv swap vault balance prediction failed", e);
|
|
386
|
-
}
|
|
387
|
-
if (vaultBalances.balances[0].scaledAmount < resp.total)
|
|
388
|
-
throw new IntermediaryError_1.IntermediaryError("SPV swap vault, insufficient balance, required: " + resp.total.toString(10) +
|
|
389
|
-
" has: " + vaultBalances.balances[0].scaledAmount.toString(10));
|
|
390
|
-
if (vaultBalances.balances[1].scaledAmount < resp.totalGas)
|
|
391
|
-
throw new IntermediaryError_1.IntermediaryError("SPV swap vault, insufficient balance, required: " + resp.totalGas.toString(10) +
|
|
392
|
-
" has: " + vaultBalances.balances[1].scaledAmount.toString(10));
|
|
393
|
-
//Also verify that all the withdrawal txns are valid, this is an extra sanity check
|
|
394
|
-
try {
|
|
395
|
-
for (let withdrawal of pendingWithdrawals) {
|
|
396
|
-
await this._contract.checkWithdrawalTx(withdrawal);
|
|
397
|
-
}
|
|
398
|
-
}
|
|
399
|
-
catch (e) {
|
|
400
|
-
this.logger.error("Error calculating spv vault balance (owner: " + resp.address + " vaultId: " + resp.vaultId.toString(10) + "): ", e);
|
|
401
|
-
throw new IntermediaryError_1.IntermediaryError("Spv swap vault balance prediction failed", e);
|
|
402
|
-
}
|
|
403
|
-
abortSignal.throwIfAborted();
|
|
404
|
-
return {
|
|
405
|
-
vault,
|
|
406
|
-
vaultUtxoValue
|
|
407
|
-
};
|
|
408
|
-
}
|
|
409
|
-
/**
|
|
410
|
-
* Returns a newly created Bitcoin -> Smart chain swap using the SPV vault (UTXO-controlled vault) swap protocol,
|
|
411
|
-
* with the passed amount. Also allows specifying additional "gas drop" native token that the receipient receives
|
|
412
|
-
* on the destination chain in the `options` argument.
|
|
413
|
-
*
|
|
414
|
-
* @param recipient Recipient address on the destination smart chain
|
|
415
|
-
* @param amountData Amount, token and exact input/output data for to swap
|
|
416
|
-
* @param lps An array of intermediaries (LPs) to get the quotes from
|
|
417
|
-
* @param options Optional additional quote options
|
|
418
|
-
* @param additionalParams Optional additional parameters sent to the LP when creating the swap
|
|
419
|
-
* @param abortSignal Abort signal
|
|
420
|
-
*/
|
|
421
|
-
create(recipient, amountData, lps, options, additionalParams, abortSignal) {
|
|
422
|
-
const _options = {
|
|
423
|
-
gasAmount: this.parseGasAmount(options?.gasAmount),
|
|
424
|
-
unsafeZeroWatchtowerFee: options?.unsafeZeroWatchtowerFee ?? false,
|
|
425
|
-
feeSafetyFactor: options?.feeSafetyFactor ?? 1.25,
|
|
426
|
-
maxAllowedBitcoinFeeRate: options?.maxAllowedBitcoinFeeRate ?? options?.maxAllowedNetworkFeeRate ?? Infinity
|
|
427
|
-
};
|
|
428
|
-
if (amountData.token === this._chain.getNativeCurrencyAddress() && _options.gasAmount !== 0n)
|
|
429
|
-
throw new UserError_1.UserError("Cannot specify `gasAmount` for swaps to a native token!");
|
|
430
|
-
const _abortController = (0, Utils_1.extendAbortController)(abortSignal);
|
|
431
|
-
const pricePrefetchPromise = this.preFetchPrice(amountData, _abortController.signal);
|
|
432
|
-
const usdPricePrefetchPromise = this.preFetchUsdPrice(_abortController.signal);
|
|
433
|
-
const finalizedBlockHeightPrefetchPromise = this.preFetchFinalizedBlockHeight(_abortController);
|
|
434
|
-
const nativeTokenAddress = this._chain.getNativeCurrencyAddress();
|
|
435
|
-
const gasTokenPricePrefetchPromise = _options.gasAmount === 0n ?
|
|
436
|
-
undefined :
|
|
437
|
-
this.preFetchPrice({ token: nativeTokenAddress }, _abortController.signal);
|
|
438
|
-
const callerFeePrefetchPromise = this.preFetchCallerFeeShare(amountData, _options, pricePrefetchPromise, gasTokenPricePrefetchPromise, _abortController);
|
|
439
|
-
const bitcoinFeeRatePromise = _options.maxAllowedBitcoinFeeRate != Infinity ?
|
|
440
|
-
Promise.resolve(_options.maxAllowedBitcoinFeeRate) :
|
|
441
|
-
this._btcRpc.getFeeRate().then(x => this._options.maxBtcFeeOffset + (x * this._options.maxBtcFeeMultiplier)).catch(e => {
|
|
442
|
-
_abortController.abort(e);
|
|
443
|
-
return undefined;
|
|
444
|
-
});
|
|
445
|
-
return lps.map(lp => {
|
|
446
|
-
return {
|
|
447
|
-
intermediary: lp,
|
|
448
|
-
quote: (0, RetryUtils_1.tryWithRetries)(async () => {
|
|
449
|
-
if (lp.services[SwapType_1.SwapType.SPV_VAULT_FROM_BTC] == null)
|
|
450
|
-
throw new Error("LP service for processing spv vault swaps not found!");
|
|
451
|
-
const abortController = (0, Utils_1.extendAbortController)(_abortController.signal);
|
|
452
|
-
try {
|
|
453
|
-
const resp = await (0, RetryUtils_1.tryWithRetries)(async (retryCount) => {
|
|
454
|
-
return await IntermediaryAPI_1.IntermediaryAPI.prepareSpvFromBTC(this.chainIdentifier, lp.url, {
|
|
455
|
-
address: recipient,
|
|
456
|
-
amount: amountData.amount,
|
|
457
|
-
token: amountData.token.toString(),
|
|
458
|
-
exactOut: !amountData.exactIn,
|
|
459
|
-
gasToken: nativeTokenAddress,
|
|
460
|
-
gasAmount: _options.gasAmount,
|
|
461
|
-
callerFeeRate: (0, Utils_1.throwIfUndefined)(callerFeePrefetchPromise, "Caller fee prefetch failed!"),
|
|
462
|
-
frontingFeeRate: 0n,
|
|
463
|
-
stickyAddress: options?.stickyAddress,
|
|
464
|
-
additionalParams
|
|
465
|
-
}, this._options.postRequestTimeout, abortController.signal, retryCount > 0 ? false : undefined);
|
|
466
|
-
}, undefined, e => e instanceof RequestError_1.RequestError, abortController.signal);
|
|
467
|
-
this.logger.debug("create(" + lp.url + "): LP response: ", resp);
|
|
468
|
-
const callerFeeShare = (await callerFeePrefetchPromise);
|
|
469
|
-
const [pricingInfo, gasPricingInfo, { vault, vaultUtxoValue }] = await Promise.all([
|
|
470
|
-
this.verifyReturnedPrice(lp.services[SwapType_1.SwapType.SPV_VAULT_FROM_BTC], false, resp.btcAmountSwap, resp.total * (100000n + callerFeeShare) / 100000n, amountData.token, { swapFeeBtc: resp.swapFeeBtc }, pricePrefetchPromise, usdPricePrefetchPromise, abortController.signal),
|
|
471
|
-
_options.gasAmount === 0n ? Promise.resolve(undefined) : this.verifyReturnedPrice({ ...lp.services[SwapType_1.SwapType.SPV_VAULT_FROM_BTC], swapBaseFee: 0 }, //Base fee should be charged only on the amount, not on gas
|
|
472
|
-
false, resp.btcAmountGas, resp.totalGas * (100000n + callerFeeShare) / 100000n, nativeTokenAddress, { swapFeeBtc: resp.gasSwapFeeBtc }, gasTokenPricePrefetchPromise, usdPricePrefetchPromise, abortController.signal),
|
|
473
|
-
this.verifyReturnedData(resp, amountData, lp, _options, callerFeeShare, bitcoinFeeRatePromise, abortController.signal)
|
|
474
|
-
]);
|
|
475
|
-
const swapInit = {
|
|
476
|
-
pricingInfo,
|
|
477
|
-
url: lp.url,
|
|
478
|
-
expiry: resp.expiry * 1000,
|
|
479
|
-
swapFee: resp.swapFee,
|
|
480
|
-
swapFeeBtc: resp.swapFeeBtc,
|
|
481
|
-
exactIn: amountData.exactIn ?? true,
|
|
482
|
-
quoteId: resp.quoteId,
|
|
483
|
-
recipient,
|
|
484
|
-
vaultOwner: resp.address,
|
|
485
|
-
vaultId: resp.vaultId,
|
|
486
|
-
vaultRequiredConfirmations: vault.getConfirmations(),
|
|
487
|
-
vaultTokenMultipliers: vault.getTokenData().map(val => val.multiplier),
|
|
488
|
-
vaultBtcAddress: resp.vaultBtcAddress,
|
|
489
|
-
vaultUtxo: resp.btcUtxo,
|
|
490
|
-
vaultUtxoValue: BigInt(vaultUtxoValue),
|
|
491
|
-
btcDestinationAddress: resp.btcAddress,
|
|
492
|
-
btcAmount: resp.btcAmount,
|
|
493
|
-
btcAmountSwap: resp.btcAmountSwap,
|
|
494
|
-
btcAmountGas: resp.btcAmountGas,
|
|
495
|
-
minimumBtcFeeRate: resp.btcFeeRate,
|
|
496
|
-
outputTotalSwap: resp.total,
|
|
497
|
-
outputSwapToken: amountData.token,
|
|
498
|
-
outputTotalGas: resp.totalGas,
|
|
499
|
-
outputGasToken: nativeTokenAddress,
|
|
500
|
-
gasSwapFeeBtc: resp.gasSwapFeeBtc,
|
|
501
|
-
gasSwapFee: resp.gasSwapFee,
|
|
502
|
-
gasPricingInfo,
|
|
503
|
-
callerFeeShare: resp.callerFeeShare,
|
|
504
|
-
frontingFeeShare: resp.frontingFeeShare,
|
|
505
|
-
executionFeeShare: resp.executionFeeShare,
|
|
506
|
-
genesisSmartChainBlockHeight: await (0, Utils_1.throwIfUndefined)(finalizedBlockHeightPrefetchPromise, "Finalize block height promise failed!")
|
|
507
|
-
};
|
|
508
|
-
const quote = new SpvFromBTCSwap_1.SpvFromBTCSwap(this, swapInit);
|
|
509
|
-
return quote;
|
|
510
|
-
}
|
|
511
|
-
catch (e) {
|
|
512
|
-
abortController.abort(e);
|
|
513
|
-
throw e;
|
|
514
|
-
}
|
|
515
|
-
}, undefined, err => !(err instanceof IntermediaryError_1.IntermediaryError && err.recoverable), _abortController.signal)
|
|
516
|
-
};
|
|
517
|
-
});
|
|
518
|
-
}
|
|
519
|
-
/**
|
|
520
|
-
* Recovers an SPV vault (UTXO-controlled vault) based swap from smart chain on-chain data
|
|
521
|
-
*
|
|
522
|
-
* @param state State of the spv vault withdrawal recovered from on-chain data
|
|
523
|
-
* @param vault SPV vault processing the swap
|
|
524
|
-
* @param lp Intermediary (LP) used as a counterparty for the swap
|
|
525
|
-
*/
|
|
526
|
-
async recoverFromState(state, vault, lp) {
|
|
527
|
-
//Get the vault
|
|
528
|
-
vault ??= await this._contract.getVaultData(state.owner, state.vaultId);
|
|
529
|
-
if (vault == null)
|
|
530
|
-
return null;
|
|
531
|
-
if (state.btcTxId == null)
|
|
532
|
-
return null;
|
|
533
|
-
const btcTx = await this._btcRpc.getTransaction(state.btcTxId);
|
|
534
|
-
if (btcTx == null)
|
|
535
|
-
return null;
|
|
536
|
-
const withdrawalData = await this._contract.getWithdrawalData(btcTx)
|
|
537
|
-
.catch(e => {
|
|
538
|
-
this.logger.warn(`Error parsing withdrawal data for tx ${btcTx.txid}: `, e);
|
|
539
|
-
return null;
|
|
540
|
-
});
|
|
541
|
-
if (withdrawalData == null)
|
|
542
|
-
return null;
|
|
543
|
-
const vaultTokens = vault.getTokenData();
|
|
544
|
-
const withdrawalDataOutputs = withdrawalData.getTotalOutput();
|
|
545
|
-
const txBlock = await state.getTxBlock?.();
|
|
546
|
-
const swapInit = {
|
|
547
|
-
pricingInfo: {
|
|
548
|
-
isValid: true,
|
|
549
|
-
satsBaseFee: 0n,
|
|
550
|
-
swapPriceUSatPerToken: 100000000000000n,
|
|
551
|
-
realPriceUSatPerToken: 100000000000000n,
|
|
552
|
-
differencePPM: 0n,
|
|
553
|
-
feePPM: 0n,
|
|
554
|
-
},
|
|
555
|
-
url: lp?.url,
|
|
556
|
-
expiry: 0,
|
|
557
|
-
swapFee: 0n,
|
|
558
|
-
swapFeeBtc: 0n,
|
|
559
|
-
exactIn: true,
|
|
560
|
-
//Use bitcoin tx id as quote id, even though this is not strictly correct as this
|
|
561
|
-
// is an off-chain identifier presented by the LP that cannot be recovered from on-chain
|
|
562
|
-
// data
|
|
563
|
-
quoteId: btcTx.txid,
|
|
564
|
-
recipient: state.recipient,
|
|
565
|
-
vaultOwner: state.owner,
|
|
566
|
-
vaultId: state.vaultId,
|
|
567
|
-
vaultRequiredConfirmations: vault.getConfirmations(),
|
|
568
|
-
vaultTokenMultipliers: vault.getTokenData().map(val => val.multiplier),
|
|
569
|
-
vaultBtcAddress: (0, BitcoinUtils_1.fromOutputScript)(this._options.bitcoinNetwork, withdrawalData.getNewVaultScript().toString("hex")),
|
|
570
|
-
vaultUtxo: withdrawalData.getSpentVaultUtxo(),
|
|
571
|
-
vaultUtxoValue: BigInt(withdrawalData.getNewVaultBtcAmount()),
|
|
572
|
-
btcDestinationAddress: (0, BitcoinUtils_1.fromOutputScript)(this._options.bitcoinNetwork, btcTx.outs[2].scriptPubKey.hex),
|
|
573
|
-
btcAmount: BigInt(btcTx.outs[2].value),
|
|
574
|
-
btcAmountSwap: BigInt(btcTx.outs[2].value),
|
|
575
|
-
btcAmountGas: 0n,
|
|
576
|
-
minimumBtcFeeRate: 0,
|
|
577
|
-
outputTotalSwap: withdrawalDataOutputs[0] * vaultTokens[0].multiplier,
|
|
578
|
-
outputSwapToken: vaultTokens[0].token,
|
|
579
|
-
outputTotalGas: withdrawalDataOutputs[1] * vaultTokens[1].multiplier,
|
|
580
|
-
outputGasToken: vaultTokens[1].token,
|
|
581
|
-
gasSwapFeeBtc: 0n,
|
|
582
|
-
gasSwapFee: 0n,
|
|
583
|
-
gasPricingInfo: {
|
|
584
|
-
isValid: true,
|
|
585
|
-
satsBaseFee: 0n,
|
|
586
|
-
swapPriceUSatPerToken: 100000000000000n,
|
|
587
|
-
realPriceUSatPerToken: 100000000000000n,
|
|
588
|
-
differencePPM: 0n,
|
|
589
|
-
feePPM: 0n,
|
|
590
|
-
},
|
|
591
|
-
callerFeeShare: withdrawalData.callerFeeRate,
|
|
592
|
-
frontingFeeShare: withdrawalData.frontingFeeRate,
|
|
593
|
-
executionFeeShare: withdrawalData.executionFeeRate,
|
|
594
|
-
genesisSmartChainBlockHeight: txBlock?.blockHeight ?? 0
|
|
595
|
-
};
|
|
596
|
-
const quote = new SpvFromBTCSwap_1.SpvFromBTCSwap(this, swapInit);
|
|
597
|
-
quote._data = withdrawalData;
|
|
598
|
-
if (txBlock != null) {
|
|
599
|
-
quote.createdAt = txBlock.blockTime * 1000;
|
|
600
|
-
}
|
|
601
|
-
else if (btcTx.blockhash == null) {
|
|
602
|
-
quote.createdAt = Date.now();
|
|
603
|
-
}
|
|
604
|
-
else {
|
|
605
|
-
const blockHeader = await this._btcRpc.getBlockHeader(btcTx.blockhash);
|
|
606
|
-
quote.createdAt = blockHeader == null ? Date.now() : blockHeader.getTimestamp() * 1000;
|
|
607
|
-
}
|
|
608
|
-
quote._setInitiated();
|
|
609
|
-
if (btcTx.inputAddresses != null)
|
|
610
|
-
quote._senderAddress = btcTx.inputAddresses[1];
|
|
611
|
-
if (state.type === base_1.SpvWithdrawalStateType.FRONTED) {
|
|
612
|
-
quote._frontTxId = state.txId;
|
|
613
|
-
quote._state = SpvFromBTCSwap_1.SpvFromBTCSwapState.FRONTED;
|
|
614
|
-
}
|
|
615
|
-
else {
|
|
616
|
-
quote._claimTxId = state.txId;
|
|
617
|
-
quote._state = SpvFromBTCSwap_1.SpvFromBTCSwapState.CLAIMED;
|
|
618
|
-
}
|
|
619
|
-
await quote._save();
|
|
620
|
-
return quote;
|
|
621
|
-
}
|
|
622
|
-
/**
|
|
623
|
-
* Returns a random dummy PSBT that can be used for fee estimation, the last output (the LP output) is omitted
|
|
624
|
-
* to allow for coinselection algorithm to determine maximum sendable amount there
|
|
625
|
-
*
|
|
626
|
-
* @param includeGasToken Whether to return the PSBT also with the gas token amount (increases the vSize by 8)
|
|
627
|
-
*/
|
|
628
|
-
getDummySwapPsbt(includeGasToken = false) {
|
|
629
|
-
//Construct dummy swap psbt
|
|
630
|
-
const psbt = new btc_signer_1.Transaction({
|
|
631
|
-
allowUnknownInputs: true,
|
|
632
|
-
allowLegacyWitnessUtxo: true,
|
|
633
|
-
allowUnknownOutputs: true
|
|
634
|
-
});
|
|
635
|
-
const randomVaultOutScript = btc_signer_1.OutScript.encode({ type: "tr", pubkey: Buffer.from("0101010101010101010101010101010101010101010101010101010101010101", "hex") });
|
|
636
|
-
psbt.addInput({
|
|
637
|
-
txid: (0, Utils_1.randomBytes)(32),
|
|
638
|
-
index: 0,
|
|
639
|
-
witnessUtxo: {
|
|
640
|
-
script: randomVaultOutScript,
|
|
641
|
-
amount: 600n
|
|
642
|
-
}
|
|
643
|
-
});
|
|
644
|
-
psbt.addOutput({
|
|
645
|
-
script: randomVaultOutScript,
|
|
646
|
-
amount: 600n
|
|
647
|
-
});
|
|
648
|
-
const opReturnData = this._contract.toOpReturnData(this._chain.randomAddress(), includeGasToken ? [0xffffffffffffffffn, 0xffffffffffffffffn] : [0xffffffffffffffffn]);
|
|
649
|
-
psbt.addOutput({
|
|
650
|
-
script: Buffer.concat([
|
|
651
|
-
opReturnData.length <= 75 ? Buffer.from([0x6a, opReturnData.length]) : Buffer.from([0x6a, 0x4c, opReturnData.length]),
|
|
652
|
-
opReturnData
|
|
653
|
-
]),
|
|
654
|
-
amount: 0n
|
|
655
|
-
});
|
|
656
|
-
return psbt;
|
|
657
|
-
}
|
|
658
|
-
/**
|
|
659
|
-
* @inheritDoc
|
|
660
|
-
* @internal
|
|
661
|
-
*/
|
|
662
|
-
async _checkPastSwaps(pastSwaps) {
|
|
663
|
-
const changedSwaps = new Set();
|
|
664
|
-
const removeSwaps = [];
|
|
665
|
-
const broadcastedOrConfirmedSwaps = [];
|
|
666
|
-
for (let pastSwap of pastSwaps) {
|
|
667
|
-
let changed = false;
|
|
668
|
-
if (pastSwap._state === SpvFromBTCSwap_1.SpvFromBTCSwapState.SIGNED ||
|
|
669
|
-
pastSwap._state === SpvFromBTCSwap_1.SpvFromBTCSwapState.POSTED ||
|
|
670
|
-
pastSwap._state === SpvFromBTCSwap_1.SpvFromBTCSwapState.BROADCASTED ||
|
|
671
|
-
pastSwap._state === SpvFromBTCSwap_1.SpvFromBTCSwapState.QUOTE_SOFT_EXPIRED ||
|
|
672
|
-
pastSwap._state === SpvFromBTCSwap_1.SpvFromBTCSwapState.DECLINED ||
|
|
673
|
-
pastSwap._state === SpvFromBTCSwap_1.SpvFromBTCSwapState.BTC_TX_CONFIRMED) {
|
|
674
|
-
//Check BTC transaction
|
|
675
|
-
if (await pastSwap._syncStateFromBitcoin(false))
|
|
676
|
-
changed ||= true;
|
|
677
|
-
}
|
|
678
|
-
if (pastSwap._state === SpvFromBTCSwap_1.SpvFromBTCSwapState.CREATED ||
|
|
679
|
-
pastSwap._state === SpvFromBTCSwap_1.SpvFromBTCSwapState.SIGNED ||
|
|
680
|
-
pastSwap._state === SpvFromBTCSwap_1.SpvFromBTCSwapState.POSTED) {
|
|
681
|
-
if (await pastSwap._verifyQuoteDefinitelyExpired()) {
|
|
682
|
-
if (pastSwap._state === SpvFromBTCSwap_1.SpvFromBTCSwapState.CREATED) {
|
|
683
|
-
pastSwap._state = SpvFromBTCSwap_1.SpvFromBTCSwapState.QUOTE_EXPIRED;
|
|
684
|
-
}
|
|
685
|
-
else {
|
|
686
|
-
pastSwap._state = SpvFromBTCSwap_1.SpvFromBTCSwapState.QUOTE_SOFT_EXPIRED;
|
|
687
|
-
}
|
|
688
|
-
changed ||= true;
|
|
689
|
-
}
|
|
690
|
-
}
|
|
691
|
-
if (pastSwap.isQuoteExpired()) {
|
|
692
|
-
removeSwaps.push(pastSwap);
|
|
693
|
-
continue;
|
|
694
|
-
}
|
|
695
|
-
if (changed)
|
|
696
|
-
changedSwaps.add(pastSwap);
|
|
697
|
-
if (pastSwap._state === SpvFromBTCSwap_1.SpvFromBTCSwapState.BROADCASTED || pastSwap._state === SpvFromBTCSwap_1.SpvFromBTCSwapState.BTC_TX_CONFIRMED) {
|
|
698
|
-
if (pastSwap._data != null)
|
|
699
|
-
broadcastedOrConfirmedSwaps.push(pastSwap);
|
|
700
|
-
}
|
|
701
|
-
}
|
|
702
|
-
const checkWithdrawalStateSwaps = [];
|
|
703
|
-
const _fronts = await this._contract.getFronterAddresses(broadcastedOrConfirmedSwaps.map(val => ({
|
|
704
|
-
...val.getSpvVaultData(),
|
|
705
|
-
withdrawal: val._data
|
|
706
|
-
})));
|
|
707
|
-
const _vaultUtxos = await this._contract.getVaultLatestUtxos(broadcastedOrConfirmedSwaps.map(val => val.getSpvVaultData()));
|
|
708
|
-
for (const pastSwap of broadcastedOrConfirmedSwaps) {
|
|
709
|
-
const fronterAddress = _fronts[pastSwap._data.getTxId()];
|
|
710
|
-
const vault = pastSwap.getSpvVaultData();
|
|
711
|
-
const latestVaultUtxo = _vaultUtxos[vault.owner]?.[vault.vaultId.toString(10)];
|
|
712
|
-
if (fronterAddress === undefined)
|
|
713
|
-
this.logger.warn(`_checkPastSwaps(): No fronter address returned for ${pastSwap._data.getTxId()}`);
|
|
714
|
-
if (latestVaultUtxo === undefined)
|
|
715
|
-
this.logger.warn(`_checkPastSwaps(): No last vault utxo returned for ${pastSwap._data.getTxId()}`);
|
|
716
|
-
if (await pastSwap._shouldCheckWithdrawalState(fronterAddress, latestVaultUtxo))
|
|
717
|
-
checkWithdrawalStateSwaps.push(pastSwap);
|
|
718
|
-
}
|
|
719
|
-
const withdrawalStates = await this._contract.getWithdrawalStates(checkWithdrawalStateSwaps.map(val => ({
|
|
720
|
-
withdrawal: val._data,
|
|
721
|
-
scStartBlockheight: val._genesisSmartChainBlockHeight
|
|
722
|
-
})));
|
|
723
|
-
for (const pastSwap of checkWithdrawalStateSwaps) {
|
|
724
|
-
const status = withdrawalStates[pastSwap._data.getTxId()];
|
|
725
|
-
if (status == null) {
|
|
726
|
-
this.logger.warn(`_checkPastSwaps(): No withdrawal state returned for ${pastSwap._data.getTxId()}`);
|
|
727
|
-
continue;
|
|
728
|
-
}
|
|
729
|
-
this.logger.debug("syncStateFromChain(): status of " + pastSwap._data.btcTx.txid, status?.type);
|
|
730
|
-
let changed = false;
|
|
731
|
-
switch (status.type) {
|
|
732
|
-
case base_1.SpvWithdrawalStateType.FRONTED:
|
|
733
|
-
pastSwap._frontTxId = status.txId;
|
|
734
|
-
pastSwap._state = SpvFromBTCSwap_1.SpvFromBTCSwapState.FRONTED;
|
|
735
|
-
changed ||= true;
|
|
736
|
-
break;
|
|
737
|
-
case base_1.SpvWithdrawalStateType.CLAIMED:
|
|
738
|
-
pastSwap._claimTxId = status.txId;
|
|
739
|
-
pastSwap._state = SpvFromBTCSwap_1.SpvFromBTCSwapState.CLAIMED;
|
|
740
|
-
changed ||= true;
|
|
741
|
-
break;
|
|
742
|
-
case base_1.SpvWithdrawalStateType.CLOSED:
|
|
743
|
-
pastSwap._state = SpvFromBTCSwap_1.SpvFromBTCSwapState.CLOSED;
|
|
744
|
-
changed ||= true;
|
|
745
|
-
break;
|
|
746
|
-
}
|
|
747
|
-
if (changed)
|
|
748
|
-
changedSwaps.add(pastSwap);
|
|
749
|
-
}
|
|
750
|
-
return {
|
|
751
|
-
changedSwaps: Array.from(changedSwaps),
|
|
752
|
-
removeSwaps
|
|
753
|
-
};
|
|
754
|
-
}
|
|
755
|
-
}
|
|
756
|
-
exports.SpvFromBTCWrapper = SpvFromBTCWrapper;
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.SpvFromBTCWrapper = void 0;
|
|
4
|
+
const ISwapWrapper_1 = require("../ISwapWrapper");
|
|
5
|
+
const base_1 = require("@atomiqlabs/base");
|
|
6
|
+
const SpvFromBTCSwap_1 = require("./SpvFromBTCSwap");
|
|
7
|
+
const utils_1 = require("@scure/btc-signer/utils");
|
|
8
|
+
const SwapType_1 = require("../../enums/SwapType");
|
|
9
|
+
const Utils_1 = require("../../utils/Utils");
|
|
10
|
+
const BitcoinUtils_1 = require("../../utils/BitcoinUtils");
|
|
11
|
+
const IntermediaryAPI_1 = require("../../intermediaries/apis/IntermediaryAPI");
|
|
12
|
+
const RequestError_1 = require("../../errors/RequestError");
|
|
13
|
+
const IntermediaryError_1 = require("../../errors/IntermediaryError");
|
|
14
|
+
const btc_signer_1 = require("@scure/btc-signer");
|
|
15
|
+
const RetryUtils_1 = require("../../utils/RetryUtils");
|
|
16
|
+
const UserError_1 = require("../../errors/UserError");
|
|
17
|
+
/**
|
|
18
|
+
* New spv vault (UTXO-controlled vault) based swaps for Bitcoin -> Smart chain swaps not requiring
|
|
19
|
+
* any initiation on the destination chain, and with the added possibility for the user to receive
|
|
20
|
+
* a native token on the destination chain as part of the swap (a "gas drop" feature).
|
|
21
|
+
*
|
|
22
|
+
* @category Swaps/Bitcoin → Smart chain
|
|
23
|
+
*/
|
|
24
|
+
class SpvFromBTCWrapper extends ISwapWrapper_1.ISwapWrapper {
|
|
25
|
+
/**
|
|
26
|
+
* @param chainIdentifier
|
|
27
|
+
* @param unifiedStorage Storage interface for the current environment
|
|
28
|
+
* @param unifiedChainEvents On-chain event listener
|
|
29
|
+
* @param chain
|
|
30
|
+
* @param contract Underlying contract handling the swaps
|
|
31
|
+
* @param prices Pricing to use
|
|
32
|
+
* @param tokens
|
|
33
|
+
* @param spvWithdrawalDataDeserializer Deserializer for SpvVaultWithdrawalData
|
|
34
|
+
* @param btcRelay
|
|
35
|
+
* @param synchronizer Btc relay synchronizer
|
|
36
|
+
* @param btcRpc Bitcoin RPC which also supports getting transactions by txoHash
|
|
37
|
+
* @param options
|
|
38
|
+
* @param events Instance to use for emitting events
|
|
39
|
+
*/
|
|
40
|
+
constructor(chainIdentifier, unifiedStorage, unifiedChainEvents, chain, contract, prices, tokens, spvWithdrawalDataDeserializer, btcRelay, synchronizer, btcRpc, options, events) {
|
|
41
|
+
super(chainIdentifier, unifiedStorage, unifiedChainEvents, chain, prices, tokens, {
|
|
42
|
+
...options,
|
|
43
|
+
bitcoinNetwork: options?.bitcoinNetwork ?? utils_1.TEST_NETWORK,
|
|
44
|
+
maxConfirmations: options?.maxConfirmations ?? 6,
|
|
45
|
+
bitcoinBlocktime: options?.bitcoinBlocktime ?? 10 * 60,
|
|
46
|
+
maxTransactionsDelta: options?.maxTransactionsDelta ?? 3,
|
|
47
|
+
maxRawAmountAdjustmentDifferencePPM: options?.maxRawAmountAdjustmentDifferencePPM ?? 100,
|
|
48
|
+
maxBtcFeeOffset: options?.maxBtcFeeOffset ?? 10,
|
|
49
|
+
maxBtcFeeMultiplier: options?.maxBtcFeeMultiplier ?? 1.5
|
|
50
|
+
}, events);
|
|
51
|
+
this.TYPE = SwapType_1.SwapType.SPV_VAULT_FROM_BTC;
|
|
52
|
+
/**
|
|
53
|
+
* @internal
|
|
54
|
+
*/
|
|
55
|
+
this._claimableSwapStates = [SpvFromBTCSwap_1.SpvFromBTCSwapState.BTC_TX_CONFIRMED];
|
|
56
|
+
/**
|
|
57
|
+
* @internal
|
|
58
|
+
*/
|
|
59
|
+
this._swapDeserializer = SpvFromBTCSwap_1.SpvFromBTCSwap;
|
|
60
|
+
/**
|
|
61
|
+
* @internal
|
|
62
|
+
*/
|
|
63
|
+
this.tickSwapState = [
|
|
64
|
+
SpvFromBTCSwap_1.SpvFromBTCSwapState.CREATED,
|
|
65
|
+
SpvFromBTCSwap_1.SpvFromBTCSwapState.QUOTE_SOFT_EXPIRED,
|
|
66
|
+
SpvFromBTCSwap_1.SpvFromBTCSwapState.SIGNED,
|
|
67
|
+
SpvFromBTCSwap_1.SpvFromBTCSwapState.POSTED,
|
|
68
|
+
SpvFromBTCSwap_1.SpvFromBTCSwapState.BROADCASTED
|
|
69
|
+
];
|
|
70
|
+
/**
|
|
71
|
+
* @internal
|
|
72
|
+
*/
|
|
73
|
+
this._pendingSwapStates = [
|
|
74
|
+
SpvFromBTCSwap_1.SpvFromBTCSwapState.CREATED,
|
|
75
|
+
SpvFromBTCSwap_1.SpvFromBTCSwapState.SIGNED,
|
|
76
|
+
SpvFromBTCSwap_1.SpvFromBTCSwapState.POSTED,
|
|
77
|
+
SpvFromBTCSwap_1.SpvFromBTCSwapState.QUOTE_SOFT_EXPIRED,
|
|
78
|
+
SpvFromBTCSwap_1.SpvFromBTCSwapState.BROADCASTED,
|
|
79
|
+
SpvFromBTCSwap_1.SpvFromBTCSwapState.DECLINED,
|
|
80
|
+
SpvFromBTCSwap_1.SpvFromBTCSwapState.BTC_TX_CONFIRMED
|
|
81
|
+
];
|
|
82
|
+
this._spvWithdrawalDataDeserializer = spvWithdrawalDataDeserializer;
|
|
83
|
+
this._contract = contract;
|
|
84
|
+
this.btcRelay = btcRelay;
|
|
85
|
+
this._synchronizer = synchronizer;
|
|
86
|
+
this._btcRpc = btcRpc;
|
|
87
|
+
}
|
|
88
|
+
async processEventFront(event, swap) {
|
|
89
|
+
if (swap._state === SpvFromBTCSwap_1.SpvFromBTCSwapState.SIGNED || swap._state === SpvFromBTCSwap_1.SpvFromBTCSwapState.POSTED ||
|
|
90
|
+
swap._state === SpvFromBTCSwap_1.SpvFromBTCSwapState.BROADCASTED || swap._state === SpvFromBTCSwap_1.SpvFromBTCSwapState.DECLINED ||
|
|
91
|
+
swap._state === SpvFromBTCSwap_1.SpvFromBTCSwapState.QUOTE_SOFT_EXPIRED || swap._state === SpvFromBTCSwap_1.SpvFromBTCSwapState.BTC_TX_CONFIRMED) {
|
|
92
|
+
swap._state = SpvFromBTCSwap_1.SpvFromBTCSwapState.FRONTED;
|
|
93
|
+
await swap._setBitcoinTxId(event.btcTxId).catch(e => {
|
|
94
|
+
this.logger.warn("processEventFront(): Failed to set bitcoin txId: ", e);
|
|
95
|
+
});
|
|
96
|
+
return true;
|
|
97
|
+
}
|
|
98
|
+
return false;
|
|
99
|
+
}
|
|
100
|
+
async processEventClaim(event, swap) {
|
|
101
|
+
if (swap._state === SpvFromBTCSwap_1.SpvFromBTCSwapState.SIGNED || swap._state === SpvFromBTCSwap_1.SpvFromBTCSwapState.POSTED ||
|
|
102
|
+
swap._state === SpvFromBTCSwap_1.SpvFromBTCSwapState.BROADCASTED || swap._state === SpvFromBTCSwap_1.SpvFromBTCSwapState.DECLINED ||
|
|
103
|
+
swap._state === SpvFromBTCSwap_1.SpvFromBTCSwapState.QUOTE_SOFT_EXPIRED || swap._state === SpvFromBTCSwap_1.SpvFromBTCSwapState.FRONTED ||
|
|
104
|
+
swap._state === SpvFromBTCSwap_1.SpvFromBTCSwapState.BTC_TX_CONFIRMED) {
|
|
105
|
+
swap._state = SpvFromBTCSwap_1.SpvFromBTCSwapState.CLAIMED;
|
|
106
|
+
await swap._setBitcoinTxId(event.btcTxId).catch(e => {
|
|
107
|
+
this.logger.warn("processEventClaim(): Failed to set bitcoin txId: ", e);
|
|
108
|
+
});
|
|
109
|
+
return true;
|
|
110
|
+
}
|
|
111
|
+
return false;
|
|
112
|
+
}
|
|
113
|
+
processEventClose(event, swap) {
|
|
114
|
+
if (swap._state === SpvFromBTCSwap_1.SpvFromBTCSwapState.SIGNED || swap._state === SpvFromBTCSwap_1.SpvFromBTCSwapState.POSTED ||
|
|
115
|
+
swap._state === SpvFromBTCSwap_1.SpvFromBTCSwapState.BROADCASTED || swap._state === SpvFromBTCSwap_1.SpvFromBTCSwapState.DECLINED ||
|
|
116
|
+
swap._state === SpvFromBTCSwap_1.SpvFromBTCSwapState.QUOTE_SOFT_EXPIRED || swap._state === SpvFromBTCSwap_1.SpvFromBTCSwapState.BTC_TX_CONFIRMED) {
|
|
117
|
+
swap._state = SpvFromBTCSwap_1.SpvFromBTCSwapState.CLOSED;
|
|
118
|
+
return Promise.resolve(true);
|
|
119
|
+
}
|
|
120
|
+
return Promise.resolve(false);
|
|
121
|
+
}
|
|
122
|
+
/**
|
|
123
|
+
* @inheritDoc
|
|
124
|
+
* @internal
|
|
125
|
+
*/
|
|
126
|
+
async processEvent(event, swap) {
|
|
127
|
+
if (swap == null)
|
|
128
|
+
return;
|
|
129
|
+
let swapChanged = false;
|
|
130
|
+
if (event instanceof base_1.SpvVaultFrontEvent) {
|
|
131
|
+
swapChanged = await this.processEventFront(event, swap);
|
|
132
|
+
if (event.meta?.txId != null && swap._frontTxId !== event.meta.txId) {
|
|
133
|
+
swap._frontTxId = event.meta.txId;
|
|
134
|
+
swapChanged ||= true;
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
if (event instanceof base_1.SpvVaultClaimEvent) {
|
|
138
|
+
swapChanged = await this.processEventClaim(event, swap);
|
|
139
|
+
if (event.meta?.txId != null && swap._claimTxId !== event.meta.txId) {
|
|
140
|
+
swap._claimTxId = event.meta.txId;
|
|
141
|
+
swapChanged ||= true;
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
if (event instanceof base_1.SpvVaultCloseEvent) {
|
|
145
|
+
swapChanged = await this.processEventClose(event, swap);
|
|
146
|
+
}
|
|
147
|
+
this.logger.info("processEvents(): " + event.constructor.name + " processed for " + swap.getId() + " swap: ", swap);
|
|
148
|
+
if (swapChanged) {
|
|
149
|
+
await swap._saveAndEmit();
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
/**
|
|
153
|
+
* Pre-fetches latest finalized block height of the smart chain
|
|
154
|
+
*
|
|
155
|
+
* @param abortController
|
|
156
|
+
* @private
|
|
157
|
+
*/
|
|
158
|
+
async preFetchFinalizedBlockHeight(abortController) {
|
|
159
|
+
try {
|
|
160
|
+
const block = await this._chain.getFinalizedBlock();
|
|
161
|
+
return block.height;
|
|
162
|
+
}
|
|
163
|
+
catch (e) {
|
|
164
|
+
abortController.abort(e);
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
/**
|
|
168
|
+
* Pre-fetches caller (watchtower) bounty data for the swap. Doesn't throw, instead returns null and aborts the
|
|
169
|
+
* provided abortController
|
|
170
|
+
*
|
|
171
|
+
* @param amountData
|
|
172
|
+
* @param options Options as passed to the swap creation function
|
|
173
|
+
* @param pricePrefetch
|
|
174
|
+
* @param nativeTokenPricePrefetch
|
|
175
|
+
* @param abortController
|
|
176
|
+
* @private
|
|
177
|
+
*/
|
|
178
|
+
async preFetchCallerFeeShare(amountData, options, pricePrefetch, nativeTokenPricePrefetch, abortController) {
|
|
179
|
+
if (options.unsafeZeroWatchtowerFee)
|
|
180
|
+
return 0n;
|
|
181
|
+
if (amountData.amount === 0n)
|
|
182
|
+
return 0n;
|
|
183
|
+
try {
|
|
184
|
+
const [feePerBlock, btcRelayData, currentBtcBlock, claimFeeRate, nativeTokenPrice] = await Promise.all([
|
|
185
|
+
this.btcRelay.getFeePerBlock(),
|
|
186
|
+
this.btcRelay.getTipData(),
|
|
187
|
+
this._btcRpc.getTipHeight(),
|
|
188
|
+
this._contract.getClaimFee(this._chain.randomAddress()),
|
|
189
|
+
nativeTokenPricePrefetch ?? (amountData.token === this._chain.getNativeCurrencyAddress() ?
|
|
190
|
+
pricePrefetch :
|
|
191
|
+
this._prices.preFetchPrice(this.chainIdentifier, this._chain.getNativeCurrencyAddress(), abortController.signal))
|
|
192
|
+
]);
|
|
193
|
+
if (btcRelayData == null)
|
|
194
|
+
throw new Error("Btc relay doesn't seem to be initialized!");
|
|
195
|
+
const currentBtcRelayBlock = btcRelayData.blockheight;
|
|
196
|
+
const blockDelta = Math.max(currentBtcBlock - currentBtcRelayBlock + this._options.maxConfirmations, 0);
|
|
197
|
+
const totalFeeInNativeToken = ((BigInt(blockDelta) * feePerBlock) +
|
|
198
|
+
(claimFeeRate * BigInt(this._options.maxTransactionsDelta))) * BigInt(Math.floor(options.feeSafetyFactor * 1000000)) / 1000000n;
|
|
199
|
+
let payoutAmount;
|
|
200
|
+
if (amountData.exactIn) {
|
|
201
|
+
//Convert input amount in BTC to
|
|
202
|
+
const amountInNativeToken = await this._prices.getFromBtcSwapAmount(this.chainIdentifier, amountData.amount, this._chain.getNativeCurrencyAddress(), abortController.signal, nativeTokenPrice);
|
|
203
|
+
payoutAmount = amountInNativeToken - totalFeeInNativeToken;
|
|
204
|
+
}
|
|
205
|
+
else {
|
|
206
|
+
if (amountData.token === this._chain.getNativeCurrencyAddress()) {
|
|
207
|
+
//Both amounts in same currency
|
|
208
|
+
payoutAmount = amountData.amount;
|
|
209
|
+
}
|
|
210
|
+
else {
|
|
211
|
+
//Need to convert both to native currency
|
|
212
|
+
const btcAmount = await this._prices.getToBtcSwapAmount(this.chainIdentifier, amountData.amount, amountData.token, abortController.signal, await pricePrefetch);
|
|
213
|
+
payoutAmount = await this._prices.getFromBtcSwapAmount(this.chainIdentifier, btcAmount, this._chain.getNativeCurrencyAddress(), abortController.signal, nativeTokenPrice);
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
this.logger.debug("preFetchCallerFeeShare(): Caller fee in native token: " + totalFeeInNativeToken.toString(10) + " total payout in native token: " + payoutAmount.toString(10));
|
|
217
|
+
const callerFeeShare = ((totalFeeInNativeToken * 100000n) + payoutAmount - 1n) / payoutAmount; //Make sure to round up here
|
|
218
|
+
if (callerFeeShare < 0n)
|
|
219
|
+
return 0n;
|
|
220
|
+
if (callerFeeShare >= 2n ** 20n)
|
|
221
|
+
return 2n ** 20n - 1n;
|
|
222
|
+
return callerFeeShare;
|
|
223
|
+
}
|
|
224
|
+
catch (e) {
|
|
225
|
+
abortController.abort(e);
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
/**
|
|
229
|
+
* Verifies response returned from intermediary
|
|
230
|
+
*
|
|
231
|
+
* @param resp Response as returned by the intermediary
|
|
232
|
+
* @param amountData
|
|
233
|
+
* @param lp Intermediary
|
|
234
|
+
* @param options Options as passed to the swap creation function
|
|
235
|
+
* @param callerFeeShare
|
|
236
|
+
* @param bitcoinFeeRatePromise Maximum accepted fee rate from the LPs
|
|
237
|
+
* @param abortSignal
|
|
238
|
+
* @private
|
|
239
|
+
* @throws {IntermediaryError} in case the response is invalid
|
|
240
|
+
*/
|
|
241
|
+
async verifyReturnedData(resp, amountData, lp, options, callerFeeShare, bitcoinFeeRatePromise, abortSignal) {
|
|
242
|
+
const btcFeeRate = await (0, Utils_1.throwIfUndefined)(bitcoinFeeRatePromise, "Bitcoin fee rate promise failed!");
|
|
243
|
+
abortSignal.throwIfAborted();
|
|
244
|
+
if (btcFeeRate != null && resp.btcFeeRate > btcFeeRate)
|
|
245
|
+
throw new IntermediaryError_1.IntermediaryError(`Required bitcoin fee rate returned from the LP is too high! Maximum accepted: ${btcFeeRate} sats/vB, required by LP: ${resp.btcFeeRate} sats/vB`);
|
|
246
|
+
//Vault related
|
|
247
|
+
let vaultScript;
|
|
248
|
+
let vaultAddressType;
|
|
249
|
+
let btcAddressScript;
|
|
250
|
+
//Ensure valid btc addresses returned
|
|
251
|
+
try {
|
|
252
|
+
vaultScript = (0, BitcoinUtils_1.toOutputScript)(this._options.bitcoinNetwork, resp.vaultBtcAddress);
|
|
253
|
+
vaultAddressType = (0, BitcoinUtils_1.toCoinselectAddressType)(vaultScript);
|
|
254
|
+
btcAddressScript = (0, BitcoinUtils_1.toOutputScript)(this._options.bitcoinNetwork, resp.btcAddress);
|
|
255
|
+
}
|
|
256
|
+
catch (e) {
|
|
257
|
+
throw new IntermediaryError_1.IntermediaryError("Invalid btc address data returned", e);
|
|
258
|
+
}
|
|
259
|
+
const decodedUtxo = resp.btcUtxo.split(":");
|
|
260
|
+
if (resp.address !== lp.getAddress(this.chainIdentifier) || //Ensure the LP is indeed the vault owner
|
|
261
|
+
resp.vaultId < 0n || //Ensure vaultId is not negative
|
|
262
|
+
vaultScript == null || //Make sure vault script is parsable and of known type
|
|
263
|
+
btcAddressScript == null || //Make sure btc address script is parsable and of known type
|
|
264
|
+
vaultAddressType === "p2pkh" || vaultAddressType === "p2sh-p2wpkh" || //Constrain the vault script type to witness types
|
|
265
|
+
decodedUtxo.length !== 2 || decodedUtxo[0].length !== 64 || isNaN(parseInt(decodedUtxo[1])) || //Check valid UTXO
|
|
266
|
+
resp.btcFeeRate < 1 || resp.btcFeeRate > 10000 //Sanity check on the returned BTC fee rate
|
|
267
|
+
)
|
|
268
|
+
throw new IntermediaryError_1.IntermediaryError("Invalid vault data returned!");
|
|
269
|
+
//Amounts sanity
|
|
270
|
+
if (resp.btcAmountSwap + resp.btcAmountGas !== resp.btcAmount)
|
|
271
|
+
throw new Error("Btc amount mismatch");
|
|
272
|
+
if (resp.swapFeeBtc + resp.gasSwapFeeBtc !== resp.totalFeeBtc)
|
|
273
|
+
throw new Error("Btc fee mismatch");
|
|
274
|
+
//TODO: For now ensure fees are at 0
|
|
275
|
+
if (resp.callerFeeShare !== callerFeeShare ||
|
|
276
|
+
resp.frontingFeeShare !== 0n ||
|
|
277
|
+
resp.executionFeeShare !== 0n)
|
|
278
|
+
throw new IntermediaryError_1.IntermediaryError("Invalid caller/fronting/execution fee returned");
|
|
279
|
+
//Check expiry
|
|
280
|
+
const timeNowSeconds = Math.floor(Date.now() / 1000);
|
|
281
|
+
if (resp.expiry < timeNowSeconds)
|
|
282
|
+
throw new IntermediaryError_1.IntermediaryError(`Quote already expired, expiry: ${resp.expiry}, systemTime: ${timeNowSeconds}, clockAdjusted: ${Date._now != null}`);
|
|
283
|
+
let utxo = resp.btcUtxo.toLowerCase();
|
|
284
|
+
const [txId, voutStr] = utxo.split(":");
|
|
285
|
+
const abortController = (0, Utils_1.extendAbortController)(abortSignal);
|
|
286
|
+
let [vault, { vaultUtxoValue, btcTx }] = await Promise.all([
|
|
287
|
+
(async () => {
|
|
288
|
+
//Fetch vault data
|
|
289
|
+
let vault;
|
|
290
|
+
try {
|
|
291
|
+
vault = await this._contract.getVaultData(resp.address, resp.vaultId);
|
|
292
|
+
}
|
|
293
|
+
catch (e) {
|
|
294
|
+
this.logger.error("Error getting spv vault (owner: " + resp.address + " vaultId: " + resp.vaultId.toString(10) + "): ", e);
|
|
295
|
+
throw new IntermediaryError_1.IntermediaryError("Spv swap vault not found", e);
|
|
296
|
+
}
|
|
297
|
+
abortController.signal.throwIfAborted();
|
|
298
|
+
//Make sure vault is opened
|
|
299
|
+
if (vault == null || !vault.isOpened())
|
|
300
|
+
throw new IntermediaryError_1.IntermediaryError("Returned spv swap vault is not opened!");
|
|
301
|
+
//Make sure the vault doesn't require insane amount of confirmations
|
|
302
|
+
if (vault.getConfirmations() > this._options.maxConfirmations)
|
|
303
|
+
throw new IntermediaryError_1.IntermediaryError("SPV swap vault needs too many confirmations: " + vault.getConfirmations());
|
|
304
|
+
const tokenData = vault.getTokenData();
|
|
305
|
+
//Amounts - make sure the amounts match
|
|
306
|
+
if (amountData.exactIn) {
|
|
307
|
+
if (resp.btcAmount !== amountData.amount)
|
|
308
|
+
throw new IntermediaryError_1.IntermediaryError("Invalid amount returned");
|
|
309
|
+
}
|
|
310
|
+
else {
|
|
311
|
+
//Check the difference between amount adjusted due to scaling to raw amount
|
|
312
|
+
const adjustedAmount = amountData.amount / tokenData[0].multiplier * tokenData[0].multiplier;
|
|
313
|
+
const adjustmentPPM = (amountData.amount - adjustedAmount) * 1000000n / amountData.amount;
|
|
314
|
+
if (adjustmentPPM > this._options.maxRawAmountAdjustmentDifferencePPM)
|
|
315
|
+
throw new IntermediaryError_1.IntermediaryError("Invalid amount0 multiplier used, rawAmount diff too high");
|
|
316
|
+
if (resp.total !== adjustedAmount)
|
|
317
|
+
throw new IntermediaryError_1.IntermediaryError("Invalid total returned");
|
|
318
|
+
}
|
|
319
|
+
if (options.gasAmount === 0n) {
|
|
320
|
+
if (resp.totalGas !== 0n)
|
|
321
|
+
throw new IntermediaryError_1.IntermediaryError("Invalid gas total returned");
|
|
322
|
+
}
|
|
323
|
+
else {
|
|
324
|
+
//Check the difference between amount adjusted due to scaling to raw amount
|
|
325
|
+
const adjustedGasAmount = options.gasAmount / tokenData[0].multiplier * tokenData[0].multiplier;
|
|
326
|
+
const adjustmentPPM = (options.gasAmount - adjustedGasAmount) * 1000000n / options.gasAmount;
|
|
327
|
+
if (adjustmentPPM > this._options.maxRawAmountAdjustmentDifferencePPM)
|
|
328
|
+
throw new IntermediaryError_1.IntermediaryError("Invalid amount1 multiplier used, rawAmount diff too high");
|
|
329
|
+
if (resp.totalGas !== adjustedGasAmount)
|
|
330
|
+
throw new IntermediaryError_1.IntermediaryError("Invalid gas total returned");
|
|
331
|
+
}
|
|
332
|
+
return vault;
|
|
333
|
+
})(),
|
|
334
|
+
(async () => {
|
|
335
|
+
//Require the vault UTXO to have at least 1 confirmation
|
|
336
|
+
let btcTx = await this._btcRpc.getTransaction(txId);
|
|
337
|
+
if (btcTx == null)
|
|
338
|
+
throw new IntermediaryError_1.IntermediaryError("Invalid UTXO, doesn't exist (txId)");
|
|
339
|
+
abortController.signal.throwIfAborted();
|
|
340
|
+
if (btcTx.confirmations == null || btcTx.confirmations < 1)
|
|
341
|
+
throw new IntermediaryError_1.IntermediaryError("SPV vault UTXO not confirmed");
|
|
342
|
+
const vout = parseInt(voutStr);
|
|
343
|
+
if (btcTx.outs[vout] == null)
|
|
344
|
+
throw new IntermediaryError_1.IntermediaryError("Invalid UTXO, doesn't exist");
|
|
345
|
+
const vaultUtxoValue = btcTx.outs[vout].value;
|
|
346
|
+
return { btcTx, vaultUtxoValue };
|
|
347
|
+
})(),
|
|
348
|
+
(async () => {
|
|
349
|
+
//Require vault UTXO is unspent
|
|
350
|
+
if (await this._btcRpc.isSpent(utxo))
|
|
351
|
+
throw new IntermediaryError_1.IntermediaryError("Returned spv vault UTXO is already spent", null, true);
|
|
352
|
+
abortController.signal.throwIfAborted();
|
|
353
|
+
})()
|
|
354
|
+
]).catch(e => {
|
|
355
|
+
abortController.abort(e);
|
|
356
|
+
throw e;
|
|
357
|
+
});
|
|
358
|
+
this.logger.debug("verifyReturnedData(): Vault UTXO: " + vault.getUtxo() + " current utxo: " + utxo);
|
|
359
|
+
//Trace returned utxo back to what's saved on-chain
|
|
360
|
+
let pendingWithdrawals = [];
|
|
361
|
+
while (vault.getUtxo() !== utxo) {
|
|
362
|
+
const [txId, voutStr] = utxo.split(":");
|
|
363
|
+
//Such that 1st tx isn't fetched twice
|
|
364
|
+
if (btcTx.txid !== txId) {
|
|
365
|
+
const _btcTx = await this._btcRpc.getTransaction(txId);
|
|
366
|
+
if (_btcTx == null)
|
|
367
|
+
throw new IntermediaryError_1.IntermediaryError("Invalid ancestor transaction (not found)");
|
|
368
|
+
btcTx = _btcTx;
|
|
369
|
+
}
|
|
370
|
+
const withdrawalData = await this._contract.getWithdrawalData(btcTx);
|
|
371
|
+
abortSignal.throwIfAborted();
|
|
372
|
+
pendingWithdrawals.unshift(withdrawalData);
|
|
373
|
+
utxo = pendingWithdrawals[0].getSpentVaultUtxo();
|
|
374
|
+
this.logger.debug("verifyReturnedData(): Vault UTXO: " + vault.getUtxo() + " current utxo: " + utxo);
|
|
375
|
+
if (pendingWithdrawals.length >= this._options.maxTransactionsDelta)
|
|
376
|
+
throw new IntermediaryError_1.IntermediaryError("BTC <> SC state difference too deep, maximum: " + this._options.maxTransactionsDelta);
|
|
377
|
+
}
|
|
378
|
+
//Verify that the vault has enough balance after processing all pending withdrawals
|
|
379
|
+
let vaultBalances;
|
|
380
|
+
try {
|
|
381
|
+
vaultBalances = vault.calculateStateAfter(pendingWithdrawals);
|
|
382
|
+
}
|
|
383
|
+
catch (e) {
|
|
384
|
+
this.logger.error("Error calculating spv vault balance (owner: " + resp.address + " vaultId: " + resp.vaultId.toString(10) + "): ", e);
|
|
385
|
+
throw new IntermediaryError_1.IntermediaryError("Spv swap vault balance prediction failed", e);
|
|
386
|
+
}
|
|
387
|
+
if (vaultBalances.balances[0].scaledAmount < resp.total)
|
|
388
|
+
throw new IntermediaryError_1.IntermediaryError("SPV swap vault, insufficient balance, required: " + resp.total.toString(10) +
|
|
389
|
+
" has: " + vaultBalances.balances[0].scaledAmount.toString(10));
|
|
390
|
+
if (vaultBalances.balances[1].scaledAmount < resp.totalGas)
|
|
391
|
+
throw new IntermediaryError_1.IntermediaryError("SPV swap vault, insufficient balance, required: " + resp.totalGas.toString(10) +
|
|
392
|
+
" has: " + vaultBalances.balances[1].scaledAmount.toString(10));
|
|
393
|
+
//Also verify that all the withdrawal txns are valid, this is an extra sanity check
|
|
394
|
+
try {
|
|
395
|
+
for (let withdrawal of pendingWithdrawals) {
|
|
396
|
+
await this._contract.checkWithdrawalTx(withdrawal);
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
catch (e) {
|
|
400
|
+
this.logger.error("Error calculating spv vault balance (owner: " + resp.address + " vaultId: " + resp.vaultId.toString(10) + "): ", e);
|
|
401
|
+
throw new IntermediaryError_1.IntermediaryError("Spv swap vault balance prediction failed", e);
|
|
402
|
+
}
|
|
403
|
+
abortSignal.throwIfAborted();
|
|
404
|
+
return {
|
|
405
|
+
vault,
|
|
406
|
+
vaultUtxoValue
|
|
407
|
+
};
|
|
408
|
+
}
|
|
409
|
+
/**
|
|
410
|
+
* Returns a newly created Bitcoin -> Smart chain swap using the SPV vault (UTXO-controlled vault) swap protocol,
|
|
411
|
+
* with the passed amount. Also allows specifying additional "gas drop" native token that the receipient receives
|
|
412
|
+
* on the destination chain in the `options` argument.
|
|
413
|
+
*
|
|
414
|
+
* @param recipient Recipient address on the destination smart chain
|
|
415
|
+
* @param amountData Amount, token and exact input/output data for to swap
|
|
416
|
+
* @param lps An array of intermediaries (LPs) to get the quotes from
|
|
417
|
+
* @param options Optional additional quote options
|
|
418
|
+
* @param additionalParams Optional additional parameters sent to the LP when creating the swap
|
|
419
|
+
* @param abortSignal Abort signal
|
|
420
|
+
*/
|
|
421
|
+
create(recipient, amountData, lps, options, additionalParams, abortSignal) {
|
|
422
|
+
const _options = {
|
|
423
|
+
gasAmount: this.parseGasAmount(options?.gasAmount),
|
|
424
|
+
unsafeZeroWatchtowerFee: options?.unsafeZeroWatchtowerFee ?? false,
|
|
425
|
+
feeSafetyFactor: options?.feeSafetyFactor ?? 1.25,
|
|
426
|
+
maxAllowedBitcoinFeeRate: options?.maxAllowedBitcoinFeeRate ?? options?.maxAllowedNetworkFeeRate ?? Infinity
|
|
427
|
+
};
|
|
428
|
+
if (amountData.token === this._chain.getNativeCurrencyAddress() && _options.gasAmount !== 0n)
|
|
429
|
+
throw new UserError_1.UserError("Cannot specify `gasAmount` for swaps to a native token!");
|
|
430
|
+
const _abortController = (0, Utils_1.extendAbortController)(abortSignal);
|
|
431
|
+
const pricePrefetchPromise = this.preFetchPrice(amountData, _abortController.signal);
|
|
432
|
+
const usdPricePrefetchPromise = this.preFetchUsdPrice(_abortController.signal);
|
|
433
|
+
const finalizedBlockHeightPrefetchPromise = this.preFetchFinalizedBlockHeight(_abortController);
|
|
434
|
+
const nativeTokenAddress = this._chain.getNativeCurrencyAddress();
|
|
435
|
+
const gasTokenPricePrefetchPromise = _options.gasAmount === 0n ?
|
|
436
|
+
undefined :
|
|
437
|
+
this.preFetchPrice({ token: nativeTokenAddress }, _abortController.signal);
|
|
438
|
+
const callerFeePrefetchPromise = this.preFetchCallerFeeShare(amountData, _options, pricePrefetchPromise, gasTokenPricePrefetchPromise, _abortController);
|
|
439
|
+
const bitcoinFeeRatePromise = _options.maxAllowedBitcoinFeeRate != Infinity ?
|
|
440
|
+
Promise.resolve(_options.maxAllowedBitcoinFeeRate) :
|
|
441
|
+
this._btcRpc.getFeeRate().then(x => this._options.maxBtcFeeOffset + (x * this._options.maxBtcFeeMultiplier)).catch(e => {
|
|
442
|
+
_abortController.abort(e);
|
|
443
|
+
return undefined;
|
|
444
|
+
});
|
|
445
|
+
return lps.map(lp => {
|
|
446
|
+
return {
|
|
447
|
+
intermediary: lp,
|
|
448
|
+
quote: (0, RetryUtils_1.tryWithRetries)(async () => {
|
|
449
|
+
if (lp.services[SwapType_1.SwapType.SPV_VAULT_FROM_BTC] == null)
|
|
450
|
+
throw new Error("LP service for processing spv vault swaps not found!");
|
|
451
|
+
const abortController = (0, Utils_1.extendAbortController)(_abortController.signal);
|
|
452
|
+
try {
|
|
453
|
+
const resp = await (0, RetryUtils_1.tryWithRetries)(async (retryCount) => {
|
|
454
|
+
return await IntermediaryAPI_1.IntermediaryAPI.prepareSpvFromBTC(this.chainIdentifier, lp.url, {
|
|
455
|
+
address: recipient,
|
|
456
|
+
amount: amountData.amount,
|
|
457
|
+
token: amountData.token.toString(),
|
|
458
|
+
exactOut: !amountData.exactIn,
|
|
459
|
+
gasToken: nativeTokenAddress,
|
|
460
|
+
gasAmount: _options.gasAmount,
|
|
461
|
+
callerFeeRate: (0, Utils_1.throwIfUndefined)(callerFeePrefetchPromise, "Caller fee prefetch failed!"),
|
|
462
|
+
frontingFeeRate: 0n,
|
|
463
|
+
stickyAddress: options?.stickyAddress,
|
|
464
|
+
additionalParams
|
|
465
|
+
}, this._options.postRequestTimeout, abortController.signal, retryCount > 0 ? false : undefined);
|
|
466
|
+
}, undefined, e => e instanceof RequestError_1.RequestError, abortController.signal);
|
|
467
|
+
this.logger.debug("create(" + lp.url + "): LP response: ", resp);
|
|
468
|
+
const callerFeeShare = (await callerFeePrefetchPromise);
|
|
469
|
+
const [pricingInfo, gasPricingInfo, { vault, vaultUtxoValue }] = await Promise.all([
|
|
470
|
+
this.verifyReturnedPrice(lp.services[SwapType_1.SwapType.SPV_VAULT_FROM_BTC], false, resp.btcAmountSwap, resp.total * (100000n + callerFeeShare) / 100000n, amountData.token, { swapFeeBtc: resp.swapFeeBtc }, pricePrefetchPromise, usdPricePrefetchPromise, abortController.signal),
|
|
471
|
+
_options.gasAmount === 0n ? Promise.resolve(undefined) : this.verifyReturnedPrice({ ...lp.services[SwapType_1.SwapType.SPV_VAULT_FROM_BTC], swapBaseFee: 0 }, //Base fee should be charged only on the amount, not on gas
|
|
472
|
+
false, resp.btcAmountGas, resp.totalGas * (100000n + callerFeeShare) / 100000n, nativeTokenAddress, { swapFeeBtc: resp.gasSwapFeeBtc }, gasTokenPricePrefetchPromise, usdPricePrefetchPromise, abortController.signal),
|
|
473
|
+
this.verifyReturnedData(resp, amountData, lp, _options, callerFeeShare, bitcoinFeeRatePromise, abortController.signal)
|
|
474
|
+
]);
|
|
475
|
+
const swapInit = {
|
|
476
|
+
pricingInfo,
|
|
477
|
+
url: lp.url,
|
|
478
|
+
expiry: resp.expiry * 1000,
|
|
479
|
+
swapFee: resp.swapFee,
|
|
480
|
+
swapFeeBtc: resp.swapFeeBtc,
|
|
481
|
+
exactIn: amountData.exactIn ?? true,
|
|
482
|
+
quoteId: resp.quoteId,
|
|
483
|
+
recipient,
|
|
484
|
+
vaultOwner: resp.address,
|
|
485
|
+
vaultId: resp.vaultId,
|
|
486
|
+
vaultRequiredConfirmations: vault.getConfirmations(),
|
|
487
|
+
vaultTokenMultipliers: vault.getTokenData().map(val => val.multiplier),
|
|
488
|
+
vaultBtcAddress: resp.vaultBtcAddress,
|
|
489
|
+
vaultUtxo: resp.btcUtxo,
|
|
490
|
+
vaultUtxoValue: BigInt(vaultUtxoValue),
|
|
491
|
+
btcDestinationAddress: resp.btcAddress,
|
|
492
|
+
btcAmount: resp.btcAmount,
|
|
493
|
+
btcAmountSwap: resp.btcAmountSwap,
|
|
494
|
+
btcAmountGas: resp.btcAmountGas,
|
|
495
|
+
minimumBtcFeeRate: resp.btcFeeRate,
|
|
496
|
+
outputTotalSwap: resp.total,
|
|
497
|
+
outputSwapToken: amountData.token,
|
|
498
|
+
outputTotalGas: resp.totalGas,
|
|
499
|
+
outputGasToken: nativeTokenAddress,
|
|
500
|
+
gasSwapFeeBtc: resp.gasSwapFeeBtc,
|
|
501
|
+
gasSwapFee: resp.gasSwapFee,
|
|
502
|
+
gasPricingInfo,
|
|
503
|
+
callerFeeShare: resp.callerFeeShare,
|
|
504
|
+
frontingFeeShare: resp.frontingFeeShare,
|
|
505
|
+
executionFeeShare: resp.executionFeeShare,
|
|
506
|
+
genesisSmartChainBlockHeight: await (0, Utils_1.throwIfUndefined)(finalizedBlockHeightPrefetchPromise, "Finalize block height promise failed!")
|
|
507
|
+
};
|
|
508
|
+
const quote = new SpvFromBTCSwap_1.SpvFromBTCSwap(this, swapInit);
|
|
509
|
+
return quote;
|
|
510
|
+
}
|
|
511
|
+
catch (e) {
|
|
512
|
+
abortController.abort(e);
|
|
513
|
+
throw e;
|
|
514
|
+
}
|
|
515
|
+
}, undefined, err => !(err instanceof IntermediaryError_1.IntermediaryError && err.recoverable), _abortController.signal)
|
|
516
|
+
};
|
|
517
|
+
});
|
|
518
|
+
}
|
|
519
|
+
/**
|
|
520
|
+
* Recovers an SPV vault (UTXO-controlled vault) based swap from smart chain on-chain data
|
|
521
|
+
*
|
|
522
|
+
* @param state State of the spv vault withdrawal recovered from on-chain data
|
|
523
|
+
* @param vault SPV vault processing the swap
|
|
524
|
+
* @param lp Intermediary (LP) used as a counterparty for the swap
|
|
525
|
+
*/
|
|
526
|
+
async recoverFromState(state, vault, lp) {
|
|
527
|
+
//Get the vault
|
|
528
|
+
vault ??= await this._contract.getVaultData(state.owner, state.vaultId);
|
|
529
|
+
if (vault == null)
|
|
530
|
+
return null;
|
|
531
|
+
if (state.btcTxId == null)
|
|
532
|
+
return null;
|
|
533
|
+
const btcTx = await this._btcRpc.getTransaction(state.btcTxId);
|
|
534
|
+
if (btcTx == null)
|
|
535
|
+
return null;
|
|
536
|
+
const withdrawalData = await this._contract.getWithdrawalData(btcTx)
|
|
537
|
+
.catch(e => {
|
|
538
|
+
this.logger.warn(`Error parsing withdrawal data for tx ${btcTx.txid}: `, e);
|
|
539
|
+
return null;
|
|
540
|
+
});
|
|
541
|
+
if (withdrawalData == null)
|
|
542
|
+
return null;
|
|
543
|
+
const vaultTokens = vault.getTokenData();
|
|
544
|
+
const withdrawalDataOutputs = withdrawalData.getTotalOutput();
|
|
545
|
+
const txBlock = await state.getTxBlock?.();
|
|
546
|
+
const swapInit = {
|
|
547
|
+
pricingInfo: {
|
|
548
|
+
isValid: true,
|
|
549
|
+
satsBaseFee: 0n,
|
|
550
|
+
swapPriceUSatPerToken: 100000000000000n,
|
|
551
|
+
realPriceUSatPerToken: 100000000000000n,
|
|
552
|
+
differencePPM: 0n,
|
|
553
|
+
feePPM: 0n,
|
|
554
|
+
},
|
|
555
|
+
url: lp?.url,
|
|
556
|
+
expiry: 0,
|
|
557
|
+
swapFee: 0n,
|
|
558
|
+
swapFeeBtc: 0n,
|
|
559
|
+
exactIn: true,
|
|
560
|
+
//Use bitcoin tx id as quote id, even though this is not strictly correct as this
|
|
561
|
+
// is an off-chain identifier presented by the LP that cannot be recovered from on-chain
|
|
562
|
+
// data
|
|
563
|
+
quoteId: btcTx.txid,
|
|
564
|
+
recipient: state.recipient,
|
|
565
|
+
vaultOwner: state.owner,
|
|
566
|
+
vaultId: state.vaultId,
|
|
567
|
+
vaultRequiredConfirmations: vault.getConfirmations(),
|
|
568
|
+
vaultTokenMultipliers: vault.getTokenData().map(val => val.multiplier),
|
|
569
|
+
vaultBtcAddress: (0, BitcoinUtils_1.fromOutputScript)(this._options.bitcoinNetwork, withdrawalData.getNewVaultScript().toString("hex")),
|
|
570
|
+
vaultUtxo: withdrawalData.getSpentVaultUtxo(),
|
|
571
|
+
vaultUtxoValue: BigInt(withdrawalData.getNewVaultBtcAmount()),
|
|
572
|
+
btcDestinationAddress: (0, BitcoinUtils_1.fromOutputScript)(this._options.bitcoinNetwork, btcTx.outs[2].scriptPubKey.hex),
|
|
573
|
+
btcAmount: BigInt(btcTx.outs[2].value),
|
|
574
|
+
btcAmountSwap: BigInt(btcTx.outs[2].value),
|
|
575
|
+
btcAmountGas: 0n,
|
|
576
|
+
minimumBtcFeeRate: 0,
|
|
577
|
+
outputTotalSwap: withdrawalDataOutputs[0] * vaultTokens[0].multiplier,
|
|
578
|
+
outputSwapToken: vaultTokens[0].token,
|
|
579
|
+
outputTotalGas: withdrawalDataOutputs[1] * vaultTokens[1].multiplier,
|
|
580
|
+
outputGasToken: vaultTokens[1].token,
|
|
581
|
+
gasSwapFeeBtc: 0n,
|
|
582
|
+
gasSwapFee: 0n,
|
|
583
|
+
gasPricingInfo: {
|
|
584
|
+
isValid: true,
|
|
585
|
+
satsBaseFee: 0n,
|
|
586
|
+
swapPriceUSatPerToken: 100000000000000n,
|
|
587
|
+
realPriceUSatPerToken: 100000000000000n,
|
|
588
|
+
differencePPM: 0n,
|
|
589
|
+
feePPM: 0n,
|
|
590
|
+
},
|
|
591
|
+
callerFeeShare: withdrawalData.callerFeeRate,
|
|
592
|
+
frontingFeeShare: withdrawalData.frontingFeeRate,
|
|
593
|
+
executionFeeShare: withdrawalData.executionFeeRate,
|
|
594
|
+
genesisSmartChainBlockHeight: txBlock?.blockHeight ?? 0
|
|
595
|
+
};
|
|
596
|
+
const quote = new SpvFromBTCSwap_1.SpvFromBTCSwap(this, swapInit);
|
|
597
|
+
quote._data = withdrawalData;
|
|
598
|
+
if (txBlock != null) {
|
|
599
|
+
quote.createdAt = txBlock.blockTime * 1000;
|
|
600
|
+
}
|
|
601
|
+
else if (btcTx.blockhash == null) {
|
|
602
|
+
quote.createdAt = Date.now();
|
|
603
|
+
}
|
|
604
|
+
else {
|
|
605
|
+
const blockHeader = await this._btcRpc.getBlockHeader(btcTx.blockhash);
|
|
606
|
+
quote.createdAt = blockHeader == null ? Date.now() : blockHeader.getTimestamp() * 1000;
|
|
607
|
+
}
|
|
608
|
+
quote._setInitiated();
|
|
609
|
+
if (btcTx.inputAddresses != null)
|
|
610
|
+
quote._senderAddress = btcTx.inputAddresses[1];
|
|
611
|
+
if (state.type === base_1.SpvWithdrawalStateType.FRONTED) {
|
|
612
|
+
quote._frontTxId = state.txId;
|
|
613
|
+
quote._state = SpvFromBTCSwap_1.SpvFromBTCSwapState.FRONTED;
|
|
614
|
+
}
|
|
615
|
+
else {
|
|
616
|
+
quote._claimTxId = state.txId;
|
|
617
|
+
quote._state = SpvFromBTCSwap_1.SpvFromBTCSwapState.CLAIMED;
|
|
618
|
+
}
|
|
619
|
+
await quote._save();
|
|
620
|
+
return quote;
|
|
621
|
+
}
|
|
622
|
+
/**
|
|
623
|
+
* Returns a random dummy PSBT that can be used for fee estimation, the last output (the LP output) is omitted
|
|
624
|
+
* to allow for coinselection algorithm to determine maximum sendable amount there
|
|
625
|
+
*
|
|
626
|
+
* @param includeGasToken Whether to return the PSBT also with the gas token amount (increases the vSize by 8)
|
|
627
|
+
*/
|
|
628
|
+
getDummySwapPsbt(includeGasToken = false) {
|
|
629
|
+
//Construct dummy swap psbt
|
|
630
|
+
const psbt = new btc_signer_1.Transaction({
|
|
631
|
+
allowUnknownInputs: true,
|
|
632
|
+
allowLegacyWitnessUtxo: true,
|
|
633
|
+
allowUnknownOutputs: true
|
|
634
|
+
});
|
|
635
|
+
const randomVaultOutScript = btc_signer_1.OutScript.encode({ type: "tr", pubkey: Buffer.from("0101010101010101010101010101010101010101010101010101010101010101", "hex") });
|
|
636
|
+
psbt.addInput({
|
|
637
|
+
txid: (0, Utils_1.randomBytes)(32),
|
|
638
|
+
index: 0,
|
|
639
|
+
witnessUtxo: {
|
|
640
|
+
script: randomVaultOutScript,
|
|
641
|
+
amount: 600n
|
|
642
|
+
}
|
|
643
|
+
});
|
|
644
|
+
psbt.addOutput({
|
|
645
|
+
script: randomVaultOutScript,
|
|
646
|
+
amount: 600n
|
|
647
|
+
});
|
|
648
|
+
const opReturnData = this._contract.toOpReturnData(this._chain.randomAddress(), includeGasToken ? [0xffffffffffffffffn, 0xffffffffffffffffn] : [0xffffffffffffffffn]);
|
|
649
|
+
psbt.addOutput({
|
|
650
|
+
script: Buffer.concat([
|
|
651
|
+
opReturnData.length <= 75 ? Buffer.from([0x6a, opReturnData.length]) : Buffer.from([0x6a, 0x4c, opReturnData.length]),
|
|
652
|
+
opReturnData
|
|
653
|
+
]),
|
|
654
|
+
amount: 0n
|
|
655
|
+
});
|
|
656
|
+
return psbt;
|
|
657
|
+
}
|
|
658
|
+
/**
|
|
659
|
+
* @inheritDoc
|
|
660
|
+
* @internal
|
|
661
|
+
*/
|
|
662
|
+
async _checkPastSwaps(pastSwaps) {
|
|
663
|
+
const changedSwaps = new Set();
|
|
664
|
+
const removeSwaps = [];
|
|
665
|
+
const broadcastedOrConfirmedSwaps = [];
|
|
666
|
+
for (let pastSwap of pastSwaps) {
|
|
667
|
+
let changed = false;
|
|
668
|
+
if (pastSwap._state === SpvFromBTCSwap_1.SpvFromBTCSwapState.SIGNED ||
|
|
669
|
+
pastSwap._state === SpvFromBTCSwap_1.SpvFromBTCSwapState.POSTED ||
|
|
670
|
+
pastSwap._state === SpvFromBTCSwap_1.SpvFromBTCSwapState.BROADCASTED ||
|
|
671
|
+
pastSwap._state === SpvFromBTCSwap_1.SpvFromBTCSwapState.QUOTE_SOFT_EXPIRED ||
|
|
672
|
+
pastSwap._state === SpvFromBTCSwap_1.SpvFromBTCSwapState.DECLINED ||
|
|
673
|
+
pastSwap._state === SpvFromBTCSwap_1.SpvFromBTCSwapState.BTC_TX_CONFIRMED) {
|
|
674
|
+
//Check BTC transaction
|
|
675
|
+
if (await pastSwap._syncStateFromBitcoin(false))
|
|
676
|
+
changed ||= true;
|
|
677
|
+
}
|
|
678
|
+
if (pastSwap._state === SpvFromBTCSwap_1.SpvFromBTCSwapState.CREATED ||
|
|
679
|
+
pastSwap._state === SpvFromBTCSwap_1.SpvFromBTCSwapState.SIGNED ||
|
|
680
|
+
pastSwap._state === SpvFromBTCSwap_1.SpvFromBTCSwapState.POSTED) {
|
|
681
|
+
if (await pastSwap._verifyQuoteDefinitelyExpired()) {
|
|
682
|
+
if (pastSwap._state === SpvFromBTCSwap_1.SpvFromBTCSwapState.CREATED) {
|
|
683
|
+
pastSwap._state = SpvFromBTCSwap_1.SpvFromBTCSwapState.QUOTE_EXPIRED;
|
|
684
|
+
}
|
|
685
|
+
else {
|
|
686
|
+
pastSwap._state = SpvFromBTCSwap_1.SpvFromBTCSwapState.QUOTE_SOFT_EXPIRED;
|
|
687
|
+
}
|
|
688
|
+
changed ||= true;
|
|
689
|
+
}
|
|
690
|
+
}
|
|
691
|
+
if (pastSwap.isQuoteExpired()) {
|
|
692
|
+
removeSwaps.push(pastSwap);
|
|
693
|
+
continue;
|
|
694
|
+
}
|
|
695
|
+
if (changed)
|
|
696
|
+
changedSwaps.add(pastSwap);
|
|
697
|
+
if (pastSwap._state === SpvFromBTCSwap_1.SpvFromBTCSwapState.BROADCASTED || pastSwap._state === SpvFromBTCSwap_1.SpvFromBTCSwapState.BTC_TX_CONFIRMED) {
|
|
698
|
+
if (pastSwap._data != null)
|
|
699
|
+
broadcastedOrConfirmedSwaps.push(pastSwap);
|
|
700
|
+
}
|
|
701
|
+
}
|
|
702
|
+
const checkWithdrawalStateSwaps = [];
|
|
703
|
+
const _fronts = await this._contract.getFronterAddresses(broadcastedOrConfirmedSwaps.map(val => ({
|
|
704
|
+
...val.getSpvVaultData(),
|
|
705
|
+
withdrawal: val._data
|
|
706
|
+
})));
|
|
707
|
+
const _vaultUtxos = await this._contract.getVaultLatestUtxos(broadcastedOrConfirmedSwaps.map(val => val.getSpvVaultData()));
|
|
708
|
+
for (const pastSwap of broadcastedOrConfirmedSwaps) {
|
|
709
|
+
const fronterAddress = _fronts[pastSwap._data.getTxId()];
|
|
710
|
+
const vault = pastSwap.getSpvVaultData();
|
|
711
|
+
const latestVaultUtxo = _vaultUtxos[vault.owner]?.[vault.vaultId.toString(10)];
|
|
712
|
+
if (fronterAddress === undefined)
|
|
713
|
+
this.logger.warn(`_checkPastSwaps(): No fronter address returned for ${pastSwap._data.getTxId()}`);
|
|
714
|
+
if (latestVaultUtxo === undefined)
|
|
715
|
+
this.logger.warn(`_checkPastSwaps(): No last vault utxo returned for ${pastSwap._data.getTxId()}`);
|
|
716
|
+
if (await pastSwap._shouldCheckWithdrawalState(fronterAddress, latestVaultUtxo))
|
|
717
|
+
checkWithdrawalStateSwaps.push(pastSwap);
|
|
718
|
+
}
|
|
719
|
+
const withdrawalStates = await this._contract.getWithdrawalStates(checkWithdrawalStateSwaps.map(val => ({
|
|
720
|
+
withdrawal: val._data,
|
|
721
|
+
scStartBlockheight: val._genesisSmartChainBlockHeight
|
|
722
|
+
})));
|
|
723
|
+
for (const pastSwap of checkWithdrawalStateSwaps) {
|
|
724
|
+
const status = withdrawalStates[pastSwap._data.getTxId()];
|
|
725
|
+
if (status == null) {
|
|
726
|
+
this.logger.warn(`_checkPastSwaps(): No withdrawal state returned for ${pastSwap._data.getTxId()}`);
|
|
727
|
+
continue;
|
|
728
|
+
}
|
|
729
|
+
this.logger.debug("syncStateFromChain(): status of " + pastSwap._data.btcTx.txid, status?.type);
|
|
730
|
+
let changed = false;
|
|
731
|
+
switch (status.type) {
|
|
732
|
+
case base_1.SpvWithdrawalStateType.FRONTED:
|
|
733
|
+
pastSwap._frontTxId = status.txId;
|
|
734
|
+
pastSwap._state = SpvFromBTCSwap_1.SpvFromBTCSwapState.FRONTED;
|
|
735
|
+
changed ||= true;
|
|
736
|
+
break;
|
|
737
|
+
case base_1.SpvWithdrawalStateType.CLAIMED:
|
|
738
|
+
pastSwap._claimTxId = status.txId;
|
|
739
|
+
pastSwap._state = SpvFromBTCSwap_1.SpvFromBTCSwapState.CLAIMED;
|
|
740
|
+
changed ||= true;
|
|
741
|
+
break;
|
|
742
|
+
case base_1.SpvWithdrawalStateType.CLOSED:
|
|
743
|
+
pastSwap._state = SpvFromBTCSwap_1.SpvFromBTCSwapState.CLOSED;
|
|
744
|
+
changed ||= true;
|
|
745
|
+
break;
|
|
746
|
+
}
|
|
747
|
+
if (changed)
|
|
748
|
+
changedSwaps.add(pastSwap);
|
|
749
|
+
}
|
|
750
|
+
return {
|
|
751
|
+
changedSwaps: Array.from(changedSwaps),
|
|
752
|
+
removeSwaps
|
|
753
|
+
};
|
|
754
|
+
}
|
|
755
|
+
}
|
|
756
|
+
exports.SpvFromBTCWrapper = SpvFromBTCWrapper;
|