@atomiqlabs/sdk 8.6.6 → 8.6.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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 +190 -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
- package/src/bitcoin/wallet/SingleAddressBitcoinWallet.ts +14 -2
|
@@ -1,1126 +1,1126 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.FromBTCSwap = exports.FromBTCSwapState = void 0;
|
|
4
|
-
|
|
5
|
-
const
|
|
6
|
-
const
|
|
7
|
-
const
|
|
8
|
-
const
|
|
9
|
-
const
|
|
10
|
-
const
|
|
11
|
-
const
|
|
12
|
-
const
|
|
13
|
-
const
|
|
14
|
-
const
|
|
15
|
-
const
|
|
16
|
-
const
|
|
17
|
-
const
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
*
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
*
|
|
33
|
-
* the
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
*
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
*
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
*
|
|
53
|
-
*
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
*
|
|
59
|
-
*
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
[FromBTCSwapState.
|
|
70
|
-
[FromBTCSwapState.
|
|
71
|
-
[FromBTCSwapState.
|
|
72
|
-
[FromBTCSwapState.
|
|
73
|
-
[FromBTCSwapState.
|
|
74
|
-
[FromBTCSwapState.
|
|
75
|
-
[FromBTCSwapState.
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
(obj.
|
|
81
|
-
(obj.
|
|
82
|
-
(
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
/**
|
|
86
|
-
* Legacy escrow (PrTLC) based swap for Bitcoin -> Smart chains, requires manual initiation
|
|
87
|
-
* of the swap escrow on the destination chain.
|
|
88
|
-
*
|
|
89
|
-
* @category Swaps/Legacy/Bitcoin → Smart chain
|
|
90
|
-
*/
|
|
91
|
-
class FromBTCSwap extends IFromBTCSelfInitSwap_1.IFromBTCSelfInitSwap {
|
|
92
|
-
constructor(wrapper, initOrObject) {
|
|
93
|
-
if (isFromBTCSwapInit(initOrObject) && initOrObject.url != null)
|
|
94
|
-
initOrObject.url += "/frombtc";
|
|
95
|
-
super(wrapper, initOrObject);
|
|
96
|
-
this.TYPE = SwapType_1.SwapType.FROM_BTC;
|
|
97
|
-
/**
|
|
98
|
-
* @internal
|
|
99
|
-
*/
|
|
100
|
-
this.swapStateName = (state) => FromBTCSwapState[state];
|
|
101
|
-
/**
|
|
102
|
-
* @internal
|
|
103
|
-
*/
|
|
104
|
-
this.swapStateDescription = FromBTCSwapStateDescription;
|
|
105
|
-
/**
|
|
106
|
-
* @internal
|
|
107
|
-
*/
|
|
108
|
-
this.inputToken = Token_1.BitcoinTokens.BTC;
|
|
109
|
-
if (isFromBTCSwapInit(initOrObject)) {
|
|
110
|
-
this._state = FromBTCSwapState.PR_CREATED;
|
|
111
|
-
this._data = initOrObject.data;
|
|
112
|
-
this.feeRate = initOrObject.feeRate;
|
|
113
|
-
this.address = initOrObject.address;
|
|
114
|
-
this.amount = initOrObject.amount;
|
|
115
|
-
this.requiredConfirmations = initOrObject.requiredConfirmations;
|
|
116
|
-
}
|
|
117
|
-
else {
|
|
118
|
-
this.address = initOrObject.address;
|
|
119
|
-
this.amount = (0, Utils_1.toBigInt)(initOrObject.amount);
|
|
120
|
-
this.senderAddress = initOrObject.senderAddress;
|
|
121
|
-
this.txId = initOrObject.txId;
|
|
122
|
-
this.vout = initOrObject.vout;
|
|
123
|
-
this.requiredConfirmations = initOrObject.requiredConfirmations ?? this._data.getConfirmationsHint();
|
|
124
|
-
this.btcTxConfirmedAt = initOrObject.btcTxConfirmedAt;
|
|
125
|
-
}
|
|
126
|
-
this.tryRecomputeSwapPrice();
|
|
127
|
-
this.logger = (0, Logger_1.getLogger)("FromBTC(" + this.getIdentifierHashString() + "): ");
|
|
128
|
-
}
|
|
129
|
-
/**
|
|
130
|
-
* @inheritDoc
|
|
131
|
-
* @internal
|
|
132
|
-
*/
|
|
133
|
-
getSwapData() {
|
|
134
|
-
return this._data;
|
|
135
|
-
}
|
|
136
|
-
/**
|
|
137
|
-
* @inheritDoc
|
|
138
|
-
* @internal
|
|
139
|
-
*/
|
|
140
|
-
upgradeVersion() {
|
|
141
|
-
if (this.version == null) {
|
|
142
|
-
switch (this._state) {
|
|
143
|
-
case -2:
|
|
144
|
-
this._state = FromBTCSwapState.FAILED;
|
|
145
|
-
break;
|
|
146
|
-
case -1:
|
|
147
|
-
this._state = FromBTCSwapState.QUOTE_EXPIRED;
|
|
148
|
-
break;
|
|
149
|
-
case 0:
|
|
150
|
-
this._state = FromBTCSwapState.PR_CREATED;
|
|
151
|
-
break;
|
|
152
|
-
case 1:
|
|
153
|
-
this._state = FromBTCSwapState.CLAIM_COMMITED;
|
|
154
|
-
break;
|
|
155
|
-
case 2:
|
|
156
|
-
this._state = FromBTCSwapState.BTC_TX_CONFIRMED;
|
|
157
|
-
break;
|
|
158
|
-
case 3:
|
|
159
|
-
this._state = FromBTCSwapState.CLAIM_CLAIMED;
|
|
160
|
-
break;
|
|
161
|
-
}
|
|
162
|
-
this.version = 1;
|
|
163
|
-
}
|
|
164
|
-
}
|
|
165
|
-
//////////////////////////////
|
|
166
|
-
//// Getters & utils
|
|
167
|
-
/**
|
|
168
|
-
* Returns bitcoin address where the on-chain BTC should be sent to
|
|
169
|
-
*/
|
|
170
|
-
getAddress() {
|
|
171
|
-
if (this._state === FromBTCSwapState.PR_CREATED)
|
|
172
|
-
throw new Error("Cannot get bitcoin address of non-initiated swaps! Initiate swap first with commit() or txsCommit().");
|
|
173
|
-
return this.address ?? "";
|
|
174
|
-
}
|
|
175
|
-
/**
|
|
176
|
-
* Unsafe bitcoin hyperlink getter, returns the address even before the swap is committed!
|
|
177
|
-
*
|
|
178
|
-
* @private
|
|
179
|
-
*/
|
|
180
|
-
_getHyperlink() {
|
|
181
|
-
return this.address == null || this.amount == null ? "" : "bitcoin:" + this.address + "?amount=" + encodeURIComponent((Number(this.amount) / 100000000).toString(10));
|
|
182
|
-
}
|
|
183
|
-
/**
|
|
184
|
-
* @inheritDoc
|
|
185
|
-
*/
|
|
186
|
-
getHyperlink() {
|
|
187
|
-
if (this._state === FromBTCSwapState.PR_CREATED)
|
|
188
|
-
throw new Error("Cannot get bitcoin address of non-initiated swaps! Initiate swap first with commit() or txsCommit().");
|
|
189
|
-
return this._getHyperlink();
|
|
190
|
-
}
|
|
191
|
-
/**
|
|
192
|
-
* @inheritDoc
|
|
193
|
-
*/
|
|
194
|
-
getInputAddress() {
|
|
195
|
-
return this.senderAddress ?? null;
|
|
196
|
-
}
|
|
197
|
-
/**
|
|
198
|
-
* @inheritDoc
|
|
199
|
-
*/
|
|
200
|
-
getInputTxId() {
|
|
201
|
-
return this.txId ?? null;
|
|
202
|
-
}
|
|
203
|
-
/**
|
|
204
|
-
* Returns timeout time (in UNIX milliseconds) when the on-chain address will expire and no funds should be sent
|
|
205
|
-
* to that address anymore
|
|
206
|
-
*/
|
|
207
|
-
getTimeoutTime() {
|
|
208
|
-
return Number(this.wrapper._getOnchainSendTimeout(this._data, this.requiredConfirmations ?? 6)) * 1000;
|
|
209
|
-
}
|
|
210
|
-
/**
|
|
211
|
-
* @inheritDoc
|
|
212
|
-
*/
|
|
213
|
-
requiresAction() {
|
|
214
|
-
return this.isClaimable() || (this._state === FromBTCSwapState.CLAIM_COMMITED && this.getTimeoutTime() > Date.now() && this.txId == null);
|
|
215
|
-
}
|
|
216
|
-
/**
|
|
217
|
-
* @inheritDoc
|
|
218
|
-
*/
|
|
219
|
-
isFinished() {
|
|
220
|
-
return this._state === FromBTCSwapState.CLAIM_CLAIMED || this._state === FromBTCSwapState.QUOTE_EXPIRED || this._state === FromBTCSwapState.FAILED;
|
|
221
|
-
}
|
|
222
|
-
/**
|
|
223
|
-
* @inheritDoc
|
|
224
|
-
*/
|
|
225
|
-
isClaimable() {
|
|
226
|
-
return this._state === FromBTCSwapState.BTC_TX_CONFIRMED;
|
|
227
|
-
}
|
|
228
|
-
/**
|
|
229
|
-
* @inheritDoc
|
|
230
|
-
*/
|
|
231
|
-
isSuccessful() {
|
|
232
|
-
return this._state === FromBTCSwapState.CLAIM_CLAIMED;
|
|
233
|
-
}
|
|
234
|
-
/**
|
|
235
|
-
* @inheritDoc
|
|
236
|
-
*/
|
|
237
|
-
isFailed() {
|
|
238
|
-
return this._state === FromBTCSwapState.FAILED || this._state === FromBTCSwapState.EXPIRED;
|
|
239
|
-
}
|
|
240
|
-
/**
|
|
241
|
-
* @inheritDoc
|
|
242
|
-
*/
|
|
243
|
-
isInProgress() {
|
|
244
|
-
return this._state === FromBTCSwapState.CLAIM_COMMITED ||
|
|
245
|
-
this._state === FromBTCSwapState.BTC_TX_CONFIRMED;
|
|
246
|
-
}
|
|
247
|
-
/**
|
|
248
|
-
* @inheritDoc
|
|
249
|
-
*/
|
|
250
|
-
isQuoteExpired() {
|
|
251
|
-
return this._state === FromBTCSwapState.QUOTE_EXPIRED;
|
|
252
|
-
}
|
|
253
|
-
/**
|
|
254
|
-
* @inheritDoc
|
|
255
|
-
*/
|
|
256
|
-
isQuoteSoftExpired() {
|
|
257
|
-
return this._state === FromBTCSwapState.QUOTE_EXPIRED || this._state === FromBTCSwapState.QUOTE_SOFT_EXPIRED;
|
|
258
|
-
}
|
|
259
|
-
/**
|
|
260
|
-
* @inheritDoc
|
|
261
|
-
* @internal
|
|
262
|
-
*/
|
|
263
|
-
canCommit(skipQuoteExpiryChecks) {
|
|
264
|
-
if (this._state !== FromBTCSwapState.PR_CREATED && (!skipQuoteExpiryChecks || this._state !== FromBTCSwapState.QUOTE_SOFT_EXPIRED))
|
|
265
|
-
return false;
|
|
266
|
-
if (this.requiredConfirmations == null)
|
|
267
|
-
return false;
|
|
268
|
-
const expiry = this.wrapper._getOnchainSendTimeout(this._data, this.requiredConfirmations);
|
|
269
|
-
const currentTimestamp = BigInt(Math.floor(Date.now() / 1000));
|
|
270
|
-
return (expiry - currentTimestamp) >= this.wrapper._options.minSendWindow;
|
|
271
|
-
}
|
|
272
|
-
//////////////////////////////
|
|
273
|
-
//// Amounts & fees
|
|
274
|
-
/**
|
|
275
|
-
* @inheritDoc
|
|
276
|
-
*/
|
|
277
|
-
getInputToken() {
|
|
278
|
-
return Token_1.BitcoinTokens.BTC;
|
|
279
|
-
}
|
|
280
|
-
/**
|
|
281
|
-
* @inheritDoc
|
|
282
|
-
*/
|
|
283
|
-
getInput() {
|
|
284
|
-
return (0, TokenAmount_1.toTokenAmount)(this.amount ?? null, this.inputToken, this.wrapper._prices);
|
|
285
|
-
}
|
|
286
|
-
/**
|
|
287
|
-
* Returns claimer bounty, acting as a reward for watchtowers to claim the swap automatically,
|
|
288
|
-
* this amount is pre-funded by the user on the destination chain when the swap escrow
|
|
289
|
-
* is initiated. For total pre-funded deposit amount see {@link getTotalDeposit}.
|
|
290
|
-
*/
|
|
291
|
-
getClaimerBounty() {
|
|
292
|
-
return (0, TokenAmount_1.toTokenAmount)(this._data.getClaimerBounty(), this.wrapper._tokens[this._data.getDepositToken()], this.wrapper._prices);
|
|
293
|
-
}
|
|
294
|
-
//////////////////////////////
|
|
295
|
-
//// Bitcoin tx
|
|
296
|
-
/**
|
|
297
|
-
* If the required number of confirmations is not known, this function tries to infer it by looping through
|
|
298
|
-
* possible confirmation targets and comparing the claim hashes
|
|
299
|
-
*
|
|
300
|
-
* @param btcTx Bitcoin transaction
|
|
301
|
-
* @param vout Output index of the desired output in the bitcoin transaction
|
|
302
|
-
*
|
|
303
|
-
* @private
|
|
304
|
-
*/
|
|
305
|
-
inferRequiredConfirmationsCount(btcTx, vout) {
|
|
306
|
-
const txOut = btcTx.outs[vout];
|
|
307
|
-
for (let i = 1; i <= 20; i++) {
|
|
308
|
-
const computedClaimHash = this.wrapper._contract.getHashForOnchain(buffer_1.Buffer.from(txOut.scriptPubKey.hex, "hex"), BigInt(txOut.value), i);
|
|
309
|
-
if (computedClaimHash.toString("hex") === this._data.getClaimHash()) {
|
|
310
|
-
return i;
|
|
311
|
-
}
|
|
312
|
-
}
|
|
313
|
-
}
|
|
314
|
-
/**
|
|
315
|
-
* @inheritDoc
|
|
316
|
-
*/
|
|
317
|
-
getRequiredConfirmationsCount() {
|
|
318
|
-
return this.requiredConfirmations ?? NaN;
|
|
319
|
-
}
|
|
320
|
-
/**
|
|
321
|
-
* Checks whether a bitcoin payment was already made, returns the payment or `null` when no payment has been made.
|
|
322
|
-
*
|
|
323
|
-
* @internal
|
|
324
|
-
*/
|
|
325
|
-
async getBitcoinPayment() {
|
|
326
|
-
const txoHashHint = this._data.getTxoHashHint();
|
|
327
|
-
if (txoHashHint == null)
|
|
328
|
-
throw new Error("Swap data doesn't include the txo hash hint! Cannot check bitcoin transaction!");
|
|
329
|
-
if (this.address == null)
|
|
330
|
-
throw new Error("Cannot check bitcoin payment, because the address is not known! This can happen after a swap is recovered.");
|
|
331
|
-
const result = await this.wrapper._btcRpc.checkAddressTxos(this.address, buffer_1.Buffer.from(txoHashHint, "hex"));
|
|
332
|
-
if (result == null)
|
|
333
|
-
return null;
|
|
334
|
-
if (this.requiredConfirmations == null) {
|
|
335
|
-
this.requiredConfirmations = this.inferRequiredConfirmationsCount(result.tx, result.vout);
|
|
336
|
-
}
|
|
337
|
-
return {
|
|
338
|
-
inputAddresses: result.tx.inputAddresses,
|
|
339
|
-
txId: result.tx.txid,
|
|
340
|
-
vout: result.vout,
|
|
341
|
-
confirmations: result.tx.confirmations ?? 0,
|
|
342
|
-
targetConfirmations: this.getRequiredConfirmationsCount()
|
|
343
|
-
};
|
|
344
|
-
}
|
|
345
|
-
/**
|
|
346
|
-
* Used to set the txId of the bitcoin payment from the on-chain events listener
|
|
347
|
-
*
|
|
348
|
-
* @param txId Transaction ID that settled the swap on the smart chain
|
|
349
|
-
*
|
|
350
|
-
* @internal
|
|
351
|
-
*/
|
|
352
|
-
async _setBitcoinTxId(txId) {
|
|
353
|
-
if (this.txId !== txId || this.address == null || this.vout == null || this.senderAddress == null || this.amount == null) {
|
|
354
|
-
const btcTx = await this.wrapper._btcRpc.getTransaction(txId);
|
|
355
|
-
if (btcTx == null)
|
|
356
|
-
return;
|
|
357
|
-
const txoHashHint = this._data.getTxoHashHint();
|
|
358
|
-
if (txoHashHint != null) {
|
|
359
|
-
const expectedTxoHash = buffer_1.Buffer.from(txoHashHint, "hex");
|
|
360
|
-
const vout = btcTx.outs.findIndex(out => (0, Utils_1.getTxoHash)(out.scriptPubKey.hex, out.value).equals(expectedTxoHash));
|
|
361
|
-
if (vout !== -1) {
|
|
362
|
-
this.vout = vout;
|
|
363
|
-
//If amount or address are not known, parse them from the bitcoin tx
|
|
364
|
-
// this can happen if the swap is recovered from on-chain data and
|
|
365
|
-
// hence doesn't contain the address and amount data
|
|
366
|
-
if (this.amount == null)
|
|
367
|
-
this.amount = BigInt(btcTx.outs[vout].value);
|
|
368
|
-
if (this.address == null)
|
|
369
|
-
try {
|
|
370
|
-
this.address = (0, BitcoinUtils_1.fromOutputScript)(this.wrapper._options.bitcoinNetwork, btcTx.outs[vout].scriptPubKey.hex);
|
|
371
|
-
}
|
|
372
|
-
catch (e) {
|
|
373
|
-
this.logger.warn("_setBitcoinTxId(): Failed to parse address from output script: ", e);
|
|
374
|
-
}
|
|
375
|
-
if (this.requiredConfirmations == null) {
|
|
376
|
-
this.requiredConfirmations = this.inferRequiredConfirmationsCount(btcTx, vout);
|
|
377
|
-
}
|
|
378
|
-
}
|
|
379
|
-
}
|
|
380
|
-
if (btcTx.inputAddresses != null) {
|
|
381
|
-
this.senderAddress = btcTx.inputAddresses[0];
|
|
382
|
-
}
|
|
383
|
-
}
|
|
384
|
-
this.txId = txId;
|
|
385
|
-
}
|
|
386
|
-
/**
|
|
387
|
-
* @inheritDoc
|
|
388
|
-
*
|
|
389
|
-
* @throws {Error} if in invalid state (must be {@link FromBTCSwapState.CLAIM_COMMITED})
|
|
390
|
-
*/
|
|
391
|
-
async waitForBitcoinTransaction(updateCallback, checkIntervalSeconds, abortSignal) {
|
|
392
|
-
if (this._state !== FromBTCSwapState.CLAIM_COMMITED && this._state !== FromBTCSwapState.EXPIRED)
|
|
393
|
-
throw new Error("Must be in COMMITED state!");
|
|
394
|
-
const txoHashHint = this._data.getTxoHashHint();
|
|
395
|
-
if (txoHashHint == null)
|
|
396
|
-
throw new Error("Swap data doesn't include the txo hash hint! Cannot check bitcoin transaction!");
|
|
397
|
-
if (this.address == null)
|
|
398
|
-
throw new Error("Cannot check bitcoin payment, because the address is not known! This can happen after a swap is recovered.");
|
|
399
|
-
let abortedDueToEnoughConfirmationsResult;
|
|
400
|
-
const abortController = (0, Utils_1.extendAbortController)(abortSignal);
|
|
401
|
-
const result = await this.wrapper._btcRpc.waitForAddressTxo(this.address, buffer_1.Buffer.from(txoHashHint, "hex"), this.requiredConfirmations ?? 6, //In case confirmation count is not known, we use a conservative estimate
|
|
402
|
-
(btcTx, vout, txEtaMs) => {
|
|
403
|
-
let requiredConfirmations = this.requiredConfirmations;
|
|
404
|
-
if (btcTx != null && vout != null && requiredConfirmations == null) {
|
|
405
|
-
requiredConfirmations = this.inferRequiredConfirmationsCount(btcTx, vout);
|
|
406
|
-
}
|
|
407
|
-
if (btcTx != null && (btcTx.txid !== this.txId || (this.requiredConfirmations == null && requiredConfirmations != null))) {
|
|
408
|
-
this.txId = btcTx.txid;
|
|
409
|
-
this.vout = vout;
|
|
410
|
-
this.requiredConfirmations = requiredConfirmations;
|
|
411
|
-
if (btcTx.inputAddresses != null)
|
|
412
|
-
this.senderAddress = btcTx.inputAddresses[0];
|
|
413
|
-
this._saveAndEmit().catch(e => {
|
|
414
|
-
this.logger.error("waitForBitcoinTransaction(): Failed to save swap from within waitForAddressTxo callback:", e);
|
|
415
|
-
});
|
|
416
|
-
}
|
|
417
|
-
//Abort the loop as soon as the transaction gets enough confirmations, this is required in case
|
|
418
|
-
// we pass a default 6 confirmations to the fn, but then are able to infer the actual confirmation
|
|
419
|
-
// target from the prior block
|
|
420
|
-
if (btcTx?.confirmations != null && requiredConfirmations != null && requiredConfirmations <= btcTx.confirmations && vout != null) {
|
|
421
|
-
abortedDueToEnoughConfirmationsResult = {
|
|
422
|
-
tx: btcTx,
|
|
423
|
-
vout
|
|
424
|
-
};
|
|
425
|
-
abortController.abort();
|
|
426
|
-
return;
|
|
427
|
-
}
|
|
428
|
-
if (updateCallback != null)
|
|
429
|
-
updateCallback(btcTx?.txid, btcTx == null ? undefined : (btcTx?.confirmations ?? 0), requiredConfirmations ?? NaN, txEtaMs);
|
|
430
|
-
}, abortController.signal, checkIntervalSeconds).catch(e => {
|
|
431
|
-
//We catch the case when the loop was aborted due to the transaction getting enough confirmations
|
|
432
|
-
if (abortedDueToEnoughConfirmationsResult != null)
|
|
433
|
-
return abortedDueToEnoughConfirmationsResult;
|
|
434
|
-
throw e;
|
|
435
|
-
});
|
|
436
|
-
if (abortSignal != null)
|
|
437
|
-
abortSignal.throwIfAborted();
|
|
438
|
-
this.txId = result.tx.txid;
|
|
439
|
-
this.vout = result.vout;
|
|
440
|
-
if (result.tx.inputAddresses != null)
|
|
441
|
-
this.senderAddress = result.tx.inputAddresses[0];
|
|
442
|
-
if (this._state !== FromBTCSwapState.CLAIM_CLAIMED &&
|
|
443
|
-
this._state !== FromBTCSwapState.FAILED) {
|
|
444
|
-
this.btcTxConfirmedAt ??= Date.now();
|
|
445
|
-
this._state = FromBTCSwapState.BTC_TX_CONFIRMED;
|
|
446
|
-
}
|
|
447
|
-
await this._saveAndEmit();
|
|
448
|
-
return result.tx.txid;
|
|
449
|
-
}
|
|
450
|
-
/**
|
|
451
|
-
* Private getter of the funded PSBT that doesn't check current state
|
|
452
|
-
*
|
|
453
|
-
* @param _bitcoinWallet Bitcoin wallet to fund the PSBT with
|
|
454
|
-
* @param feeRate Optional bitcoin fee rate in sats/vB
|
|
455
|
-
* @param additionalOutputs Optional additional outputs that should also be included in the generated PSBT
|
|
456
|
-
*
|
|
457
|
-
* @private
|
|
458
|
-
*/
|
|
459
|
-
async _getFundedPsbt(_bitcoinWallet, feeRate, additionalOutputs) {
|
|
460
|
-
if (this.address == null)
|
|
461
|
-
throw new Error("Cannot create funded PSBT, because the address is not known! This can happen after a swap is recovered.");
|
|
462
|
-
let bitcoinWallet;
|
|
463
|
-
if ((0, IBitcoinWallet_1.isIBitcoinWallet)(_bitcoinWallet)) {
|
|
464
|
-
bitcoinWallet = _bitcoinWallet;
|
|
465
|
-
}
|
|
466
|
-
else {
|
|
467
|
-
bitcoinWallet = new SingleAddressBitcoinWallet_1.SingleAddressBitcoinWallet(this.wrapper._btcRpc, this.wrapper._options.bitcoinNetwork, _bitcoinWallet);
|
|
468
|
-
}
|
|
469
|
-
//TODO: Maybe re-introduce fee rate check here if passed from the user
|
|
470
|
-
if (feeRate == null) {
|
|
471
|
-
feeRate = await bitcoinWallet.getFeeRate();
|
|
472
|
-
}
|
|
473
|
-
const basePsbt = new btc_signer_1.Transaction({
|
|
474
|
-
allowUnknownOutputs: true,
|
|
475
|
-
allowLegacyWitnessUtxo: true
|
|
476
|
-
});
|
|
477
|
-
basePsbt.addOutput({
|
|
478
|
-
amount: this.amount,
|
|
479
|
-
script: (0, BitcoinUtils_1.toOutputScript)(this.wrapper._options.bitcoinNetwork, this.address)
|
|
480
|
-
});
|
|
481
|
-
if (additionalOutputs != null)
|
|
482
|
-
additionalOutputs.forEach(output => {
|
|
483
|
-
basePsbt.addOutput({
|
|
484
|
-
amount: output.amount,
|
|
485
|
-
script: output.outputScript ?? (0, BitcoinUtils_1.toOutputScript)(this.wrapper._options.bitcoinNetwork, output.address)
|
|
486
|
-
});
|
|
487
|
-
});
|
|
488
|
-
const psbt = await bitcoinWallet.fundPsbt(basePsbt, feeRate);
|
|
489
|
-
//Sign every input
|
|
490
|
-
const signInputs = [];
|
|
491
|
-
for (let i = 0; i < psbt.inputsLength; i++) {
|
|
492
|
-
signInputs.push(i);
|
|
493
|
-
}
|
|
494
|
-
const serializedPsbt = buffer_1.Buffer.from(psbt.toPSBT());
|
|
495
|
-
return {
|
|
496
|
-
psbt,
|
|
497
|
-
psbtHex: serializedPsbt.toString("hex"),
|
|
498
|
-
psbtBase64: serializedPsbt.toString("base64"),
|
|
499
|
-
signInputs
|
|
500
|
-
};
|
|
501
|
-
}
|
|
502
|
-
/**
|
|
503
|
-
* @inheritDoc
|
|
504
|
-
*/
|
|
505
|
-
getFundedPsbt(_bitcoinWallet, feeRate, additionalOutputs) {
|
|
506
|
-
if (this._state !== FromBTCSwapState.CLAIM_COMMITED)
|
|
507
|
-
throw new Error("Swap not committed yet, please initiate the swap first with commit() call!");
|
|
508
|
-
return this._getFundedPsbt(_bitcoinWallet, feeRate, additionalOutputs);
|
|
509
|
-
}
|
|
510
|
-
/**
|
|
511
|
-
* @inheritDoc
|
|
512
|
-
*
|
|
513
|
-
* @throws {Error} if the swap is in invalid state (not in {@link FromBTCSwapState.CLAIM_COMMITED}), or if
|
|
514
|
-
* the swap bitcoin address already expired.
|
|
515
|
-
*/
|
|
516
|
-
async submitPsbt(_psbt) {
|
|
517
|
-
const psbt = (0, BitcoinUtils_1.parsePsbtTransaction)(_psbt);
|
|
518
|
-
if (this._state !== FromBTCSwapState.CLAIM_COMMITED)
|
|
519
|
-
throw new Error("Swap not committed yet, please initiate the swap first with commit() call!");
|
|
520
|
-
//Ensure not expired
|
|
521
|
-
if (this.getTimeoutTime() < Date.now()) {
|
|
522
|
-
throw new Error("Swap address expired!");
|
|
523
|
-
}
|
|
524
|
-
const output0 = psbt.getOutput(0);
|
|
525
|
-
if (this.amount != null && output0.amount !== this.amount)
|
|
526
|
-
throw new Error("PSBT output amount invalid, expected: " + this.amount + " got: " + output0.amount);
|
|
527
|
-
if (this.address != null) {
|
|
528
|
-
const expectedOutputScript = (0, BitcoinUtils_1.toOutputScript)(this.wrapper._options.bitcoinNetwork, this.address);
|
|
529
|
-
if (output0.script == null || !expectedOutputScript.equals(output0.script))
|
|
530
|
-
throw new Error("PSBT output script invalid!");
|
|
531
|
-
}
|
|
532
|
-
if (!psbt.isFinal)
|
|
533
|
-
psbt.finalize();
|
|
534
|
-
return await this.wrapper._btcRpc.sendRawTransaction(buffer_1.Buffer.from(psbt.toBytes(true, true)).toString("hex"));
|
|
535
|
-
}
|
|
536
|
-
/**
|
|
537
|
-
* @inheritDoc
|
|
538
|
-
*/
|
|
539
|
-
async estimateBitcoinFee(_bitcoinWallet, feeRate) {
|
|
540
|
-
if (this.address == null || this.amount == null)
|
|
541
|
-
return null;
|
|
542
|
-
const bitcoinWallet = (0, BitcoinWalletUtils_1.toBitcoinWallet)(_bitcoinWallet, this.wrapper._btcRpc, this.wrapper._options.bitcoinNetwork);
|
|
543
|
-
const txFee = await bitcoinWallet.getTransactionFee(this.address, this.amount, feeRate);
|
|
544
|
-
if (txFee == null)
|
|
545
|
-
return null;
|
|
546
|
-
return (0, TokenAmount_1.toTokenAmount)(BigInt(txFee), Token_1.BitcoinTokens.BTC, this.wrapper._prices);
|
|
547
|
-
}
|
|
548
|
-
/**
|
|
549
|
-
* @inheritDoc
|
|
550
|
-
*/
|
|
551
|
-
async sendBitcoinTransaction(wallet, feeRate) {
|
|
552
|
-
if (this.address == null || this.amount == null)
|
|
553
|
-
throw new Error("Cannot send bitcoin transaction, because the address is not known! This can happen after a swap is recovered.");
|
|
554
|
-
if (this._state !== FromBTCSwapState.CLAIM_COMMITED)
|
|
555
|
-
throw new Error("Swap not committed yet, please initiate the swap first with commit() call!");
|
|
556
|
-
//Ensure not expired
|
|
557
|
-
if (this.getTimeoutTime() < Date.now()) {
|
|
558
|
-
throw new Error("Swap address expired!");
|
|
559
|
-
}
|
|
560
|
-
if ((0, IBitcoinWallet_1.isIBitcoinWallet)(wallet)) {
|
|
561
|
-
return await wallet.sendTransaction(this.address, this.amount, feeRate);
|
|
562
|
-
}
|
|
563
|
-
else {
|
|
564
|
-
const { psbt, psbtHex, psbtBase64, signInputs } = await this.getFundedPsbt(wallet, feeRate);
|
|
565
|
-
const signedPsbt = await wallet.signPsbt({
|
|
566
|
-
psbt, psbtHex, psbtBase64
|
|
567
|
-
}, signInputs);
|
|
568
|
-
return await this.submitPsbt(signedPsbt);
|
|
569
|
-
}
|
|
570
|
-
}
|
|
571
|
-
//////////////////////////////
|
|
572
|
-
//// Execution
|
|
573
|
-
/**
|
|
574
|
-
* Executes the swap with the provided bitcoin wallet,
|
|
575
|
-
*
|
|
576
|
-
* @param dstSigner Signer on the destination network, needs to have the same address as the one specified when
|
|
577
|
-
* quote was created, this is required for legacy swaps because the destination wallet needs to actively open
|
|
578
|
-
* a bitcoin swap address to which the BTC is then sent, this means that the address also needs to have enough
|
|
579
|
-
* native tokens to pay for gas on the destination network
|
|
580
|
-
* @param wallet Bitcoin wallet to use to sign the bitcoin transaction, can also be null - then the execution waits
|
|
581
|
-
* till a transaction is received from an external wallet
|
|
582
|
-
* @param callbacks Callbacks to track the progress of the swap
|
|
583
|
-
* @param options Optional options for the swap like feeRate, AbortSignal, and timeouts/intervals
|
|
584
|
-
*
|
|
585
|
-
* @returns {boolean} Whether a swap was settled automatically by swap watchtowers or requires manual claim by the
|
|
586
|
-
* user, in case `false` is returned the user should call `swap.claim()` to settle the swap on the destination manually
|
|
587
|
-
*/
|
|
588
|
-
async execute(dstSigner, wallet, callbacks, options) {
|
|
589
|
-
if (this._state === FromBTCSwapState.FAILED)
|
|
590
|
-
throw new Error("Swap failed!");
|
|
591
|
-
if (this._state === FromBTCSwapState.EXPIRED)
|
|
592
|
-
throw new Error("Swap address expired!");
|
|
593
|
-
if (this._state === FromBTCSwapState.QUOTE_EXPIRED || this._state === FromBTCSwapState.QUOTE_SOFT_EXPIRED)
|
|
594
|
-
throw new Error("Swap quote expired!");
|
|
595
|
-
if (this._state === FromBTCSwapState.CLAIM_CLAIMED)
|
|
596
|
-
throw new Error("Swap already settled!");
|
|
597
|
-
if (this._state === FromBTCSwapState.PR_CREATED) {
|
|
598
|
-
await this.commit(dstSigner, options?.abortSignal, undefined, callbacks?.onDestinationCommitSent);
|
|
599
|
-
}
|
|
600
|
-
if (this._state === FromBTCSwapState.CLAIM_COMMITED) {
|
|
601
|
-
if (wallet != null) {
|
|
602
|
-
const bitcoinPaymentSent = await this.getBitcoinPayment();
|
|
603
|
-
if (bitcoinPaymentSent == null) {
|
|
604
|
-
//Send btc tx
|
|
605
|
-
const txId = await this.sendBitcoinTransaction(wallet, options?.feeRate);
|
|
606
|
-
if (callbacks?.onSourceTransactionSent != null)
|
|
607
|
-
callbacks.onSourceTransactionSent(txId);
|
|
608
|
-
}
|
|
609
|
-
}
|
|
610
|
-
const txId = await this.waitForBitcoinTransaction(callbacks?.onSourceTransactionConfirmationStatus, options?.btcTxCheckIntervalSeconds, options?.abortSignal);
|
|
611
|
-
if (callbacks?.onSourceTransactionConfirmed != null)
|
|
612
|
-
callbacks.onSourceTransactionConfirmed(txId);
|
|
613
|
-
}
|
|
614
|
-
// @ts-ignore
|
|
615
|
-
if (this._state === FromBTCSwapState.CLAIM_CLAIMED)
|
|
616
|
-
return true;
|
|
617
|
-
if (this._state === FromBTCSwapState.BTC_TX_CONFIRMED) {
|
|
618
|
-
const success = await this.waitTillClaimed(options?.maxWaitTillAutomaticSettlementSeconds ?? 60, options?.abortSignal);
|
|
619
|
-
if (success && callbacks?.onSwapSettled != null)
|
|
620
|
-
callbacks.onSwapSettled(this.getOutputTxId());
|
|
621
|
-
return success;
|
|
622
|
-
}
|
|
623
|
-
throw new Error("Invalid state reached!");
|
|
624
|
-
}
|
|
625
|
-
/**
|
|
626
|
-
* @inheritDoc
|
|
627
|
-
*
|
|
628
|
-
* @param options.bitcoinFeeRate Optional fee rate to use for the created Bitcoin transaction
|
|
629
|
-
* @param options.bitcoinWallet Bitcoin wallet to use, when provided the function returns a funded
|
|
630
|
-
* psbt (`"FUNDED_PSBT"`), if not passed just a bitcoin receive address is returned (`"ADDRESS"`)
|
|
631
|
-
* @param options.skipChecks Skip checks like making sure init signature is still valid and swap
|
|
632
|
-
* wasn't commited yet (this is handled on swap creation, if you commit right after quoting, you
|
|
633
|
-
* can use `skipChecks=true`)
|
|
634
|
-
*
|
|
635
|
-
* @throws {Error} if the swap or quote is expired, or if triggered in invalid state
|
|
636
|
-
*/
|
|
637
|
-
async txsExecute(options) {
|
|
638
|
-
if (this._state === FromBTCSwapState.PR_CREATED) {
|
|
639
|
-
if (!await this._verifyQuoteValid())
|
|
640
|
-
throw new Error("Quote already expired or close to expiry!");
|
|
641
|
-
if (this.getTimeoutTime() < Date.now())
|
|
642
|
-
throw new Error("Swap address already expired or close to expiry!");
|
|
643
|
-
return [
|
|
644
|
-
{
|
|
645
|
-
name: "Commit",
|
|
646
|
-
description: `Opens up the bitcoin swap address on the ${this.chainIdentifier} side`,
|
|
647
|
-
chain: this.chainIdentifier,
|
|
648
|
-
txs: await this.txsCommit(options?.skipChecks)
|
|
649
|
-
},
|
|
650
|
-
{
|
|
651
|
-
name: "Payment",
|
|
652
|
-
description: "Send funds to the bitcoin swap address",
|
|
653
|
-
chain: "BITCOIN",
|
|
654
|
-
txs: [
|
|
655
|
-
options?.bitcoinWallet == null ? {
|
|
656
|
-
address: this.address ?? "",
|
|
657
|
-
amount: Number(this.amount),
|
|
658
|
-
hyperlink: this._getHyperlink(),
|
|
659
|
-
type: "ADDRESS"
|
|
660
|
-
} : {
|
|
661
|
-
...await this.getFundedPsbt(options.bitcoinWallet),
|
|
662
|
-
type: "FUNDED_PSBT"
|
|
663
|
-
}
|
|
664
|
-
]
|
|
665
|
-
}
|
|
666
|
-
];
|
|
667
|
-
}
|
|
668
|
-
if (this._state === FromBTCSwapState.CLAIM_COMMITED) {
|
|
669
|
-
if (this.getTimeoutTime() < Date.now())
|
|
670
|
-
throw new Error("Swap address already expired or close to expiry!");
|
|
671
|
-
return [
|
|
672
|
-
{
|
|
673
|
-
name: "Payment",
|
|
674
|
-
description: "Send funds to the bitcoin swap address",
|
|
675
|
-
chain: "BITCOIN",
|
|
676
|
-
txs: [
|
|
677
|
-
options?.bitcoinWallet == null ? {
|
|
678
|
-
address: this.getAddress(),
|
|
679
|
-
amount: Number(this.amount),
|
|
680
|
-
hyperlink: this._getHyperlink(),
|
|
681
|
-
type: "ADDRESS"
|
|
682
|
-
} : {
|
|
683
|
-
...await this.getFundedPsbt(options.bitcoinWallet),
|
|
684
|
-
type: "FUNDED_PSBT"
|
|
685
|
-
}
|
|
686
|
-
]
|
|
687
|
-
}
|
|
688
|
-
];
|
|
689
|
-
}
|
|
690
|
-
throw new Error("Invalid swap state to obtain execution txns, required PR_CREATED or CLAIM_COMMITED");
|
|
691
|
-
}
|
|
692
|
-
/**
|
|
693
|
-
* @inheritDoc
|
|
694
|
-
*
|
|
695
|
-
* @param options.bitcoinFeeRate Optional fee rate to use for the created Bitcoin transaction
|
|
696
|
-
* @param options.bitcoinWallet Bitcoin wallet to use, when provided the function returns a funded
|
|
697
|
-
* psbt (`"FUNDED_PSBT"`), if not passed just a bitcoin receive address is returned (`"ADDRESS"`)
|
|
698
|
-
* @param options.skipChecks Skip checks like making sure init signature is still valid and swap
|
|
699
|
-
* wasn't commited yet (this is handled on swap creation, if you commit right after quoting, you
|
|
700
|
-
* can use `skipChecks=true`)
|
|
701
|
-
* @param options.manualSettlementSmartChainSigner Optional smart chain signer to create a manual claim (settlement) transaction
|
|
702
|
-
* @param options.maxWaitTillAutomaticSettlementSeconds Maximum time to wait for an automatic settlement after
|
|
703
|
-
* the bitcoin transaction is confirmed (defaults to 60 seconds)
|
|
704
|
-
*/
|
|
705
|
-
async getCurrentActions(options) {
|
|
706
|
-
if (this._state === FromBTCSwapState.PR_CREATED || this._state === FromBTCSwapState.CLAIM_COMMITED) {
|
|
707
|
-
try {
|
|
708
|
-
return await this.txsExecute(options);
|
|
709
|
-
}
|
|
710
|
-
catch (e) { }
|
|
711
|
-
}
|
|
712
|
-
if (this.isClaimable()) {
|
|
713
|
-
if (this.btcTxConfirmedAt == null ||
|
|
714
|
-
options?.maxWaitTillAutomaticSettlementSeconds === 0 ||
|
|
715
|
-
(Date.now() - this.btcTxConfirmedAt) > (options?.maxWaitTillAutomaticSettlementSeconds ?? 60) * 1000) {
|
|
716
|
-
return [{
|
|
717
|
-
name: "Claim",
|
|
718
|
-
description: "Manually settle (claim) the swap on the destination smart chain",
|
|
719
|
-
chain: this.chainIdentifier,
|
|
720
|
-
txs: await this.txsClaim(options?.manualSettlementSmartChainSigner)
|
|
721
|
-
}];
|
|
722
|
-
}
|
|
723
|
-
}
|
|
724
|
-
return [];
|
|
725
|
-
}
|
|
726
|
-
//////////////////////////////
|
|
727
|
-
//// Commit
|
|
728
|
-
/**
|
|
729
|
-
* @inheritDoc
|
|
730
|
-
*
|
|
731
|
-
* @throws {Error} If invalid signer is provided that doesn't match the swap data
|
|
732
|
-
*/
|
|
733
|
-
async commit(_signer, abortSignal, skipChecks, onBeforeTxSent) {
|
|
734
|
-
const signer = (0, base_1.isAbstractSigner)(_signer) ? _signer : await this.wrapper._chain.wrapSigner(_signer);
|
|
735
|
-
this.checkSigner(signer);
|
|
736
|
-
let txCount = 0;
|
|
737
|
-
const txs = await this.txsCommit(skipChecks);
|
|
738
|
-
const result = await this.wrapper._chain.sendAndConfirm(signer, txs, true, abortSignal, undefined, (txId) => {
|
|
739
|
-
txCount++;
|
|
740
|
-
if (onBeforeTxSent != null && txCount === txs.length)
|
|
741
|
-
onBeforeTxSent(txId);
|
|
742
|
-
return Promise.resolve();
|
|
743
|
-
});
|
|
744
|
-
this._commitTxId = result[result.length - 1];
|
|
745
|
-
if (this._state === FromBTCSwapState.PR_CREATED || this._state === FromBTCSwapState.QUOTE_SOFT_EXPIRED || this._state === FromBTCSwapState.QUOTE_EXPIRED) {
|
|
746
|
-
await this._saveAndEmit(FromBTCSwapState.CLAIM_COMMITED);
|
|
747
|
-
}
|
|
748
|
-
return this._commitTxId;
|
|
749
|
-
}
|
|
750
|
-
/**
|
|
751
|
-
* @inheritDoc
|
|
752
|
-
*/
|
|
753
|
-
async waitTillCommited(abortSignal) {
|
|
754
|
-
if (this._state === FromBTCSwapState.CLAIM_COMMITED || this._state === FromBTCSwapState.CLAIM_CLAIMED)
|
|
755
|
-
return Promise.resolve();
|
|
756
|
-
if (this._state !== FromBTCSwapState.PR_CREATED && this._state !== FromBTCSwapState.QUOTE_SOFT_EXPIRED)
|
|
757
|
-
throw new Error("Invalid state");
|
|
758
|
-
const abortController = (0, Utils_1.extendAbortController)(abortSignal);
|
|
759
|
-
const result = await Promise.race([
|
|
760
|
-
this.watchdogWaitTillCommited(undefined, abortController.signal),
|
|
761
|
-
this.waitTillState(FromBTCSwapState.CLAIM_COMMITED, "gte", abortController.signal).then(() => 0)
|
|
762
|
-
]);
|
|
763
|
-
abortController.abort();
|
|
764
|
-
if (result === 0)
|
|
765
|
-
this.logger.debug("waitTillCommited(): Resolved from state changed");
|
|
766
|
-
if (result === true)
|
|
767
|
-
this.logger.debug("waitTillCommited(): Resolved from watchdog - commited");
|
|
768
|
-
if (result === false) {
|
|
769
|
-
this.logger.debug("waitTillCommited(): Resolved from watchdog - signature expired");
|
|
770
|
-
if (this._state === FromBTCSwapState.PR_CREATED || this._state === FromBTCSwapState.QUOTE_SOFT_EXPIRED) {
|
|
771
|
-
await this._saveAndEmit(FromBTCSwapState.QUOTE_EXPIRED);
|
|
772
|
-
}
|
|
773
|
-
return;
|
|
774
|
-
}
|
|
775
|
-
if (this._state === FromBTCSwapState.PR_CREATED || this._state === FromBTCSwapState.QUOTE_SOFT_EXPIRED) {
|
|
776
|
-
await this._saveAndEmit(FromBTCSwapState.CLAIM_COMMITED);
|
|
777
|
-
}
|
|
778
|
-
}
|
|
779
|
-
//////////////////////////////
|
|
780
|
-
//// Claim
|
|
781
|
-
/**
|
|
782
|
-
* Returns transactions for settling (claiming) the swap if the swap requires manual settlement, you can check so
|
|
783
|
-
* with isClaimable. After sending the transaction manually be sure to call the waitTillClaimed function to wait
|
|
784
|
-
* till the claim transaction is observed, processed by the SDK and state of the swap properly updated.
|
|
785
|
-
*
|
|
786
|
-
* @remarks
|
|
787
|
-
* Might also return transactions necessary to sync the bitcoin light client.
|
|
788
|
-
*
|
|
789
|
-
* @param _signer Address of the signer to create the claim transactions for
|
|
790
|
-
*
|
|
791
|
-
* @throws {Error} If the swap is in invalid state (must be {@link FromBTCSwapState.BTC_TX_CONFIRMED})
|
|
792
|
-
*/
|
|
793
|
-
async txsClaim(_signer) {
|
|
794
|
-
let signer = undefined;
|
|
795
|
-
if (_signer != null) {
|
|
796
|
-
if (typeof (_signer) === "string") {
|
|
797
|
-
signer = _signer;
|
|
798
|
-
}
|
|
799
|
-
else if ((0, base_1.isAbstractSigner)(_signer)) {
|
|
800
|
-
signer = _signer;
|
|
801
|
-
}
|
|
802
|
-
else {
|
|
803
|
-
signer = await this.wrapper._chain.wrapSigner(_signer);
|
|
804
|
-
}
|
|
805
|
-
}
|
|
806
|
-
if (this._state !== FromBTCSwapState.BTC_TX_CONFIRMED)
|
|
807
|
-
throw new Error("Must be in BTC_TX_CONFIRMED state!");
|
|
808
|
-
if (this.txId == null || this.vout == null)
|
|
809
|
-
throw new Error("Bitcoin transaction ID not known!");
|
|
810
|
-
const tx = await this.wrapper._btcRpc.getTransaction(this.txId);
|
|
811
|
-
if (tx == null)
|
|
812
|
-
throw new Error("Bitcoin transaction not found on the network!");
|
|
813
|
-
this.requiredConfirmations ??= this.inferRequiredConfirmationsCount(tx, this.vout);
|
|
814
|
-
if (this.requiredConfirmations == null)
|
|
815
|
-
throw new Error("Cannot create claim transaction, because required confirmations are not known and cannot be infered! This can happen after a swap is recovered.");
|
|
816
|
-
if (tx.blockhash == null || tx.confirmations == null || tx.blockheight == null || tx.confirmations < this.requiredConfirmations)
|
|
817
|
-
throw new Error("Bitcoin transaction not confirmed yet!");
|
|
818
|
-
return await this.wrapper._contract.txsClaimWithTxData(signer ?? this._getInitiator(), this._data, {
|
|
819
|
-
blockhash: tx.blockhash,
|
|
820
|
-
confirmations: tx.confirmations,
|
|
821
|
-
txid: tx.txid,
|
|
822
|
-
hex: tx.hex,
|
|
823
|
-
height: tx.blockheight
|
|
824
|
-
}, this.requiredConfirmations, this.vout, undefined, this.wrapper._synchronizer, true);
|
|
825
|
-
}
|
|
826
|
-
/**
|
|
827
|
-
* Settles the swap by claiming the funds on the destination chain if the swap requires manual settlement, you can
|
|
828
|
-
* check so with isClaimable.
|
|
829
|
-
*
|
|
830
|
-
* @remarks
|
|
831
|
-
* Might also sync the bitcoin light client during the process.
|
|
832
|
-
*
|
|
833
|
-
* @param _signer Signer to use for signing the settlement transactions, can also be different to the recipient
|
|
834
|
-
* @param abortSignal Abort signal
|
|
835
|
-
* @param onBeforeTxSent Optional callback triggered before the claim transaction is broadcasted
|
|
836
|
-
*
|
|
837
|
-
* @returns Transaction ID of the settlement (claim) transaction on the destination smart chain
|
|
838
|
-
*/
|
|
839
|
-
async claim(_signer, abortSignal, onBeforeTxSent) {
|
|
840
|
-
const signer = (0, base_1.isAbstractSigner)(_signer) ? _signer : await this.wrapper._chain.wrapSigner(_signer);
|
|
841
|
-
let txIds;
|
|
842
|
-
try {
|
|
843
|
-
let txCount = 0;
|
|
844
|
-
const txs = await this.txsClaim(signer);
|
|
845
|
-
txIds = await this.wrapper._chain.sendAndConfirm(signer, txs, true, abortSignal, undefined, (txId) => {
|
|
846
|
-
txCount++;
|
|
847
|
-
if (onBeforeTxSent != null && txCount === txs.length)
|
|
848
|
-
onBeforeTxSent(txId);
|
|
849
|
-
return Promise.resolve();
|
|
850
|
-
});
|
|
851
|
-
}
|
|
852
|
-
catch (e) {
|
|
853
|
-
this.logger.info("claim(): Failed to claim ourselves, checking swap claim state...");
|
|
854
|
-
if (this._state === FromBTCSwapState.CLAIM_CLAIMED) {
|
|
855
|
-
this.logger.info("claim(): Transaction state is CLAIM_CLAIMED, swap was successfully claimed by the watchtower");
|
|
856
|
-
return this._claimTxId;
|
|
857
|
-
}
|
|
858
|
-
const status = await this.wrapper._contract.getCommitStatus(this._getInitiator(), this._data);
|
|
859
|
-
if (status?.type === base_1.SwapCommitStateType.PAID) {
|
|
860
|
-
this.logger.info("claim(): Transaction commit status is PAID, swap was successfully claimed by the watchtower");
|
|
861
|
-
if (this._claimTxId == null)
|
|
862
|
-
this._claimTxId = await status.getClaimTxId();
|
|
863
|
-
const txId = buffer_1.Buffer.from(await status.getClaimResult(), "hex").reverse().toString("hex");
|
|
864
|
-
await this._setBitcoinTxId(txId);
|
|
865
|
-
await this._saveAndEmit(FromBTCSwapState.CLAIM_CLAIMED);
|
|
866
|
-
return this._claimTxId;
|
|
867
|
-
}
|
|
868
|
-
throw e;
|
|
869
|
-
}
|
|
870
|
-
this._claimTxId = txIds[txIds.length - 1];
|
|
871
|
-
if (this._state === FromBTCSwapState.CLAIM_COMMITED || this._state === FromBTCSwapState.BTC_TX_CONFIRMED ||
|
|
872
|
-
this._state === FromBTCSwapState.EXPIRED || this._state === FromBTCSwapState.FAILED) {
|
|
873
|
-
await this._saveAndEmit(FromBTCSwapState.CLAIM_CLAIMED);
|
|
874
|
-
}
|
|
875
|
-
return txIds[txIds.length - 1];
|
|
876
|
-
}
|
|
877
|
-
/**
|
|
878
|
-
* @inheritDoc
|
|
879
|
-
*
|
|
880
|
-
* @throws {Error} If swap is in invalid state (must be {@link FromBTCSwapState.BTC_TX_CONFIRMED})
|
|
881
|
-
* @throws {Error} If the LP refunded sooner than we were able to claim
|
|
882
|
-
*/
|
|
883
|
-
async waitTillClaimed(maxWaitTimeSeconds, abortSignal) {
|
|
884
|
-
if (this._state === FromBTCSwapState.CLAIM_CLAIMED)
|
|
885
|
-
return Promise.resolve(true);
|
|
886
|
-
if (this._state !== FromBTCSwapState.BTC_TX_CONFIRMED)
|
|
887
|
-
throw new Error("Invalid state (not BTC_TX_CONFIRMED)");
|
|
888
|
-
const abortController = (0, Utils_1.extendAbortController)(abortSignal);
|
|
889
|
-
let timedOut = false;
|
|
890
|
-
if (maxWaitTimeSeconds != null) {
|
|
891
|
-
const timeout = setTimeout(() => {
|
|
892
|
-
timedOut = true;
|
|
893
|
-
abortController.abort();
|
|
894
|
-
}, maxWaitTimeSeconds * 1000);
|
|
895
|
-
abortController.signal.addEventListener("abort", () => clearTimeout(timeout));
|
|
896
|
-
}
|
|
897
|
-
let res;
|
|
898
|
-
try {
|
|
899
|
-
res = await Promise.race([
|
|
900
|
-
this.watchdogWaitTillResult(undefined, abortController.signal),
|
|
901
|
-
this.waitTillState(FromBTCSwapState.CLAIM_CLAIMED, "eq", abortController.signal).then(() => 0),
|
|
902
|
-
this.waitTillState(FromBTCSwapState.FAILED, "eq", abortController.signal).then(() => 1),
|
|
903
|
-
]);
|
|
904
|
-
abortController.abort();
|
|
905
|
-
}
|
|
906
|
-
catch (e) {
|
|
907
|
-
abortController.abort();
|
|
908
|
-
if (timedOut)
|
|
909
|
-
return false;
|
|
910
|
-
throw e;
|
|
911
|
-
}
|
|
912
|
-
if (res === 0) {
|
|
913
|
-
this.logger.debug("waitTillClaimed(): Resolved from state change (CLAIM_CLAIMED)");
|
|
914
|
-
return true;
|
|
915
|
-
}
|
|
916
|
-
if (res === 1) {
|
|
917
|
-
this.logger.debug("waitTillClaimed(): Resolved from state change (FAILED)");
|
|
918
|
-
throw new Error("Offerer refunded during claiming");
|
|
919
|
-
}
|
|
920
|
-
this.logger.debug("waitTillClaimed(): Resolved from watchdog");
|
|
921
|
-
if (res?.type === base_1.SwapCommitStateType.PAID) {
|
|
922
|
-
if (this._state !== FromBTCSwapState.CLAIM_CLAIMED) {
|
|
923
|
-
if (this._claimTxId == null)
|
|
924
|
-
this._claimTxId = await res.getClaimTxId();
|
|
925
|
-
const txId = buffer_1.Buffer.from(await res.getClaimResult(), "hex").reverse().toString("hex");
|
|
926
|
-
await this._setBitcoinTxId(txId);
|
|
927
|
-
await this._saveAndEmit(FromBTCSwapState.CLAIM_CLAIMED);
|
|
928
|
-
}
|
|
929
|
-
}
|
|
930
|
-
if (res?.type === base_1.SwapCommitStateType.NOT_COMMITED || res?.type === base_1.SwapCommitStateType.EXPIRED) {
|
|
931
|
-
if (this._state !== FromBTCSwapState.CLAIM_CLAIMED &&
|
|
932
|
-
this._state !== FromBTCSwapState.FAILED) {
|
|
933
|
-
if (res.getRefundTxId != null)
|
|
934
|
-
this._refundTxId = await res.getRefundTxId();
|
|
935
|
-
await this._saveAndEmit(FromBTCSwapState.FAILED);
|
|
936
|
-
}
|
|
937
|
-
throw new Error("Swap expired while waiting for claim!");
|
|
938
|
-
}
|
|
939
|
-
return true;
|
|
940
|
-
}
|
|
941
|
-
//////////////////////////////
|
|
942
|
-
//// Storage
|
|
943
|
-
/**
|
|
944
|
-
* @inheritDoc
|
|
945
|
-
*/
|
|
946
|
-
serialize() {
|
|
947
|
-
return {
|
|
948
|
-
...super.serialize(),
|
|
949
|
-
address: this.address,
|
|
950
|
-
amount: this.amount == null ? null : this.amount.toString(10),
|
|
951
|
-
requiredConfirmations: this.requiredConfirmations,
|
|
952
|
-
senderAddress: this.senderAddress,
|
|
953
|
-
txId: this.txId,
|
|
954
|
-
vout: this.vout,
|
|
955
|
-
btcTxConfirmedAt: this.btcTxConfirmedAt
|
|
956
|
-
};
|
|
957
|
-
}
|
|
958
|
-
//////////////////////////////
|
|
959
|
-
//// Swap ticks & sync
|
|
960
|
-
/**
|
|
961
|
-
* Checks the swap's state on-chain and compares it to its internal state, updates/changes it according to on-chain
|
|
962
|
-
* data
|
|
963
|
-
*
|
|
964
|
-
* @private
|
|
965
|
-
*/
|
|
966
|
-
async syncStateFromChain(quoteDefinitelyExpired, commitStatus) {
|
|
967
|
-
if (this._state === FromBTCSwapState.PR_CREATED ||
|
|
968
|
-
this._state === FromBTCSwapState.QUOTE_SOFT_EXPIRED ||
|
|
969
|
-
this._state === FromBTCSwapState.CLAIM_COMMITED ||
|
|
970
|
-
this._state === FromBTCSwapState.BTC_TX_CONFIRMED ||
|
|
971
|
-
this._state === FromBTCSwapState.EXPIRED) {
|
|
972
|
-
let quoteExpired = false;
|
|
973
|
-
if (this._state === FromBTCSwapState.PR_CREATED || this._state === FromBTCSwapState.QUOTE_SOFT_EXPIRED) {
|
|
974
|
-
quoteExpired = quoteDefinitelyExpired ?? await this._verifyQuoteDefinitelyExpired(); //Make sure we check for expiry here, to prevent race conditions
|
|
975
|
-
}
|
|
976
|
-
const status = commitStatus ?? await this.wrapper._contract.getCommitStatus(this._getInitiator(), this._data);
|
|
977
|
-
if (status != null && await this._forciblySetOnchainState(status))
|
|
978
|
-
return true;
|
|
979
|
-
if (this._state === FromBTCSwapState.PR_CREATED || this._state === FromBTCSwapState.QUOTE_SOFT_EXPIRED) {
|
|
980
|
-
if (quoteExpired) {
|
|
981
|
-
this._state = FromBTCSwapState.QUOTE_EXPIRED;
|
|
982
|
-
return true;
|
|
983
|
-
}
|
|
984
|
-
}
|
|
985
|
-
}
|
|
986
|
-
return false;
|
|
987
|
-
}
|
|
988
|
-
/**
|
|
989
|
-
* @inheritDoc
|
|
990
|
-
* @internal
|
|
991
|
-
*/
|
|
992
|
-
_shouldFetchOnchainState() {
|
|
993
|
-
return this._state === FromBTCSwapState.PR_CREATED || this._state === FromBTCSwapState.QUOTE_SOFT_EXPIRED ||
|
|
994
|
-
this._state === FromBTCSwapState.CLAIM_COMMITED || this._state === FromBTCSwapState.BTC_TX_CONFIRMED ||
|
|
995
|
-
this._state === FromBTCSwapState.EXPIRED;
|
|
996
|
-
}
|
|
997
|
-
/**
|
|
998
|
-
* @inheritDoc
|
|
999
|
-
* @internal
|
|
1000
|
-
*/
|
|
1001
|
-
_shouldFetchExpiryStatus() {
|
|
1002
|
-
return this._state === FromBTCSwapState.PR_CREATED || this._state === FromBTCSwapState.QUOTE_SOFT_EXPIRED;
|
|
1003
|
-
}
|
|
1004
|
-
/**
|
|
1005
|
-
* @inheritDoc
|
|
1006
|
-
* @internal
|
|
1007
|
-
*/
|
|
1008
|
-
async _sync(save, quoteDefinitelyExpired, commitStatus) {
|
|
1009
|
-
const changed = await this.syncStateFromChain(quoteDefinitelyExpired, commitStatus);
|
|
1010
|
-
if (changed && save)
|
|
1011
|
-
await this._saveAndEmit();
|
|
1012
|
-
return changed;
|
|
1013
|
-
}
|
|
1014
|
-
/**
|
|
1015
|
-
* @inheritDoc
|
|
1016
|
-
* @internal
|
|
1017
|
-
*/
|
|
1018
|
-
async _forciblySetOnchainState(status) {
|
|
1019
|
-
switch (status.type) {
|
|
1020
|
-
case base_1.SwapCommitStateType.PAID:
|
|
1021
|
-
if (this._claimTxId == null)
|
|
1022
|
-
this._claimTxId = await status.getClaimTxId();
|
|
1023
|
-
const txId = buffer_1.Buffer.from(await status.getClaimResult(), "hex").reverse().toString("hex");
|
|
1024
|
-
await this._setBitcoinTxId(txId);
|
|
1025
|
-
this._state = FromBTCSwapState.CLAIM_CLAIMED;
|
|
1026
|
-
return true;
|
|
1027
|
-
case base_1.SwapCommitStateType.NOT_COMMITED:
|
|
1028
|
-
if (this._refundTxId == null && status.getRefundTxId)
|
|
1029
|
-
this._refundTxId = await status.getRefundTxId();
|
|
1030
|
-
if (this._refundTxId != null) {
|
|
1031
|
-
this._state = FromBTCSwapState.FAILED;
|
|
1032
|
-
return true;
|
|
1033
|
-
}
|
|
1034
|
-
break;
|
|
1035
|
-
case base_1.SwapCommitStateType.EXPIRED:
|
|
1036
|
-
if (this._refundTxId == null && status.getRefundTxId)
|
|
1037
|
-
this._refundTxId = await status.getRefundTxId();
|
|
1038
|
-
this._state = this._refundTxId == null ? FromBTCSwapState.QUOTE_EXPIRED : FromBTCSwapState.FAILED;
|
|
1039
|
-
return true;
|
|
1040
|
-
case base_1.SwapCommitStateType.COMMITED:
|
|
1041
|
-
let save = false;
|
|
1042
|
-
if (this._state !== FromBTCSwapState.CLAIM_COMMITED && this._state !== FromBTCSwapState.BTC_TX_CONFIRMED && this._state !== FromBTCSwapState.EXPIRED) {
|
|
1043
|
-
this._state = FromBTCSwapState.CLAIM_COMMITED;
|
|
1044
|
-
save = true;
|
|
1045
|
-
}
|
|
1046
|
-
if (this.address == null)
|
|
1047
|
-
return save;
|
|
1048
|
-
this.btcTxLastChecked = Date.now();
|
|
1049
|
-
const res = await this.getBitcoinPayment();
|
|
1050
|
-
if (res != null) {
|
|
1051
|
-
if (this.txId !== res.txId) {
|
|
1052
|
-
if (res.inputAddresses != null)
|
|
1053
|
-
this.senderAddress = res.inputAddresses[0];
|
|
1054
|
-
this.txId = res.txId;
|
|
1055
|
-
this.vout = res.vout;
|
|
1056
|
-
save = true;
|
|
1057
|
-
}
|
|
1058
|
-
if (this.requiredConfirmations != null && res.confirmations >= this.requiredConfirmations) {
|
|
1059
|
-
this.btcTxConfirmedAt ??= Date.now();
|
|
1060
|
-
this._state = FromBTCSwapState.BTC_TX_CONFIRMED;
|
|
1061
|
-
save = true;
|
|
1062
|
-
}
|
|
1063
|
-
}
|
|
1064
|
-
return save;
|
|
1065
|
-
}
|
|
1066
|
-
return false;
|
|
1067
|
-
}
|
|
1068
|
-
/**
|
|
1069
|
-
* @inheritDoc
|
|
1070
|
-
* @internal
|
|
1071
|
-
*/
|
|
1072
|
-
async _tick(save) {
|
|
1073
|
-
switch (this._state) {
|
|
1074
|
-
case FromBTCSwapState.PR_CREATED:
|
|
1075
|
-
if (this.expiry < Date.now()) {
|
|
1076
|
-
this._state = FromBTCSwapState.QUOTE_SOFT_EXPIRED;
|
|
1077
|
-
if (save)
|
|
1078
|
-
await this._saveAndEmit();
|
|
1079
|
-
return true;
|
|
1080
|
-
}
|
|
1081
|
-
break;
|
|
1082
|
-
case FromBTCSwapState.CLAIM_COMMITED:
|
|
1083
|
-
if (this.getTimeoutTime() < Date.now()) {
|
|
1084
|
-
this._state = FromBTCSwapState.EXPIRED;
|
|
1085
|
-
if (save)
|
|
1086
|
-
await this._saveAndEmit();
|
|
1087
|
-
return true;
|
|
1088
|
-
}
|
|
1089
|
-
case FromBTCSwapState.EXPIRED:
|
|
1090
|
-
//Check if bitcoin payment was received at least every 2 minutes
|
|
1091
|
-
if (this.btcTxLastChecked == null || Date.now() - this.btcTxLastChecked >
|
|
1092
|
-
if (this.address != null)
|
|
1093
|
-
try {
|
|
1094
|
-
this.btcTxLastChecked = Date.now();
|
|
1095
|
-
const res = await this.getBitcoinPayment();
|
|
1096
|
-
if (res != null) {
|
|
1097
|
-
let shouldSave = false;
|
|
1098
|
-
if (this.txId !== res.txId) {
|
|
1099
|
-
this.txId = res.txId;
|
|
1100
|
-
this.vout = res.vout;
|
|
1101
|
-
if (res.inputAddresses != null)
|
|
1102
|
-
this.senderAddress = res.inputAddresses[0];
|
|
1103
|
-
shouldSave = true;
|
|
1104
|
-
}
|
|
1105
|
-
if (this.requiredConfirmations != null && res.confirmations >= this.requiredConfirmations) {
|
|
1106
|
-
this.btcTxConfirmedAt ??= Date.now();
|
|
1107
|
-
this._state = FromBTCSwapState.BTC_TX_CONFIRMED;
|
|
1108
|
-
if (save)
|
|
1109
|
-
await this._saveAndEmit();
|
|
1110
|
-
shouldSave = true;
|
|
1111
|
-
}
|
|
1112
|
-
if (shouldSave && save)
|
|
1113
|
-
await this._saveAndEmit();
|
|
1114
|
-
return shouldSave;
|
|
1115
|
-
}
|
|
1116
|
-
}
|
|
1117
|
-
catch (e) {
|
|
1118
|
-
this.logger.warn("tickSwap(" + this.getIdentifierHashString() + "): ", e);
|
|
1119
|
-
}
|
|
1120
|
-
}
|
|
1121
|
-
break;
|
|
1122
|
-
}
|
|
1123
|
-
return false;
|
|
1124
|
-
}
|
|
1125
|
-
}
|
|
1126
|
-
exports.FromBTCSwap = FromBTCSwap;
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.FromBTCSwap = exports.isFromBTCSwapInit = exports.FromBTCSwapState = void 0;
|
|
4
|
+
const IFromBTCSelfInitSwap_1 = require("../IFromBTCSelfInitSwap");
|
|
5
|
+
const SwapType_1 = require("../../../../enums/SwapType");
|
|
6
|
+
const base_1 = require("@atomiqlabs/base");
|
|
7
|
+
const buffer_1 = require("buffer");
|
|
8
|
+
const Utils_1 = require("../../../../utils/Utils");
|
|
9
|
+
const BitcoinUtils_1 = require("../../../../utils/BitcoinUtils");
|
|
10
|
+
const IBitcoinWallet_1 = require("../../../../bitcoin/wallet/IBitcoinWallet");
|
|
11
|
+
const btc_signer_1 = require("@scure/btc-signer");
|
|
12
|
+
const SingleAddressBitcoinWallet_1 = require("../../../../bitcoin/wallet/SingleAddressBitcoinWallet");
|
|
13
|
+
const IEscrowSelfInitSwap_1 = require("../../IEscrowSelfInitSwap");
|
|
14
|
+
const TokenAmount_1 = require("../../../../types/TokenAmount");
|
|
15
|
+
const Token_1 = require("../../../../types/Token");
|
|
16
|
+
const Logger_1 = require("../../../../utils/Logger");
|
|
17
|
+
const BitcoinWalletUtils_1 = require("../../../../utils/BitcoinWalletUtils");
|
|
18
|
+
/**
|
|
19
|
+
* State enum for legacy escrow based Bitcoin -> Smart chain swaps.
|
|
20
|
+
*
|
|
21
|
+
* @category Swaps/Legacy/Bitcoin → Smart chain
|
|
22
|
+
*/
|
|
23
|
+
var FromBTCSwapState;
|
|
24
|
+
(function (FromBTCSwapState) {
|
|
25
|
+
/**
|
|
26
|
+
* Bitcoin swap address has expired and the intermediary (LP) has already refunded
|
|
27
|
+
* its funds. No BTC should be sent anymore!
|
|
28
|
+
*/
|
|
29
|
+
FromBTCSwapState[FromBTCSwapState["FAILED"] = -4] = "FAILED";
|
|
30
|
+
/**
|
|
31
|
+
* Bitcoin swap address has expired, user should not send any BTC anymore! Though
|
|
32
|
+
* the intermediary (LP) hasn't refunded yet. So if there is a transaction already
|
|
33
|
+
* in-flight the swap might still succeed.
|
|
34
|
+
*/
|
|
35
|
+
FromBTCSwapState[FromBTCSwapState["EXPIRED"] = -3] = "EXPIRED";
|
|
36
|
+
/**
|
|
37
|
+
* Swap has expired for good and there is no way how it can be executed anymore
|
|
38
|
+
*/
|
|
39
|
+
FromBTCSwapState[FromBTCSwapState["QUOTE_EXPIRED"] = -2] = "QUOTE_EXPIRED";
|
|
40
|
+
/**
|
|
41
|
+
* A swap is almost expired, and it should be presented to the user as expired, though
|
|
42
|
+
* there is still a chance that it will be processed
|
|
43
|
+
*/
|
|
44
|
+
FromBTCSwapState[FromBTCSwapState["QUOTE_SOFT_EXPIRED"] = -1] = "QUOTE_SOFT_EXPIRED";
|
|
45
|
+
/**
|
|
46
|
+
* Swap quote was created, use the {@link FromBTCSwap.commit} or {@link FromBTCSwap.txsCommit} functions
|
|
47
|
+
* to initiate it by creating the swap escrow on the destination smart chain
|
|
48
|
+
*/
|
|
49
|
+
FromBTCSwapState[FromBTCSwapState["PR_CREATED"] = 0] = "PR_CREATED";
|
|
50
|
+
/**
|
|
51
|
+
* Swap escrow was initiated (committed) on the destination chain, user can send the BTC to the
|
|
52
|
+
* swap address with the {@link FromBTCSwap.getFundedPsbt}, {@link FromBTCSwap.getAddress} or
|
|
53
|
+
* {@link FromBTCSwap.getHyperlink} functions.
|
|
54
|
+
*/
|
|
55
|
+
FromBTCSwapState[FromBTCSwapState["CLAIM_COMMITED"] = 1] = "CLAIM_COMMITED";
|
|
56
|
+
/**
|
|
57
|
+
* Input bitcoin transaction was confirmed, wait for automatic settlement by the watchtowers
|
|
58
|
+
* using the {@link FromBTCSwap.waitTillClaimed} function or settle manually using the {@link FromBTCSwap.claim}
|
|
59
|
+
* or {@link FromBTCSwap.txsClaim} function.
|
|
60
|
+
*/
|
|
61
|
+
FromBTCSwapState[FromBTCSwapState["BTC_TX_CONFIRMED"] = 2] = "BTC_TX_CONFIRMED";
|
|
62
|
+
/**
|
|
63
|
+
* Swap successfully settled and funds received on the destination chain
|
|
64
|
+
*/
|
|
65
|
+
FromBTCSwapState[FromBTCSwapState["CLAIM_CLAIMED"] = 3] = "CLAIM_CLAIMED";
|
|
66
|
+
})(FromBTCSwapState = exports.FromBTCSwapState || (exports.FromBTCSwapState = {}));
|
|
67
|
+
const FromBTCSwapStateDescription = {
|
|
68
|
+
[FromBTCSwapState.FAILED]: "Bitcoin swap address has expired and the intermediary (LP) has already refunded its funds. No BTC should be sent anymore!",
|
|
69
|
+
[FromBTCSwapState.EXPIRED]: "Bitcoin swap address has expired, user should not send any BTC anymore! Though the intermediary (LP) hasn't refunded yet. So if there is a transaction already in-flight the swap might still succeed.",
|
|
70
|
+
[FromBTCSwapState.QUOTE_EXPIRED]: "Swap has expired for good and there is no way how it can be executed anymore",
|
|
71
|
+
[FromBTCSwapState.QUOTE_SOFT_EXPIRED]: "The swap is expired, though there is still a chance that it will be processed",
|
|
72
|
+
[FromBTCSwapState.PR_CREATED]: "Swap quote was created, initiate it by creating the swap escrow on the destination smart chain",
|
|
73
|
+
[FromBTCSwapState.CLAIM_COMMITED]: "Swap escrow was initiated (committed) on the destination chain, user can send the BTC to the Bitcoin swap address.",
|
|
74
|
+
[FromBTCSwapState.BTC_TX_CONFIRMED]: "Input bitcoin transaction was confirmed, wait for automatic settlement by the watchtower or settle manually.",
|
|
75
|
+
[FromBTCSwapState.CLAIM_CLAIMED]: "Swap successfully settled and funds received on the destination chain"
|
|
76
|
+
};
|
|
77
|
+
function isFromBTCSwapInit(obj) {
|
|
78
|
+
return typeof (obj.data) === "object" &&
|
|
79
|
+
(obj.address == null || typeof (obj.address) === "string") &&
|
|
80
|
+
(obj.amount == null || typeof (obj.amount) === "bigint") &&
|
|
81
|
+
(obj.requiredConfirmations == null || typeof (obj.requiredConfirmations) === "number") &&
|
|
82
|
+
(0, IEscrowSelfInitSwap_1.isIEscrowSelfInitSwapInit)(obj);
|
|
83
|
+
}
|
|
84
|
+
exports.isFromBTCSwapInit = isFromBTCSwapInit;
|
|
85
|
+
/**
|
|
86
|
+
* Legacy escrow (PrTLC) based swap for Bitcoin -> Smart chains, requires manual initiation
|
|
87
|
+
* of the swap escrow on the destination chain.
|
|
88
|
+
*
|
|
89
|
+
* @category Swaps/Legacy/Bitcoin → Smart chain
|
|
90
|
+
*/
|
|
91
|
+
class FromBTCSwap extends IFromBTCSelfInitSwap_1.IFromBTCSelfInitSwap {
|
|
92
|
+
constructor(wrapper, initOrObject) {
|
|
93
|
+
if (isFromBTCSwapInit(initOrObject) && initOrObject.url != null)
|
|
94
|
+
initOrObject.url += "/frombtc";
|
|
95
|
+
super(wrapper, initOrObject);
|
|
96
|
+
this.TYPE = SwapType_1.SwapType.FROM_BTC;
|
|
97
|
+
/**
|
|
98
|
+
* @internal
|
|
99
|
+
*/
|
|
100
|
+
this.swapStateName = (state) => FromBTCSwapState[state];
|
|
101
|
+
/**
|
|
102
|
+
* @internal
|
|
103
|
+
*/
|
|
104
|
+
this.swapStateDescription = FromBTCSwapStateDescription;
|
|
105
|
+
/**
|
|
106
|
+
* @internal
|
|
107
|
+
*/
|
|
108
|
+
this.inputToken = Token_1.BitcoinTokens.BTC;
|
|
109
|
+
if (isFromBTCSwapInit(initOrObject)) {
|
|
110
|
+
this._state = FromBTCSwapState.PR_CREATED;
|
|
111
|
+
this._data = initOrObject.data;
|
|
112
|
+
this.feeRate = initOrObject.feeRate;
|
|
113
|
+
this.address = initOrObject.address;
|
|
114
|
+
this.amount = initOrObject.amount;
|
|
115
|
+
this.requiredConfirmations = initOrObject.requiredConfirmations;
|
|
116
|
+
}
|
|
117
|
+
else {
|
|
118
|
+
this.address = initOrObject.address;
|
|
119
|
+
this.amount = (0, Utils_1.toBigInt)(initOrObject.amount);
|
|
120
|
+
this.senderAddress = initOrObject.senderAddress;
|
|
121
|
+
this.txId = initOrObject.txId;
|
|
122
|
+
this.vout = initOrObject.vout;
|
|
123
|
+
this.requiredConfirmations = initOrObject.requiredConfirmations ?? this._data.getConfirmationsHint();
|
|
124
|
+
this.btcTxConfirmedAt = initOrObject.btcTxConfirmedAt;
|
|
125
|
+
}
|
|
126
|
+
this.tryRecomputeSwapPrice();
|
|
127
|
+
this.logger = (0, Logger_1.getLogger)("FromBTC(" + this.getIdentifierHashString() + "): ");
|
|
128
|
+
}
|
|
129
|
+
/**
|
|
130
|
+
* @inheritDoc
|
|
131
|
+
* @internal
|
|
132
|
+
*/
|
|
133
|
+
getSwapData() {
|
|
134
|
+
return this._data;
|
|
135
|
+
}
|
|
136
|
+
/**
|
|
137
|
+
* @inheritDoc
|
|
138
|
+
* @internal
|
|
139
|
+
*/
|
|
140
|
+
upgradeVersion() {
|
|
141
|
+
if (this.version == null) {
|
|
142
|
+
switch (this._state) {
|
|
143
|
+
case -2:
|
|
144
|
+
this._state = FromBTCSwapState.FAILED;
|
|
145
|
+
break;
|
|
146
|
+
case -1:
|
|
147
|
+
this._state = FromBTCSwapState.QUOTE_EXPIRED;
|
|
148
|
+
break;
|
|
149
|
+
case 0:
|
|
150
|
+
this._state = FromBTCSwapState.PR_CREATED;
|
|
151
|
+
break;
|
|
152
|
+
case 1:
|
|
153
|
+
this._state = FromBTCSwapState.CLAIM_COMMITED;
|
|
154
|
+
break;
|
|
155
|
+
case 2:
|
|
156
|
+
this._state = FromBTCSwapState.BTC_TX_CONFIRMED;
|
|
157
|
+
break;
|
|
158
|
+
case 3:
|
|
159
|
+
this._state = FromBTCSwapState.CLAIM_CLAIMED;
|
|
160
|
+
break;
|
|
161
|
+
}
|
|
162
|
+
this.version = 1;
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
//////////////////////////////
|
|
166
|
+
//// Getters & utils
|
|
167
|
+
/**
|
|
168
|
+
* Returns bitcoin address where the on-chain BTC should be sent to
|
|
169
|
+
*/
|
|
170
|
+
getAddress() {
|
|
171
|
+
if (this._state === FromBTCSwapState.PR_CREATED)
|
|
172
|
+
throw new Error("Cannot get bitcoin address of non-initiated swaps! Initiate swap first with commit() or txsCommit().");
|
|
173
|
+
return this.address ?? "";
|
|
174
|
+
}
|
|
175
|
+
/**
|
|
176
|
+
* Unsafe bitcoin hyperlink getter, returns the address even before the swap is committed!
|
|
177
|
+
*
|
|
178
|
+
* @private
|
|
179
|
+
*/
|
|
180
|
+
_getHyperlink() {
|
|
181
|
+
return this.address == null || this.amount == null ? "" : "bitcoin:" + this.address + "?amount=" + encodeURIComponent((Number(this.amount) / 100000000).toString(10));
|
|
182
|
+
}
|
|
183
|
+
/**
|
|
184
|
+
* @inheritDoc
|
|
185
|
+
*/
|
|
186
|
+
getHyperlink() {
|
|
187
|
+
if (this._state === FromBTCSwapState.PR_CREATED)
|
|
188
|
+
throw new Error("Cannot get bitcoin address of non-initiated swaps! Initiate swap first with commit() or txsCommit().");
|
|
189
|
+
return this._getHyperlink();
|
|
190
|
+
}
|
|
191
|
+
/**
|
|
192
|
+
* @inheritDoc
|
|
193
|
+
*/
|
|
194
|
+
getInputAddress() {
|
|
195
|
+
return this.senderAddress ?? null;
|
|
196
|
+
}
|
|
197
|
+
/**
|
|
198
|
+
* @inheritDoc
|
|
199
|
+
*/
|
|
200
|
+
getInputTxId() {
|
|
201
|
+
return this.txId ?? null;
|
|
202
|
+
}
|
|
203
|
+
/**
|
|
204
|
+
* Returns timeout time (in UNIX milliseconds) when the on-chain address will expire and no funds should be sent
|
|
205
|
+
* to that address anymore
|
|
206
|
+
*/
|
|
207
|
+
getTimeoutTime() {
|
|
208
|
+
return Number(this.wrapper._getOnchainSendTimeout(this._data, this.requiredConfirmations ?? 6)) * 1000;
|
|
209
|
+
}
|
|
210
|
+
/**
|
|
211
|
+
* @inheritDoc
|
|
212
|
+
*/
|
|
213
|
+
requiresAction() {
|
|
214
|
+
return this.isClaimable() || (this._state === FromBTCSwapState.CLAIM_COMMITED && this.getTimeoutTime() > Date.now() && this.txId == null);
|
|
215
|
+
}
|
|
216
|
+
/**
|
|
217
|
+
* @inheritDoc
|
|
218
|
+
*/
|
|
219
|
+
isFinished() {
|
|
220
|
+
return this._state === FromBTCSwapState.CLAIM_CLAIMED || this._state === FromBTCSwapState.QUOTE_EXPIRED || this._state === FromBTCSwapState.FAILED;
|
|
221
|
+
}
|
|
222
|
+
/**
|
|
223
|
+
* @inheritDoc
|
|
224
|
+
*/
|
|
225
|
+
isClaimable() {
|
|
226
|
+
return this._state === FromBTCSwapState.BTC_TX_CONFIRMED;
|
|
227
|
+
}
|
|
228
|
+
/**
|
|
229
|
+
* @inheritDoc
|
|
230
|
+
*/
|
|
231
|
+
isSuccessful() {
|
|
232
|
+
return this._state === FromBTCSwapState.CLAIM_CLAIMED;
|
|
233
|
+
}
|
|
234
|
+
/**
|
|
235
|
+
* @inheritDoc
|
|
236
|
+
*/
|
|
237
|
+
isFailed() {
|
|
238
|
+
return this._state === FromBTCSwapState.FAILED || this._state === FromBTCSwapState.EXPIRED;
|
|
239
|
+
}
|
|
240
|
+
/**
|
|
241
|
+
* @inheritDoc
|
|
242
|
+
*/
|
|
243
|
+
isInProgress() {
|
|
244
|
+
return this._state === FromBTCSwapState.CLAIM_COMMITED ||
|
|
245
|
+
this._state === FromBTCSwapState.BTC_TX_CONFIRMED;
|
|
246
|
+
}
|
|
247
|
+
/**
|
|
248
|
+
* @inheritDoc
|
|
249
|
+
*/
|
|
250
|
+
isQuoteExpired() {
|
|
251
|
+
return this._state === FromBTCSwapState.QUOTE_EXPIRED;
|
|
252
|
+
}
|
|
253
|
+
/**
|
|
254
|
+
* @inheritDoc
|
|
255
|
+
*/
|
|
256
|
+
isQuoteSoftExpired() {
|
|
257
|
+
return this._state === FromBTCSwapState.QUOTE_EXPIRED || this._state === FromBTCSwapState.QUOTE_SOFT_EXPIRED;
|
|
258
|
+
}
|
|
259
|
+
/**
|
|
260
|
+
* @inheritDoc
|
|
261
|
+
* @internal
|
|
262
|
+
*/
|
|
263
|
+
canCommit(skipQuoteExpiryChecks) {
|
|
264
|
+
if (this._state !== FromBTCSwapState.PR_CREATED && (!skipQuoteExpiryChecks || this._state !== FromBTCSwapState.QUOTE_SOFT_EXPIRED))
|
|
265
|
+
return false;
|
|
266
|
+
if (this.requiredConfirmations == null)
|
|
267
|
+
return false;
|
|
268
|
+
const expiry = this.wrapper._getOnchainSendTimeout(this._data, this.requiredConfirmations);
|
|
269
|
+
const currentTimestamp = BigInt(Math.floor(Date.now() / 1000));
|
|
270
|
+
return (expiry - currentTimestamp) >= this.wrapper._options.minSendWindow;
|
|
271
|
+
}
|
|
272
|
+
//////////////////////////////
|
|
273
|
+
//// Amounts & fees
|
|
274
|
+
/**
|
|
275
|
+
* @inheritDoc
|
|
276
|
+
*/
|
|
277
|
+
getInputToken() {
|
|
278
|
+
return Token_1.BitcoinTokens.BTC;
|
|
279
|
+
}
|
|
280
|
+
/**
|
|
281
|
+
* @inheritDoc
|
|
282
|
+
*/
|
|
283
|
+
getInput() {
|
|
284
|
+
return (0, TokenAmount_1.toTokenAmount)(this.amount ?? null, this.inputToken, this.wrapper._prices);
|
|
285
|
+
}
|
|
286
|
+
/**
|
|
287
|
+
* Returns claimer bounty, acting as a reward for watchtowers to claim the swap automatically,
|
|
288
|
+
* this amount is pre-funded by the user on the destination chain when the swap escrow
|
|
289
|
+
* is initiated. For total pre-funded deposit amount see {@link getTotalDeposit}.
|
|
290
|
+
*/
|
|
291
|
+
getClaimerBounty() {
|
|
292
|
+
return (0, TokenAmount_1.toTokenAmount)(this._data.getClaimerBounty(), this.wrapper._tokens[this._data.getDepositToken()], this.wrapper._prices);
|
|
293
|
+
}
|
|
294
|
+
//////////////////////////////
|
|
295
|
+
//// Bitcoin tx
|
|
296
|
+
/**
|
|
297
|
+
* If the required number of confirmations is not known, this function tries to infer it by looping through
|
|
298
|
+
* possible confirmation targets and comparing the claim hashes
|
|
299
|
+
*
|
|
300
|
+
* @param btcTx Bitcoin transaction
|
|
301
|
+
* @param vout Output index of the desired output in the bitcoin transaction
|
|
302
|
+
*
|
|
303
|
+
* @private
|
|
304
|
+
*/
|
|
305
|
+
inferRequiredConfirmationsCount(btcTx, vout) {
|
|
306
|
+
const txOut = btcTx.outs[vout];
|
|
307
|
+
for (let i = 1; i <= 20; i++) {
|
|
308
|
+
const computedClaimHash = this.wrapper._contract.getHashForOnchain(buffer_1.Buffer.from(txOut.scriptPubKey.hex, "hex"), BigInt(txOut.value), i);
|
|
309
|
+
if (computedClaimHash.toString("hex") === this._data.getClaimHash()) {
|
|
310
|
+
return i;
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
/**
|
|
315
|
+
* @inheritDoc
|
|
316
|
+
*/
|
|
317
|
+
getRequiredConfirmationsCount() {
|
|
318
|
+
return this.requiredConfirmations ?? NaN;
|
|
319
|
+
}
|
|
320
|
+
/**
|
|
321
|
+
* Checks whether a bitcoin payment was already made, returns the payment or `null` when no payment has been made.
|
|
322
|
+
*
|
|
323
|
+
* @internal
|
|
324
|
+
*/
|
|
325
|
+
async getBitcoinPayment() {
|
|
326
|
+
const txoHashHint = this._data.getTxoHashHint();
|
|
327
|
+
if (txoHashHint == null)
|
|
328
|
+
throw new Error("Swap data doesn't include the txo hash hint! Cannot check bitcoin transaction!");
|
|
329
|
+
if (this.address == null)
|
|
330
|
+
throw new Error("Cannot check bitcoin payment, because the address is not known! This can happen after a swap is recovered.");
|
|
331
|
+
const result = await this.wrapper._btcRpc.checkAddressTxos(this.address, buffer_1.Buffer.from(txoHashHint, "hex"));
|
|
332
|
+
if (result == null)
|
|
333
|
+
return null;
|
|
334
|
+
if (this.requiredConfirmations == null) {
|
|
335
|
+
this.requiredConfirmations = this.inferRequiredConfirmationsCount(result.tx, result.vout);
|
|
336
|
+
}
|
|
337
|
+
return {
|
|
338
|
+
inputAddresses: result.tx.inputAddresses,
|
|
339
|
+
txId: result.tx.txid,
|
|
340
|
+
vout: result.vout,
|
|
341
|
+
confirmations: result.tx.confirmations ?? 0,
|
|
342
|
+
targetConfirmations: this.getRequiredConfirmationsCount()
|
|
343
|
+
};
|
|
344
|
+
}
|
|
345
|
+
/**
|
|
346
|
+
* Used to set the txId of the bitcoin payment from the on-chain events listener
|
|
347
|
+
*
|
|
348
|
+
* @param txId Transaction ID that settled the swap on the smart chain
|
|
349
|
+
*
|
|
350
|
+
* @internal
|
|
351
|
+
*/
|
|
352
|
+
async _setBitcoinTxId(txId) {
|
|
353
|
+
if (this.txId !== txId || this.address == null || this.vout == null || this.senderAddress == null || this.amount == null) {
|
|
354
|
+
const btcTx = await this.wrapper._btcRpc.getTransaction(txId);
|
|
355
|
+
if (btcTx == null)
|
|
356
|
+
return;
|
|
357
|
+
const txoHashHint = this._data.getTxoHashHint();
|
|
358
|
+
if (txoHashHint != null) {
|
|
359
|
+
const expectedTxoHash = buffer_1.Buffer.from(txoHashHint, "hex");
|
|
360
|
+
const vout = btcTx.outs.findIndex(out => (0, Utils_1.getTxoHash)(out.scriptPubKey.hex, out.value).equals(expectedTxoHash));
|
|
361
|
+
if (vout !== -1) {
|
|
362
|
+
this.vout = vout;
|
|
363
|
+
//If amount or address are not known, parse them from the bitcoin tx
|
|
364
|
+
// this can happen if the swap is recovered from on-chain data and
|
|
365
|
+
// hence doesn't contain the address and amount data
|
|
366
|
+
if (this.amount == null)
|
|
367
|
+
this.amount = BigInt(btcTx.outs[vout].value);
|
|
368
|
+
if (this.address == null)
|
|
369
|
+
try {
|
|
370
|
+
this.address = (0, BitcoinUtils_1.fromOutputScript)(this.wrapper._options.bitcoinNetwork, btcTx.outs[vout].scriptPubKey.hex);
|
|
371
|
+
}
|
|
372
|
+
catch (e) {
|
|
373
|
+
this.logger.warn("_setBitcoinTxId(): Failed to parse address from output script: ", e);
|
|
374
|
+
}
|
|
375
|
+
if (this.requiredConfirmations == null) {
|
|
376
|
+
this.requiredConfirmations = this.inferRequiredConfirmationsCount(btcTx, vout);
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
if (btcTx.inputAddresses != null) {
|
|
381
|
+
this.senderAddress = btcTx.inputAddresses[0];
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
this.txId = txId;
|
|
385
|
+
}
|
|
386
|
+
/**
|
|
387
|
+
* @inheritDoc
|
|
388
|
+
*
|
|
389
|
+
* @throws {Error} if in invalid state (must be {@link FromBTCSwapState.CLAIM_COMMITED})
|
|
390
|
+
*/
|
|
391
|
+
async waitForBitcoinTransaction(updateCallback, checkIntervalSeconds, abortSignal) {
|
|
392
|
+
if (this._state !== FromBTCSwapState.CLAIM_COMMITED && this._state !== FromBTCSwapState.EXPIRED)
|
|
393
|
+
throw new Error("Must be in COMMITED state!");
|
|
394
|
+
const txoHashHint = this._data.getTxoHashHint();
|
|
395
|
+
if (txoHashHint == null)
|
|
396
|
+
throw new Error("Swap data doesn't include the txo hash hint! Cannot check bitcoin transaction!");
|
|
397
|
+
if (this.address == null)
|
|
398
|
+
throw new Error("Cannot check bitcoin payment, because the address is not known! This can happen after a swap is recovered.");
|
|
399
|
+
let abortedDueToEnoughConfirmationsResult;
|
|
400
|
+
const abortController = (0, Utils_1.extendAbortController)(abortSignal);
|
|
401
|
+
const result = await this.wrapper._btcRpc.waitForAddressTxo(this.address, buffer_1.Buffer.from(txoHashHint, "hex"), this.requiredConfirmations ?? 6, //In case confirmation count is not known, we use a conservative estimate
|
|
402
|
+
(btcTx, vout, txEtaMs) => {
|
|
403
|
+
let requiredConfirmations = this.requiredConfirmations;
|
|
404
|
+
if (btcTx != null && vout != null && requiredConfirmations == null) {
|
|
405
|
+
requiredConfirmations = this.inferRequiredConfirmationsCount(btcTx, vout);
|
|
406
|
+
}
|
|
407
|
+
if (btcTx != null && (btcTx.txid !== this.txId || (this.requiredConfirmations == null && requiredConfirmations != null))) {
|
|
408
|
+
this.txId = btcTx.txid;
|
|
409
|
+
this.vout = vout;
|
|
410
|
+
this.requiredConfirmations = requiredConfirmations;
|
|
411
|
+
if (btcTx.inputAddresses != null)
|
|
412
|
+
this.senderAddress = btcTx.inputAddresses[0];
|
|
413
|
+
this._saveAndEmit().catch(e => {
|
|
414
|
+
this.logger.error("waitForBitcoinTransaction(): Failed to save swap from within waitForAddressTxo callback:", e);
|
|
415
|
+
});
|
|
416
|
+
}
|
|
417
|
+
//Abort the loop as soon as the transaction gets enough confirmations, this is required in case
|
|
418
|
+
// we pass a default 6 confirmations to the fn, but then are able to infer the actual confirmation
|
|
419
|
+
// target from the prior block
|
|
420
|
+
if (btcTx?.confirmations != null && requiredConfirmations != null && requiredConfirmations <= btcTx.confirmations && vout != null) {
|
|
421
|
+
abortedDueToEnoughConfirmationsResult = {
|
|
422
|
+
tx: btcTx,
|
|
423
|
+
vout
|
|
424
|
+
};
|
|
425
|
+
abortController.abort();
|
|
426
|
+
return;
|
|
427
|
+
}
|
|
428
|
+
if (updateCallback != null)
|
|
429
|
+
updateCallback(btcTx?.txid, btcTx == null ? undefined : (btcTx?.confirmations ?? 0), requiredConfirmations ?? NaN, txEtaMs);
|
|
430
|
+
}, abortController.signal, checkIntervalSeconds).catch(e => {
|
|
431
|
+
//We catch the case when the loop was aborted due to the transaction getting enough confirmations
|
|
432
|
+
if (abortedDueToEnoughConfirmationsResult != null)
|
|
433
|
+
return abortedDueToEnoughConfirmationsResult;
|
|
434
|
+
throw e;
|
|
435
|
+
});
|
|
436
|
+
if (abortSignal != null)
|
|
437
|
+
abortSignal.throwIfAborted();
|
|
438
|
+
this.txId = result.tx.txid;
|
|
439
|
+
this.vout = result.vout;
|
|
440
|
+
if (result.tx.inputAddresses != null)
|
|
441
|
+
this.senderAddress = result.tx.inputAddresses[0];
|
|
442
|
+
if (this._state !== FromBTCSwapState.CLAIM_CLAIMED &&
|
|
443
|
+
this._state !== FromBTCSwapState.FAILED) {
|
|
444
|
+
this.btcTxConfirmedAt ??= Date.now();
|
|
445
|
+
this._state = FromBTCSwapState.BTC_TX_CONFIRMED;
|
|
446
|
+
}
|
|
447
|
+
await this._saveAndEmit();
|
|
448
|
+
return result.tx.txid;
|
|
449
|
+
}
|
|
450
|
+
/**
|
|
451
|
+
* Private getter of the funded PSBT that doesn't check current state
|
|
452
|
+
*
|
|
453
|
+
* @param _bitcoinWallet Bitcoin wallet to fund the PSBT with
|
|
454
|
+
* @param feeRate Optional bitcoin fee rate in sats/vB
|
|
455
|
+
* @param additionalOutputs Optional additional outputs that should also be included in the generated PSBT
|
|
456
|
+
*
|
|
457
|
+
* @private
|
|
458
|
+
*/
|
|
459
|
+
async _getFundedPsbt(_bitcoinWallet, feeRate, additionalOutputs) {
|
|
460
|
+
if (this.address == null)
|
|
461
|
+
throw new Error("Cannot create funded PSBT, because the address is not known! This can happen after a swap is recovered.");
|
|
462
|
+
let bitcoinWallet;
|
|
463
|
+
if ((0, IBitcoinWallet_1.isIBitcoinWallet)(_bitcoinWallet)) {
|
|
464
|
+
bitcoinWallet = _bitcoinWallet;
|
|
465
|
+
}
|
|
466
|
+
else {
|
|
467
|
+
bitcoinWallet = new SingleAddressBitcoinWallet_1.SingleAddressBitcoinWallet(this.wrapper._btcRpc, this.wrapper._options.bitcoinNetwork, _bitcoinWallet);
|
|
468
|
+
}
|
|
469
|
+
//TODO: Maybe re-introduce fee rate check here if passed from the user
|
|
470
|
+
if (feeRate == null) {
|
|
471
|
+
feeRate = await bitcoinWallet.getFeeRate();
|
|
472
|
+
}
|
|
473
|
+
const basePsbt = new btc_signer_1.Transaction({
|
|
474
|
+
allowUnknownOutputs: true,
|
|
475
|
+
allowLegacyWitnessUtxo: true
|
|
476
|
+
});
|
|
477
|
+
basePsbt.addOutput({
|
|
478
|
+
amount: this.amount,
|
|
479
|
+
script: (0, BitcoinUtils_1.toOutputScript)(this.wrapper._options.bitcoinNetwork, this.address)
|
|
480
|
+
});
|
|
481
|
+
if (additionalOutputs != null)
|
|
482
|
+
additionalOutputs.forEach(output => {
|
|
483
|
+
basePsbt.addOutput({
|
|
484
|
+
amount: output.amount,
|
|
485
|
+
script: output.outputScript ?? (0, BitcoinUtils_1.toOutputScript)(this.wrapper._options.bitcoinNetwork, output.address)
|
|
486
|
+
});
|
|
487
|
+
});
|
|
488
|
+
const psbt = await bitcoinWallet.fundPsbt(basePsbt, feeRate);
|
|
489
|
+
//Sign every input
|
|
490
|
+
const signInputs = [];
|
|
491
|
+
for (let i = 0; i < psbt.inputsLength; i++) {
|
|
492
|
+
signInputs.push(i);
|
|
493
|
+
}
|
|
494
|
+
const serializedPsbt = buffer_1.Buffer.from(psbt.toPSBT());
|
|
495
|
+
return {
|
|
496
|
+
psbt,
|
|
497
|
+
psbtHex: serializedPsbt.toString("hex"),
|
|
498
|
+
psbtBase64: serializedPsbt.toString("base64"),
|
|
499
|
+
signInputs
|
|
500
|
+
};
|
|
501
|
+
}
|
|
502
|
+
/**
|
|
503
|
+
* @inheritDoc
|
|
504
|
+
*/
|
|
505
|
+
getFundedPsbt(_bitcoinWallet, feeRate, additionalOutputs) {
|
|
506
|
+
if (this._state !== FromBTCSwapState.CLAIM_COMMITED)
|
|
507
|
+
throw new Error("Swap not committed yet, please initiate the swap first with commit() call!");
|
|
508
|
+
return this._getFundedPsbt(_bitcoinWallet, feeRate, additionalOutputs);
|
|
509
|
+
}
|
|
510
|
+
/**
|
|
511
|
+
* @inheritDoc
|
|
512
|
+
*
|
|
513
|
+
* @throws {Error} if the swap is in invalid state (not in {@link FromBTCSwapState.CLAIM_COMMITED}), or if
|
|
514
|
+
* the swap bitcoin address already expired.
|
|
515
|
+
*/
|
|
516
|
+
async submitPsbt(_psbt) {
|
|
517
|
+
const psbt = (0, BitcoinUtils_1.parsePsbtTransaction)(_psbt);
|
|
518
|
+
if (this._state !== FromBTCSwapState.CLAIM_COMMITED)
|
|
519
|
+
throw new Error("Swap not committed yet, please initiate the swap first with commit() call!");
|
|
520
|
+
//Ensure not expired
|
|
521
|
+
if (this.getTimeoutTime() < Date.now()) {
|
|
522
|
+
throw new Error("Swap address expired!");
|
|
523
|
+
}
|
|
524
|
+
const output0 = psbt.getOutput(0);
|
|
525
|
+
if (this.amount != null && output0.amount !== this.amount)
|
|
526
|
+
throw new Error("PSBT output amount invalid, expected: " + this.amount + " got: " + output0.amount);
|
|
527
|
+
if (this.address != null) {
|
|
528
|
+
const expectedOutputScript = (0, BitcoinUtils_1.toOutputScript)(this.wrapper._options.bitcoinNetwork, this.address);
|
|
529
|
+
if (output0.script == null || !expectedOutputScript.equals(output0.script))
|
|
530
|
+
throw new Error("PSBT output script invalid!");
|
|
531
|
+
}
|
|
532
|
+
if (!psbt.isFinal)
|
|
533
|
+
psbt.finalize();
|
|
534
|
+
return await this.wrapper._btcRpc.sendRawTransaction(buffer_1.Buffer.from(psbt.toBytes(true, true)).toString("hex"));
|
|
535
|
+
}
|
|
536
|
+
/**
|
|
537
|
+
* @inheritDoc
|
|
538
|
+
*/
|
|
539
|
+
async estimateBitcoinFee(_bitcoinWallet, feeRate) {
|
|
540
|
+
if (this.address == null || this.amount == null)
|
|
541
|
+
return null;
|
|
542
|
+
const bitcoinWallet = (0, BitcoinWalletUtils_1.toBitcoinWallet)(_bitcoinWallet, this.wrapper._btcRpc, this.wrapper._options.bitcoinNetwork);
|
|
543
|
+
const txFee = await bitcoinWallet.getTransactionFee(this.address, this.amount, feeRate);
|
|
544
|
+
if (txFee == null)
|
|
545
|
+
return null;
|
|
546
|
+
return (0, TokenAmount_1.toTokenAmount)(BigInt(txFee), Token_1.BitcoinTokens.BTC, this.wrapper._prices);
|
|
547
|
+
}
|
|
548
|
+
/**
|
|
549
|
+
* @inheritDoc
|
|
550
|
+
*/
|
|
551
|
+
async sendBitcoinTransaction(wallet, feeRate) {
|
|
552
|
+
if (this.address == null || this.amount == null)
|
|
553
|
+
throw new Error("Cannot send bitcoin transaction, because the address is not known! This can happen after a swap is recovered.");
|
|
554
|
+
if (this._state !== FromBTCSwapState.CLAIM_COMMITED)
|
|
555
|
+
throw new Error("Swap not committed yet, please initiate the swap first with commit() call!");
|
|
556
|
+
//Ensure not expired
|
|
557
|
+
if (this.getTimeoutTime() < Date.now()) {
|
|
558
|
+
throw new Error("Swap address expired!");
|
|
559
|
+
}
|
|
560
|
+
if ((0, IBitcoinWallet_1.isIBitcoinWallet)(wallet)) {
|
|
561
|
+
return await wallet.sendTransaction(this.address, this.amount, feeRate);
|
|
562
|
+
}
|
|
563
|
+
else {
|
|
564
|
+
const { psbt, psbtHex, psbtBase64, signInputs } = await this.getFundedPsbt(wallet, feeRate);
|
|
565
|
+
const signedPsbt = await wallet.signPsbt({
|
|
566
|
+
psbt, psbtHex, psbtBase64
|
|
567
|
+
}, signInputs);
|
|
568
|
+
return await this.submitPsbt(signedPsbt);
|
|
569
|
+
}
|
|
570
|
+
}
|
|
571
|
+
//////////////////////////////
|
|
572
|
+
//// Execution
|
|
573
|
+
/**
|
|
574
|
+
* Executes the swap with the provided bitcoin wallet,
|
|
575
|
+
*
|
|
576
|
+
* @param dstSigner Signer on the destination network, needs to have the same address as the one specified when
|
|
577
|
+
* quote was created, this is required for legacy swaps because the destination wallet needs to actively open
|
|
578
|
+
* a bitcoin swap address to which the BTC is then sent, this means that the address also needs to have enough
|
|
579
|
+
* native tokens to pay for gas on the destination network
|
|
580
|
+
* @param wallet Bitcoin wallet to use to sign the bitcoin transaction, can also be null - then the execution waits
|
|
581
|
+
* till a transaction is received from an external wallet
|
|
582
|
+
* @param callbacks Callbacks to track the progress of the swap
|
|
583
|
+
* @param options Optional options for the swap like feeRate, AbortSignal, and timeouts/intervals
|
|
584
|
+
*
|
|
585
|
+
* @returns {boolean} Whether a swap was settled automatically by swap watchtowers or requires manual claim by the
|
|
586
|
+
* user, in case `false` is returned the user should call `swap.claim()` to settle the swap on the destination manually
|
|
587
|
+
*/
|
|
588
|
+
async execute(dstSigner, wallet, callbacks, options) {
|
|
589
|
+
if (this._state === FromBTCSwapState.FAILED)
|
|
590
|
+
throw new Error("Swap failed!");
|
|
591
|
+
if (this._state === FromBTCSwapState.EXPIRED)
|
|
592
|
+
throw new Error("Swap address expired!");
|
|
593
|
+
if (this._state === FromBTCSwapState.QUOTE_EXPIRED || this._state === FromBTCSwapState.QUOTE_SOFT_EXPIRED)
|
|
594
|
+
throw new Error("Swap quote expired!");
|
|
595
|
+
if (this._state === FromBTCSwapState.CLAIM_CLAIMED)
|
|
596
|
+
throw new Error("Swap already settled!");
|
|
597
|
+
if (this._state === FromBTCSwapState.PR_CREATED) {
|
|
598
|
+
await this.commit(dstSigner, options?.abortSignal, undefined, callbacks?.onDestinationCommitSent);
|
|
599
|
+
}
|
|
600
|
+
if (this._state === FromBTCSwapState.CLAIM_COMMITED) {
|
|
601
|
+
if (wallet != null) {
|
|
602
|
+
const bitcoinPaymentSent = await this.getBitcoinPayment();
|
|
603
|
+
if (bitcoinPaymentSent == null) {
|
|
604
|
+
//Send btc tx
|
|
605
|
+
const txId = await this.sendBitcoinTransaction(wallet, options?.feeRate);
|
|
606
|
+
if (callbacks?.onSourceTransactionSent != null)
|
|
607
|
+
callbacks.onSourceTransactionSent(txId);
|
|
608
|
+
}
|
|
609
|
+
}
|
|
610
|
+
const txId = await this.waitForBitcoinTransaction(callbacks?.onSourceTransactionConfirmationStatus, options?.btcTxCheckIntervalSeconds, options?.abortSignal);
|
|
611
|
+
if (callbacks?.onSourceTransactionConfirmed != null)
|
|
612
|
+
callbacks.onSourceTransactionConfirmed(txId);
|
|
613
|
+
}
|
|
614
|
+
// @ts-ignore
|
|
615
|
+
if (this._state === FromBTCSwapState.CLAIM_CLAIMED)
|
|
616
|
+
return true;
|
|
617
|
+
if (this._state === FromBTCSwapState.BTC_TX_CONFIRMED) {
|
|
618
|
+
const success = await this.waitTillClaimed(options?.maxWaitTillAutomaticSettlementSeconds ?? 60, options?.abortSignal);
|
|
619
|
+
if (success && callbacks?.onSwapSettled != null)
|
|
620
|
+
callbacks.onSwapSettled(this.getOutputTxId());
|
|
621
|
+
return success;
|
|
622
|
+
}
|
|
623
|
+
throw new Error("Invalid state reached!");
|
|
624
|
+
}
|
|
625
|
+
/**
|
|
626
|
+
* @inheritDoc
|
|
627
|
+
*
|
|
628
|
+
* @param options.bitcoinFeeRate Optional fee rate to use for the created Bitcoin transaction
|
|
629
|
+
* @param options.bitcoinWallet Bitcoin wallet to use, when provided the function returns a funded
|
|
630
|
+
* psbt (`"FUNDED_PSBT"`), if not passed just a bitcoin receive address is returned (`"ADDRESS"`)
|
|
631
|
+
* @param options.skipChecks Skip checks like making sure init signature is still valid and swap
|
|
632
|
+
* wasn't commited yet (this is handled on swap creation, if you commit right after quoting, you
|
|
633
|
+
* can use `skipChecks=true`)
|
|
634
|
+
*
|
|
635
|
+
* @throws {Error} if the swap or quote is expired, or if triggered in invalid state
|
|
636
|
+
*/
|
|
637
|
+
async txsExecute(options) {
|
|
638
|
+
if (this._state === FromBTCSwapState.PR_CREATED) {
|
|
639
|
+
if (!await this._verifyQuoteValid())
|
|
640
|
+
throw new Error("Quote already expired or close to expiry!");
|
|
641
|
+
if (this.getTimeoutTime() < Date.now())
|
|
642
|
+
throw new Error("Swap address already expired or close to expiry!");
|
|
643
|
+
return [
|
|
644
|
+
{
|
|
645
|
+
name: "Commit",
|
|
646
|
+
description: `Opens up the bitcoin swap address on the ${this.chainIdentifier} side`,
|
|
647
|
+
chain: this.chainIdentifier,
|
|
648
|
+
txs: await this.txsCommit(options?.skipChecks)
|
|
649
|
+
},
|
|
650
|
+
{
|
|
651
|
+
name: "Payment",
|
|
652
|
+
description: "Send funds to the bitcoin swap address",
|
|
653
|
+
chain: "BITCOIN",
|
|
654
|
+
txs: [
|
|
655
|
+
options?.bitcoinWallet == null ? {
|
|
656
|
+
address: this.address ?? "",
|
|
657
|
+
amount: Number(this.amount),
|
|
658
|
+
hyperlink: this._getHyperlink(),
|
|
659
|
+
type: "ADDRESS"
|
|
660
|
+
} : {
|
|
661
|
+
...await this.getFundedPsbt(options.bitcoinWallet),
|
|
662
|
+
type: "FUNDED_PSBT"
|
|
663
|
+
}
|
|
664
|
+
]
|
|
665
|
+
}
|
|
666
|
+
];
|
|
667
|
+
}
|
|
668
|
+
if (this._state === FromBTCSwapState.CLAIM_COMMITED) {
|
|
669
|
+
if (this.getTimeoutTime() < Date.now())
|
|
670
|
+
throw new Error("Swap address already expired or close to expiry!");
|
|
671
|
+
return [
|
|
672
|
+
{
|
|
673
|
+
name: "Payment",
|
|
674
|
+
description: "Send funds to the bitcoin swap address",
|
|
675
|
+
chain: "BITCOIN",
|
|
676
|
+
txs: [
|
|
677
|
+
options?.bitcoinWallet == null ? {
|
|
678
|
+
address: this.getAddress(),
|
|
679
|
+
amount: Number(this.amount),
|
|
680
|
+
hyperlink: this._getHyperlink(),
|
|
681
|
+
type: "ADDRESS"
|
|
682
|
+
} : {
|
|
683
|
+
...await this.getFundedPsbt(options.bitcoinWallet),
|
|
684
|
+
type: "FUNDED_PSBT"
|
|
685
|
+
}
|
|
686
|
+
]
|
|
687
|
+
}
|
|
688
|
+
];
|
|
689
|
+
}
|
|
690
|
+
throw new Error("Invalid swap state to obtain execution txns, required PR_CREATED or CLAIM_COMMITED");
|
|
691
|
+
}
|
|
692
|
+
/**
|
|
693
|
+
* @inheritDoc
|
|
694
|
+
*
|
|
695
|
+
* @param options.bitcoinFeeRate Optional fee rate to use for the created Bitcoin transaction
|
|
696
|
+
* @param options.bitcoinWallet Bitcoin wallet to use, when provided the function returns a funded
|
|
697
|
+
* psbt (`"FUNDED_PSBT"`), if not passed just a bitcoin receive address is returned (`"ADDRESS"`)
|
|
698
|
+
* @param options.skipChecks Skip checks like making sure init signature is still valid and swap
|
|
699
|
+
* wasn't commited yet (this is handled on swap creation, if you commit right after quoting, you
|
|
700
|
+
* can use `skipChecks=true`)
|
|
701
|
+
* @param options.manualSettlementSmartChainSigner Optional smart chain signer to create a manual claim (settlement) transaction
|
|
702
|
+
* @param options.maxWaitTillAutomaticSettlementSeconds Maximum time to wait for an automatic settlement after
|
|
703
|
+
* the bitcoin transaction is confirmed (defaults to 60 seconds)
|
|
704
|
+
*/
|
|
705
|
+
async getCurrentActions(options) {
|
|
706
|
+
if (this._state === FromBTCSwapState.PR_CREATED || this._state === FromBTCSwapState.CLAIM_COMMITED) {
|
|
707
|
+
try {
|
|
708
|
+
return await this.txsExecute(options);
|
|
709
|
+
}
|
|
710
|
+
catch (e) { }
|
|
711
|
+
}
|
|
712
|
+
if (this.isClaimable()) {
|
|
713
|
+
if (this.btcTxConfirmedAt == null ||
|
|
714
|
+
options?.maxWaitTillAutomaticSettlementSeconds === 0 ||
|
|
715
|
+
(Date.now() - this.btcTxConfirmedAt) > (options?.maxWaitTillAutomaticSettlementSeconds ?? 60) * 1000) {
|
|
716
|
+
return [{
|
|
717
|
+
name: "Claim",
|
|
718
|
+
description: "Manually settle (claim) the swap on the destination smart chain",
|
|
719
|
+
chain: this.chainIdentifier,
|
|
720
|
+
txs: await this.txsClaim(options?.manualSettlementSmartChainSigner)
|
|
721
|
+
}];
|
|
722
|
+
}
|
|
723
|
+
}
|
|
724
|
+
return [];
|
|
725
|
+
}
|
|
726
|
+
//////////////////////////////
|
|
727
|
+
//// Commit
|
|
728
|
+
/**
|
|
729
|
+
* @inheritDoc
|
|
730
|
+
*
|
|
731
|
+
* @throws {Error} If invalid signer is provided that doesn't match the swap data
|
|
732
|
+
*/
|
|
733
|
+
async commit(_signer, abortSignal, skipChecks, onBeforeTxSent) {
|
|
734
|
+
const signer = (0, base_1.isAbstractSigner)(_signer) ? _signer : await this.wrapper._chain.wrapSigner(_signer);
|
|
735
|
+
this.checkSigner(signer);
|
|
736
|
+
let txCount = 0;
|
|
737
|
+
const txs = await this.txsCommit(skipChecks);
|
|
738
|
+
const result = await this.wrapper._chain.sendAndConfirm(signer, txs, true, abortSignal, undefined, (txId) => {
|
|
739
|
+
txCount++;
|
|
740
|
+
if (onBeforeTxSent != null && txCount === txs.length)
|
|
741
|
+
onBeforeTxSent(txId);
|
|
742
|
+
return Promise.resolve();
|
|
743
|
+
});
|
|
744
|
+
this._commitTxId = result[result.length - 1];
|
|
745
|
+
if (this._state === FromBTCSwapState.PR_CREATED || this._state === FromBTCSwapState.QUOTE_SOFT_EXPIRED || this._state === FromBTCSwapState.QUOTE_EXPIRED) {
|
|
746
|
+
await this._saveAndEmit(FromBTCSwapState.CLAIM_COMMITED);
|
|
747
|
+
}
|
|
748
|
+
return this._commitTxId;
|
|
749
|
+
}
|
|
750
|
+
/**
|
|
751
|
+
* @inheritDoc
|
|
752
|
+
*/
|
|
753
|
+
async waitTillCommited(abortSignal) {
|
|
754
|
+
if (this._state === FromBTCSwapState.CLAIM_COMMITED || this._state === FromBTCSwapState.CLAIM_CLAIMED)
|
|
755
|
+
return Promise.resolve();
|
|
756
|
+
if (this._state !== FromBTCSwapState.PR_CREATED && this._state !== FromBTCSwapState.QUOTE_SOFT_EXPIRED)
|
|
757
|
+
throw new Error("Invalid state");
|
|
758
|
+
const abortController = (0, Utils_1.extendAbortController)(abortSignal);
|
|
759
|
+
const result = await Promise.race([
|
|
760
|
+
this.watchdogWaitTillCommited(undefined, abortController.signal),
|
|
761
|
+
this.waitTillState(FromBTCSwapState.CLAIM_COMMITED, "gte", abortController.signal).then(() => 0)
|
|
762
|
+
]);
|
|
763
|
+
abortController.abort();
|
|
764
|
+
if (result === 0)
|
|
765
|
+
this.logger.debug("waitTillCommited(): Resolved from state changed");
|
|
766
|
+
if (result === true)
|
|
767
|
+
this.logger.debug("waitTillCommited(): Resolved from watchdog - commited");
|
|
768
|
+
if (result === false) {
|
|
769
|
+
this.logger.debug("waitTillCommited(): Resolved from watchdog - signature expired");
|
|
770
|
+
if (this._state === FromBTCSwapState.PR_CREATED || this._state === FromBTCSwapState.QUOTE_SOFT_EXPIRED) {
|
|
771
|
+
await this._saveAndEmit(FromBTCSwapState.QUOTE_EXPIRED);
|
|
772
|
+
}
|
|
773
|
+
return;
|
|
774
|
+
}
|
|
775
|
+
if (this._state === FromBTCSwapState.PR_CREATED || this._state === FromBTCSwapState.QUOTE_SOFT_EXPIRED) {
|
|
776
|
+
await this._saveAndEmit(FromBTCSwapState.CLAIM_COMMITED);
|
|
777
|
+
}
|
|
778
|
+
}
|
|
779
|
+
//////////////////////////////
|
|
780
|
+
//// Claim
|
|
781
|
+
/**
|
|
782
|
+
* Returns transactions for settling (claiming) the swap if the swap requires manual settlement, you can check so
|
|
783
|
+
* with isClaimable. After sending the transaction manually be sure to call the waitTillClaimed function to wait
|
|
784
|
+
* till the claim transaction is observed, processed by the SDK and state of the swap properly updated.
|
|
785
|
+
*
|
|
786
|
+
* @remarks
|
|
787
|
+
* Might also return transactions necessary to sync the bitcoin light client.
|
|
788
|
+
*
|
|
789
|
+
* @param _signer Address of the signer to create the claim transactions for
|
|
790
|
+
*
|
|
791
|
+
* @throws {Error} If the swap is in invalid state (must be {@link FromBTCSwapState.BTC_TX_CONFIRMED})
|
|
792
|
+
*/
|
|
793
|
+
async txsClaim(_signer) {
|
|
794
|
+
let signer = undefined;
|
|
795
|
+
if (_signer != null) {
|
|
796
|
+
if (typeof (_signer) === "string") {
|
|
797
|
+
signer = _signer;
|
|
798
|
+
}
|
|
799
|
+
else if ((0, base_1.isAbstractSigner)(_signer)) {
|
|
800
|
+
signer = _signer;
|
|
801
|
+
}
|
|
802
|
+
else {
|
|
803
|
+
signer = await this.wrapper._chain.wrapSigner(_signer);
|
|
804
|
+
}
|
|
805
|
+
}
|
|
806
|
+
if (this._state !== FromBTCSwapState.BTC_TX_CONFIRMED)
|
|
807
|
+
throw new Error("Must be in BTC_TX_CONFIRMED state!");
|
|
808
|
+
if (this.txId == null || this.vout == null)
|
|
809
|
+
throw new Error("Bitcoin transaction ID not known!");
|
|
810
|
+
const tx = await this.wrapper._btcRpc.getTransaction(this.txId);
|
|
811
|
+
if (tx == null)
|
|
812
|
+
throw new Error("Bitcoin transaction not found on the network!");
|
|
813
|
+
this.requiredConfirmations ??= this.inferRequiredConfirmationsCount(tx, this.vout);
|
|
814
|
+
if (this.requiredConfirmations == null)
|
|
815
|
+
throw new Error("Cannot create claim transaction, because required confirmations are not known and cannot be infered! This can happen after a swap is recovered.");
|
|
816
|
+
if (tx.blockhash == null || tx.confirmations == null || tx.blockheight == null || tx.confirmations < this.requiredConfirmations)
|
|
817
|
+
throw new Error("Bitcoin transaction not confirmed yet!");
|
|
818
|
+
return await this.wrapper._contract.txsClaimWithTxData(signer ?? this._getInitiator(), this._data, {
|
|
819
|
+
blockhash: tx.blockhash,
|
|
820
|
+
confirmations: tx.confirmations,
|
|
821
|
+
txid: tx.txid,
|
|
822
|
+
hex: tx.hex,
|
|
823
|
+
height: tx.blockheight
|
|
824
|
+
}, this.requiredConfirmations, this.vout, undefined, this.wrapper._synchronizer, true);
|
|
825
|
+
}
|
|
826
|
+
/**
|
|
827
|
+
* Settles the swap by claiming the funds on the destination chain if the swap requires manual settlement, you can
|
|
828
|
+
* check so with isClaimable.
|
|
829
|
+
*
|
|
830
|
+
* @remarks
|
|
831
|
+
* Might also sync the bitcoin light client during the process.
|
|
832
|
+
*
|
|
833
|
+
* @param _signer Signer to use for signing the settlement transactions, can also be different to the recipient
|
|
834
|
+
* @param abortSignal Abort signal
|
|
835
|
+
* @param onBeforeTxSent Optional callback triggered before the claim transaction is broadcasted
|
|
836
|
+
*
|
|
837
|
+
* @returns Transaction ID of the settlement (claim) transaction on the destination smart chain
|
|
838
|
+
*/
|
|
839
|
+
async claim(_signer, abortSignal, onBeforeTxSent) {
|
|
840
|
+
const signer = (0, base_1.isAbstractSigner)(_signer) ? _signer : await this.wrapper._chain.wrapSigner(_signer);
|
|
841
|
+
let txIds;
|
|
842
|
+
try {
|
|
843
|
+
let txCount = 0;
|
|
844
|
+
const txs = await this.txsClaim(signer);
|
|
845
|
+
txIds = await this.wrapper._chain.sendAndConfirm(signer, txs, true, abortSignal, undefined, (txId) => {
|
|
846
|
+
txCount++;
|
|
847
|
+
if (onBeforeTxSent != null && txCount === txs.length)
|
|
848
|
+
onBeforeTxSent(txId);
|
|
849
|
+
return Promise.resolve();
|
|
850
|
+
});
|
|
851
|
+
}
|
|
852
|
+
catch (e) {
|
|
853
|
+
this.logger.info("claim(): Failed to claim ourselves, checking swap claim state...");
|
|
854
|
+
if (this._state === FromBTCSwapState.CLAIM_CLAIMED) {
|
|
855
|
+
this.logger.info("claim(): Transaction state is CLAIM_CLAIMED, swap was successfully claimed by the watchtower");
|
|
856
|
+
return this._claimTxId;
|
|
857
|
+
}
|
|
858
|
+
const status = await this.wrapper._contract.getCommitStatus(this._getInitiator(), this._data);
|
|
859
|
+
if (status?.type === base_1.SwapCommitStateType.PAID) {
|
|
860
|
+
this.logger.info("claim(): Transaction commit status is PAID, swap was successfully claimed by the watchtower");
|
|
861
|
+
if (this._claimTxId == null)
|
|
862
|
+
this._claimTxId = await status.getClaimTxId();
|
|
863
|
+
const txId = buffer_1.Buffer.from(await status.getClaimResult(), "hex").reverse().toString("hex");
|
|
864
|
+
await this._setBitcoinTxId(txId);
|
|
865
|
+
await this._saveAndEmit(FromBTCSwapState.CLAIM_CLAIMED);
|
|
866
|
+
return this._claimTxId;
|
|
867
|
+
}
|
|
868
|
+
throw e;
|
|
869
|
+
}
|
|
870
|
+
this._claimTxId = txIds[txIds.length - 1];
|
|
871
|
+
if (this._state === FromBTCSwapState.CLAIM_COMMITED || this._state === FromBTCSwapState.BTC_TX_CONFIRMED ||
|
|
872
|
+
this._state === FromBTCSwapState.EXPIRED || this._state === FromBTCSwapState.FAILED) {
|
|
873
|
+
await this._saveAndEmit(FromBTCSwapState.CLAIM_CLAIMED);
|
|
874
|
+
}
|
|
875
|
+
return txIds[txIds.length - 1];
|
|
876
|
+
}
|
|
877
|
+
/**
|
|
878
|
+
* @inheritDoc
|
|
879
|
+
*
|
|
880
|
+
* @throws {Error} If swap is in invalid state (must be {@link FromBTCSwapState.BTC_TX_CONFIRMED})
|
|
881
|
+
* @throws {Error} If the LP refunded sooner than we were able to claim
|
|
882
|
+
*/
|
|
883
|
+
async waitTillClaimed(maxWaitTimeSeconds, abortSignal) {
|
|
884
|
+
if (this._state === FromBTCSwapState.CLAIM_CLAIMED)
|
|
885
|
+
return Promise.resolve(true);
|
|
886
|
+
if (this._state !== FromBTCSwapState.BTC_TX_CONFIRMED)
|
|
887
|
+
throw new Error("Invalid state (not BTC_TX_CONFIRMED)");
|
|
888
|
+
const abortController = (0, Utils_1.extendAbortController)(abortSignal);
|
|
889
|
+
let timedOut = false;
|
|
890
|
+
if (maxWaitTimeSeconds != null) {
|
|
891
|
+
const timeout = setTimeout(() => {
|
|
892
|
+
timedOut = true;
|
|
893
|
+
abortController.abort();
|
|
894
|
+
}, maxWaitTimeSeconds * 1000);
|
|
895
|
+
abortController.signal.addEventListener("abort", () => clearTimeout(timeout));
|
|
896
|
+
}
|
|
897
|
+
let res;
|
|
898
|
+
try {
|
|
899
|
+
res = await Promise.race([
|
|
900
|
+
this.watchdogWaitTillResult(undefined, abortController.signal),
|
|
901
|
+
this.waitTillState(FromBTCSwapState.CLAIM_CLAIMED, "eq", abortController.signal).then(() => 0),
|
|
902
|
+
this.waitTillState(FromBTCSwapState.FAILED, "eq", abortController.signal).then(() => 1),
|
|
903
|
+
]);
|
|
904
|
+
abortController.abort();
|
|
905
|
+
}
|
|
906
|
+
catch (e) {
|
|
907
|
+
abortController.abort();
|
|
908
|
+
if (timedOut)
|
|
909
|
+
return false;
|
|
910
|
+
throw e;
|
|
911
|
+
}
|
|
912
|
+
if (res === 0) {
|
|
913
|
+
this.logger.debug("waitTillClaimed(): Resolved from state change (CLAIM_CLAIMED)");
|
|
914
|
+
return true;
|
|
915
|
+
}
|
|
916
|
+
if (res === 1) {
|
|
917
|
+
this.logger.debug("waitTillClaimed(): Resolved from state change (FAILED)");
|
|
918
|
+
throw new Error("Offerer refunded during claiming");
|
|
919
|
+
}
|
|
920
|
+
this.logger.debug("waitTillClaimed(): Resolved from watchdog");
|
|
921
|
+
if (res?.type === base_1.SwapCommitStateType.PAID) {
|
|
922
|
+
if (this._state !== FromBTCSwapState.CLAIM_CLAIMED) {
|
|
923
|
+
if (this._claimTxId == null)
|
|
924
|
+
this._claimTxId = await res.getClaimTxId();
|
|
925
|
+
const txId = buffer_1.Buffer.from(await res.getClaimResult(), "hex").reverse().toString("hex");
|
|
926
|
+
await this._setBitcoinTxId(txId);
|
|
927
|
+
await this._saveAndEmit(FromBTCSwapState.CLAIM_CLAIMED);
|
|
928
|
+
}
|
|
929
|
+
}
|
|
930
|
+
if (res?.type === base_1.SwapCommitStateType.NOT_COMMITED || res?.type === base_1.SwapCommitStateType.EXPIRED) {
|
|
931
|
+
if (this._state !== FromBTCSwapState.CLAIM_CLAIMED &&
|
|
932
|
+
this._state !== FromBTCSwapState.FAILED) {
|
|
933
|
+
if (res.getRefundTxId != null)
|
|
934
|
+
this._refundTxId = await res.getRefundTxId();
|
|
935
|
+
await this._saveAndEmit(FromBTCSwapState.FAILED);
|
|
936
|
+
}
|
|
937
|
+
throw new Error("Swap expired while waiting for claim!");
|
|
938
|
+
}
|
|
939
|
+
return true;
|
|
940
|
+
}
|
|
941
|
+
//////////////////////////////
|
|
942
|
+
//// Storage
|
|
943
|
+
/**
|
|
944
|
+
* @inheritDoc
|
|
945
|
+
*/
|
|
946
|
+
serialize() {
|
|
947
|
+
return {
|
|
948
|
+
...super.serialize(),
|
|
949
|
+
address: this.address,
|
|
950
|
+
amount: this.amount == null ? null : this.amount.toString(10),
|
|
951
|
+
requiredConfirmations: this.requiredConfirmations,
|
|
952
|
+
senderAddress: this.senderAddress,
|
|
953
|
+
txId: this.txId,
|
|
954
|
+
vout: this.vout,
|
|
955
|
+
btcTxConfirmedAt: this.btcTxConfirmedAt
|
|
956
|
+
};
|
|
957
|
+
}
|
|
958
|
+
//////////////////////////////
|
|
959
|
+
//// Swap ticks & sync
|
|
960
|
+
/**
|
|
961
|
+
* Checks the swap's state on-chain and compares it to its internal state, updates/changes it according to on-chain
|
|
962
|
+
* data
|
|
963
|
+
*
|
|
964
|
+
* @private
|
|
965
|
+
*/
|
|
966
|
+
async syncStateFromChain(quoteDefinitelyExpired, commitStatus) {
|
|
967
|
+
if (this._state === FromBTCSwapState.PR_CREATED ||
|
|
968
|
+
this._state === FromBTCSwapState.QUOTE_SOFT_EXPIRED ||
|
|
969
|
+
this._state === FromBTCSwapState.CLAIM_COMMITED ||
|
|
970
|
+
this._state === FromBTCSwapState.BTC_TX_CONFIRMED ||
|
|
971
|
+
this._state === FromBTCSwapState.EXPIRED) {
|
|
972
|
+
let quoteExpired = false;
|
|
973
|
+
if (this._state === FromBTCSwapState.PR_CREATED || this._state === FromBTCSwapState.QUOTE_SOFT_EXPIRED) {
|
|
974
|
+
quoteExpired = quoteDefinitelyExpired ?? await this._verifyQuoteDefinitelyExpired(); //Make sure we check for expiry here, to prevent race conditions
|
|
975
|
+
}
|
|
976
|
+
const status = commitStatus ?? await this.wrapper._contract.getCommitStatus(this._getInitiator(), this._data);
|
|
977
|
+
if (status != null && await this._forciblySetOnchainState(status))
|
|
978
|
+
return true;
|
|
979
|
+
if (this._state === FromBTCSwapState.PR_CREATED || this._state === FromBTCSwapState.QUOTE_SOFT_EXPIRED) {
|
|
980
|
+
if (quoteExpired) {
|
|
981
|
+
this._state = FromBTCSwapState.QUOTE_EXPIRED;
|
|
982
|
+
return true;
|
|
983
|
+
}
|
|
984
|
+
}
|
|
985
|
+
}
|
|
986
|
+
return false;
|
|
987
|
+
}
|
|
988
|
+
/**
|
|
989
|
+
* @inheritDoc
|
|
990
|
+
* @internal
|
|
991
|
+
*/
|
|
992
|
+
_shouldFetchOnchainState() {
|
|
993
|
+
return this._state === FromBTCSwapState.PR_CREATED || this._state === FromBTCSwapState.QUOTE_SOFT_EXPIRED ||
|
|
994
|
+
this._state === FromBTCSwapState.CLAIM_COMMITED || this._state === FromBTCSwapState.BTC_TX_CONFIRMED ||
|
|
995
|
+
this._state === FromBTCSwapState.EXPIRED;
|
|
996
|
+
}
|
|
997
|
+
/**
|
|
998
|
+
* @inheritDoc
|
|
999
|
+
* @internal
|
|
1000
|
+
*/
|
|
1001
|
+
_shouldFetchExpiryStatus() {
|
|
1002
|
+
return this._state === FromBTCSwapState.PR_CREATED || this._state === FromBTCSwapState.QUOTE_SOFT_EXPIRED;
|
|
1003
|
+
}
|
|
1004
|
+
/**
|
|
1005
|
+
* @inheritDoc
|
|
1006
|
+
* @internal
|
|
1007
|
+
*/
|
|
1008
|
+
async _sync(save, quoteDefinitelyExpired, commitStatus) {
|
|
1009
|
+
const changed = await this.syncStateFromChain(quoteDefinitelyExpired, commitStatus);
|
|
1010
|
+
if (changed && save)
|
|
1011
|
+
await this._saveAndEmit();
|
|
1012
|
+
return changed;
|
|
1013
|
+
}
|
|
1014
|
+
/**
|
|
1015
|
+
* @inheritDoc
|
|
1016
|
+
* @internal
|
|
1017
|
+
*/
|
|
1018
|
+
async _forciblySetOnchainState(status) {
|
|
1019
|
+
switch (status.type) {
|
|
1020
|
+
case base_1.SwapCommitStateType.PAID:
|
|
1021
|
+
if (this._claimTxId == null)
|
|
1022
|
+
this._claimTxId = await status.getClaimTxId();
|
|
1023
|
+
const txId = buffer_1.Buffer.from(await status.getClaimResult(), "hex").reverse().toString("hex");
|
|
1024
|
+
await this._setBitcoinTxId(txId);
|
|
1025
|
+
this._state = FromBTCSwapState.CLAIM_CLAIMED;
|
|
1026
|
+
return true;
|
|
1027
|
+
case base_1.SwapCommitStateType.NOT_COMMITED:
|
|
1028
|
+
if (this._refundTxId == null && status.getRefundTxId)
|
|
1029
|
+
this._refundTxId = await status.getRefundTxId();
|
|
1030
|
+
if (this._refundTxId != null) {
|
|
1031
|
+
this._state = FromBTCSwapState.FAILED;
|
|
1032
|
+
return true;
|
|
1033
|
+
}
|
|
1034
|
+
break;
|
|
1035
|
+
case base_1.SwapCommitStateType.EXPIRED:
|
|
1036
|
+
if (this._refundTxId == null && status.getRefundTxId)
|
|
1037
|
+
this._refundTxId = await status.getRefundTxId();
|
|
1038
|
+
this._state = this._refundTxId == null ? FromBTCSwapState.QUOTE_EXPIRED : FromBTCSwapState.FAILED;
|
|
1039
|
+
return true;
|
|
1040
|
+
case base_1.SwapCommitStateType.COMMITED:
|
|
1041
|
+
let save = false;
|
|
1042
|
+
if (this._state !== FromBTCSwapState.CLAIM_COMMITED && this._state !== FromBTCSwapState.BTC_TX_CONFIRMED && this._state !== FromBTCSwapState.EXPIRED) {
|
|
1043
|
+
this._state = FromBTCSwapState.CLAIM_COMMITED;
|
|
1044
|
+
save = true;
|
|
1045
|
+
}
|
|
1046
|
+
if (this.address == null)
|
|
1047
|
+
return save;
|
|
1048
|
+
this.btcTxLastChecked = Date.now();
|
|
1049
|
+
const res = await this.getBitcoinPayment();
|
|
1050
|
+
if (res != null) {
|
|
1051
|
+
if (this.txId !== res.txId) {
|
|
1052
|
+
if (res.inputAddresses != null)
|
|
1053
|
+
this.senderAddress = res.inputAddresses[0];
|
|
1054
|
+
this.txId = res.txId;
|
|
1055
|
+
this.vout = res.vout;
|
|
1056
|
+
save = true;
|
|
1057
|
+
}
|
|
1058
|
+
if (this.requiredConfirmations != null && res.confirmations >= this.requiredConfirmations) {
|
|
1059
|
+
this.btcTxConfirmedAt ??= Date.now();
|
|
1060
|
+
this._state = FromBTCSwapState.BTC_TX_CONFIRMED;
|
|
1061
|
+
save = true;
|
|
1062
|
+
}
|
|
1063
|
+
}
|
|
1064
|
+
return save;
|
|
1065
|
+
}
|
|
1066
|
+
return false;
|
|
1067
|
+
}
|
|
1068
|
+
/**
|
|
1069
|
+
* @inheritDoc
|
|
1070
|
+
* @internal
|
|
1071
|
+
*/
|
|
1072
|
+
async _tick(save) {
|
|
1073
|
+
switch (this._state) {
|
|
1074
|
+
case FromBTCSwapState.PR_CREATED:
|
|
1075
|
+
if (this.expiry < Date.now()) {
|
|
1076
|
+
this._state = FromBTCSwapState.QUOTE_SOFT_EXPIRED;
|
|
1077
|
+
if (save)
|
|
1078
|
+
await this._saveAndEmit();
|
|
1079
|
+
return true;
|
|
1080
|
+
}
|
|
1081
|
+
break;
|
|
1082
|
+
case FromBTCSwapState.CLAIM_COMMITED:
|
|
1083
|
+
if (this.getTimeoutTime() < Date.now()) {
|
|
1084
|
+
this._state = FromBTCSwapState.EXPIRED;
|
|
1085
|
+
if (save)
|
|
1086
|
+
await this._saveAndEmit();
|
|
1087
|
+
return true;
|
|
1088
|
+
}
|
|
1089
|
+
case FromBTCSwapState.EXPIRED:
|
|
1090
|
+
//Check if bitcoin payment was received at least every 2 minutes
|
|
1091
|
+
if (this.btcTxLastChecked == null || Date.now() - this.btcTxLastChecked > 120000) {
|
|
1092
|
+
if (this.address != null)
|
|
1093
|
+
try {
|
|
1094
|
+
this.btcTxLastChecked = Date.now();
|
|
1095
|
+
const res = await this.getBitcoinPayment();
|
|
1096
|
+
if (res != null) {
|
|
1097
|
+
let shouldSave = false;
|
|
1098
|
+
if (this.txId !== res.txId) {
|
|
1099
|
+
this.txId = res.txId;
|
|
1100
|
+
this.vout = res.vout;
|
|
1101
|
+
if (res.inputAddresses != null)
|
|
1102
|
+
this.senderAddress = res.inputAddresses[0];
|
|
1103
|
+
shouldSave = true;
|
|
1104
|
+
}
|
|
1105
|
+
if (this.requiredConfirmations != null && res.confirmations >= this.requiredConfirmations) {
|
|
1106
|
+
this.btcTxConfirmedAt ??= Date.now();
|
|
1107
|
+
this._state = FromBTCSwapState.BTC_TX_CONFIRMED;
|
|
1108
|
+
if (save)
|
|
1109
|
+
await this._saveAndEmit();
|
|
1110
|
+
shouldSave = true;
|
|
1111
|
+
}
|
|
1112
|
+
if (shouldSave && save)
|
|
1113
|
+
await this._saveAndEmit();
|
|
1114
|
+
return shouldSave;
|
|
1115
|
+
}
|
|
1116
|
+
}
|
|
1117
|
+
catch (e) {
|
|
1118
|
+
this.logger.warn("tickSwap(" + this.getIdentifierHashString() + "): ", e);
|
|
1119
|
+
}
|
|
1120
|
+
}
|
|
1121
|
+
break;
|
|
1122
|
+
}
|
|
1123
|
+
return false;
|
|
1124
|
+
}
|
|
1125
|
+
}
|
|
1126
|
+
exports.FromBTCSwap = FromBTCSwap;
|