@atomiqlabs/lp-lib 14.0.0-dev.21 → 14.0.0-dev.23
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/LICENSE +201 -201
- package/dist/fees/IBtcFeeEstimator.d.ts +3 -3
- package/dist/fees/IBtcFeeEstimator.js +2 -2
- package/dist/index.d.ts +42 -42
- package/dist/index.js +58 -58
- package/dist/info/InfoHandler.d.ts +17 -17
- package/dist/info/InfoHandler.js +61 -61
- package/dist/plugins/IPlugin.d.ts +143 -143
- package/dist/plugins/IPlugin.js +34 -34
- package/dist/plugins/PluginManager.d.ts +112 -112
- package/dist/plugins/PluginManager.js +259 -259
- package/dist/prices/BinanceSwapPrice.d.ts +26 -26
- package/dist/prices/BinanceSwapPrice.js +92 -92
- package/dist/prices/CoinGeckoSwapPrice.d.ts +30 -30
- package/dist/prices/CoinGeckoSwapPrice.js +64 -64
- package/dist/prices/ISwapPrice.d.ts +43 -43
- package/dist/prices/ISwapPrice.js +55 -55
- package/dist/prices/OKXSwapPrice.d.ts +26 -26
- package/dist/prices/OKXSwapPrice.js +92 -92
- package/dist/storage/IIntermediaryStorage.d.ts +18 -18
- package/dist/storage/IIntermediaryStorage.js +2 -2
- package/dist/storagemanager/IntermediaryStorageManager.d.ts +19 -19
- package/dist/storagemanager/IntermediaryStorageManager.js +111 -111
- package/dist/storagemanager/StorageManager.d.ts +13 -13
- package/dist/storagemanager/StorageManager.js +64 -64
- package/dist/swaps/SwapHandler.d.ts +153 -153
- package/dist/swaps/SwapHandler.js +160 -160
- package/dist/swaps/SwapHandlerSwap.d.ts +79 -79
- package/dist/swaps/SwapHandlerSwap.js +78 -78
- package/dist/swaps/assertions/AmountAssertions.d.ts +28 -28
- package/dist/swaps/assertions/AmountAssertions.js +72 -72
- package/dist/swaps/assertions/FromBtcAmountAssertions.d.ts +76 -76
- package/dist/swaps/assertions/FromBtcAmountAssertions.js +180 -180
- package/dist/swaps/assertions/LightningAssertions.d.ts +44 -44
- package/dist/swaps/assertions/LightningAssertions.js +86 -86
- package/dist/swaps/assertions/ToBtcAmountAssertions.d.ts +53 -53
- package/dist/swaps/assertions/ToBtcAmountAssertions.js +150 -150
- package/dist/swaps/escrow/EscrowHandler.d.ts +51 -51
- package/dist/swaps/escrow/EscrowHandler.js +158 -158
- package/dist/swaps/escrow/EscrowHandlerSwap.d.ts +35 -35
- package/dist/swaps/escrow/EscrowHandlerSwap.js +69 -69
- package/dist/swaps/escrow/FromBtcBaseSwap.d.ts +14 -14
- package/dist/swaps/escrow/FromBtcBaseSwap.js +32 -32
- package/dist/swaps/escrow/FromBtcBaseSwapHandler.d.ts +102 -102
- package/dist/swaps/escrow/FromBtcBaseSwapHandler.js +210 -210
- package/dist/swaps/escrow/ToBtcBaseSwap.d.ts +36 -36
- package/dist/swaps/escrow/ToBtcBaseSwap.js +67 -67
- package/dist/swaps/escrow/ToBtcBaseSwapHandler.d.ts +53 -53
- package/dist/swaps/escrow/ToBtcBaseSwapHandler.js +81 -81
- package/dist/swaps/escrow/frombtc_abstract/FromBtcAbs.d.ts +83 -83
- package/dist/swaps/escrow/frombtc_abstract/FromBtcAbs.js +318 -318
- package/dist/swaps/escrow/frombtc_abstract/FromBtcSwapAbs.d.ts +21 -21
- package/dist/swaps/escrow/frombtc_abstract/FromBtcSwapAbs.js +50 -50
- package/dist/swaps/escrow/frombtcln_abstract/FromBtcLnAbs.d.ts +107 -107
- package/dist/swaps/escrow/frombtcln_abstract/FromBtcLnAbs.js +675 -675
- package/dist/swaps/escrow/frombtcln_abstract/FromBtcLnSwapAbs.d.ts +33 -33
- package/dist/swaps/escrow/frombtcln_abstract/FromBtcLnSwapAbs.js +91 -91
- package/dist/swaps/escrow/frombtcln_autoinit/FromBtcLnAuto.d.ts +111 -111
- package/dist/swaps/escrow/frombtcln_autoinit/FromBtcLnAuto.js +684 -684
- package/dist/swaps/escrow/frombtcln_autoinit/FromBtcLnAutoSwap.d.ts +55 -55
- package/dist/swaps/escrow/frombtcln_autoinit/FromBtcLnAutoSwap.js +120 -120
- package/dist/swaps/escrow/tobtc_abstract/ToBtcAbs.d.ts +171 -171
- package/dist/swaps/escrow/tobtc_abstract/ToBtcAbs.js +706 -706
- package/dist/swaps/escrow/tobtc_abstract/ToBtcSwapAbs.d.ts +26 -26
- package/dist/swaps/escrow/tobtc_abstract/ToBtcSwapAbs.js +62 -62
- package/dist/swaps/escrow/tobtcln_abstract/ToBtcLnAbs.d.ts +177 -177
- package/dist/swaps/escrow/tobtcln_abstract/ToBtcLnAbs.js +863 -863
- package/dist/swaps/escrow/tobtcln_abstract/ToBtcLnSwapAbs.d.ts +24 -24
- package/dist/swaps/escrow/tobtcln_abstract/ToBtcLnSwapAbs.js +58 -58
- package/dist/swaps/spv_vault_swap/SpvVault.d.ts +40 -41
- package/dist/swaps/spv_vault_swap/SpvVault.js +111 -111
- package/dist/swaps/spv_vault_swap/SpvVaultSwap.d.ts +67 -67
- package/dist/swaps/spv_vault_swap/SpvVaultSwap.js +158 -158
- package/dist/swaps/spv_vault_swap/SpvVaultSwapHandler.d.ts +68 -68
- package/dist/swaps/spv_vault_swap/SpvVaultSwapHandler.js +492 -492
- package/dist/swaps/spv_vault_swap/SpvVaults.d.ts +52 -52
- package/dist/swaps/spv_vault_swap/SpvVaults.js +386 -364
- package/dist/swaps/trusted/frombtc_trusted/FromBtcTrusted.d.ts +51 -51
- package/dist/swaps/trusted/frombtc_trusted/FromBtcTrusted.js +650 -650
- package/dist/swaps/trusted/frombtc_trusted/FromBtcTrustedSwap.d.ts +52 -52
- package/dist/swaps/trusted/frombtc_trusted/FromBtcTrustedSwap.js +118 -118
- package/dist/swaps/trusted/frombtcln_trusted/FromBtcLnTrusted.d.ts +76 -76
- package/dist/swaps/trusted/frombtcln_trusted/FromBtcLnTrusted.js +494 -494
- package/dist/swaps/trusted/frombtcln_trusted/FromBtcLnTrustedSwap.d.ts +34 -34
- package/dist/swaps/trusted/frombtcln_trusted/FromBtcLnTrustedSwap.js +81 -81
- package/dist/utils/BitcoinUtils.d.ts +2 -2
- package/dist/utils/BitcoinUtils.js +45 -45
- package/dist/utils/Utils.d.ts +29 -29
- package/dist/utils/Utils.js +89 -89
- package/dist/utils/paramcoders/IParamReader.d.ts +5 -5
- package/dist/utils/paramcoders/IParamReader.js +2 -2
- package/dist/utils/paramcoders/IParamWriter.d.ts +4 -4
- package/dist/utils/paramcoders/IParamWriter.js +2 -2
- package/dist/utils/paramcoders/LegacyParamEncoder.d.ts +10 -10
- package/dist/utils/paramcoders/LegacyParamEncoder.js +22 -22
- package/dist/utils/paramcoders/ParamDecoder.d.ts +25 -25
- package/dist/utils/paramcoders/ParamDecoder.js +222 -222
- package/dist/utils/paramcoders/ParamEncoder.d.ts +9 -9
- package/dist/utils/paramcoders/ParamEncoder.js +22 -22
- package/dist/utils/paramcoders/SchemaVerifier.d.ts +21 -21
- package/dist/utils/paramcoders/SchemaVerifier.js +84 -84
- package/dist/utils/paramcoders/server/ServerParamDecoder.d.ts +8 -8
- package/dist/utils/paramcoders/server/ServerParamDecoder.js +107 -107
- package/dist/utils/paramcoders/server/ServerParamEncoder.d.ts +11 -11
- package/dist/utils/paramcoders/server/ServerParamEncoder.js +65 -65
- package/dist/wallets/IBitcoinWallet.d.ts +67 -67
- package/dist/wallets/IBitcoinWallet.js +2 -2
- package/dist/wallets/ILightningWallet.d.ts +117 -117
- package/dist/wallets/ILightningWallet.js +37 -37
- package/dist/wallets/ISpvVaultSigner.d.ts +7 -7
- package/dist/wallets/ISpvVaultSigner.js +2 -2
- package/package.json +36 -36
- package/src/fees/IBtcFeeEstimator.ts +6 -6
- package/src/index.ts +53 -53
- package/src/info/InfoHandler.ts +106 -106
- package/src/plugins/IPlugin.ts +168 -168
- package/src/plugins/PluginManager.ts +336 -336
- package/src/prices/BinanceSwapPrice.ts +113 -113
- package/src/prices/CoinGeckoSwapPrice.ts +87 -87
- package/src/prices/ISwapPrice.ts +88 -88
- package/src/prices/OKXSwapPrice.ts +113 -113
- package/src/storage/IIntermediaryStorage.ts +19 -19
- package/src/storagemanager/IntermediaryStorageManager.ts +118 -118
- package/src/storagemanager/StorageManager.ts +78 -78
- package/src/swaps/SwapHandler.ts +277 -277
- package/src/swaps/SwapHandlerSwap.ts +141 -141
- package/src/swaps/assertions/AmountAssertions.ts +76 -76
- package/src/swaps/assertions/FromBtcAmountAssertions.ts +246 -246
- package/src/swaps/assertions/LightningAssertions.ts +103 -103
- package/src/swaps/assertions/ToBtcAmountAssertions.ts +203 -203
- package/src/swaps/escrow/EscrowHandler.ts +179 -179
- package/src/swaps/escrow/EscrowHandlerSwap.ts +86 -86
- package/src/swaps/escrow/FromBtcBaseSwap.ts +38 -38
- package/src/swaps/escrow/FromBtcBaseSwapHandler.ts +286 -286
- package/src/swaps/escrow/ToBtcBaseSwap.ts +85 -85
- package/src/swaps/escrow/ToBtcBaseSwapHandler.ts +129 -129
- package/src/swaps/escrow/frombtc_abstract/FromBtcAbs.ts +452 -452
- package/src/swaps/escrow/frombtc_abstract/FromBtcSwapAbs.ts +61 -61
- package/src/swaps/escrow/frombtcln_abstract/FromBtcLnAbs.ts +856 -856
- package/src/swaps/escrow/frombtcln_abstract/FromBtcLnSwapAbs.ts +141 -141
- package/src/swaps/escrow/frombtcln_autoinit/FromBtcLnAuto.ts +850 -850
- package/src/swaps/escrow/frombtcln_autoinit/FromBtcLnAutoSwap.ts +196 -196
- package/src/swaps/escrow/tobtc_abstract/ToBtcAbs.ts +879 -879
- package/src/swaps/escrow/tobtc_abstract/ToBtcSwapAbs.ts +102 -102
- package/src/swaps/escrow/tobtcln_abstract/ToBtcLnAbs.ts +1112 -1112
- package/src/swaps/escrow/tobtcln_abstract/ToBtcLnSwapAbs.ts +80 -80
- package/src/swaps/spv_vault_swap/SpvVault.ts +143 -143
- package/src/swaps/spv_vault_swap/SpvVaultSwap.ts +225 -225
- package/src/swaps/spv_vault_swap/SpvVaultSwapHandler.ts +628 -628
- package/src/swaps/spv_vault_swap/SpvVaults.ts +458 -435
- package/src/swaps/trusted/frombtc_trusted/FromBtcTrusted.ts +747 -747
- package/src/swaps/trusted/frombtc_trusted/FromBtcTrustedSwap.ts +185 -185
- package/src/swaps/trusted/frombtcln_trusted/FromBtcLnTrusted.ts +590 -590
- package/src/swaps/trusted/frombtcln_trusted/FromBtcLnTrustedSwap.ts +121 -121
- package/src/utils/BitcoinUtils.ts +42 -42
- package/src/utils/Utils.ts +104 -104
- package/src/utils/paramcoders/IParamReader.ts +7 -7
- package/src/utils/paramcoders/IParamWriter.ts +8 -8
- package/src/utils/paramcoders/LegacyParamEncoder.ts +27 -27
- package/src/utils/paramcoders/ParamDecoder.ts +218 -218
- package/src/utils/paramcoders/ParamEncoder.ts +29 -29
- package/src/utils/paramcoders/SchemaVerifier.ts +96 -96
- package/src/utils/paramcoders/server/ServerParamDecoder.ts +118 -118
- package/src/utils/paramcoders/server/ServerParamEncoder.ts +75 -75
- package/src/wallets/IBitcoinWallet.ts +68 -68
- package/src/wallets/ILightningWallet.ts +178 -178
- package/src/wallets/ISpvVaultSigner.ts +10 -10
|
@@ -1,650 +1,650 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.FromBtcTrusted = void 0;
|
|
4
|
-
const FromBtcTrustedSwap_1 = require("./FromBtcTrustedSwap");
|
|
5
|
-
const SwapHandler_1 = require("../../SwapHandler");
|
|
6
|
-
const PluginManager_1 = require("../../../plugins/PluginManager");
|
|
7
|
-
const Utils_1 = require("../../../utils/Utils");
|
|
8
|
-
const SchemaVerifier_1 = require("../../../utils/paramcoders/SchemaVerifier");
|
|
9
|
-
const FromBtcAmountAssertions_1 = require("../../assertions/FromBtcAmountAssertions");
|
|
10
|
-
class FromBtcTrusted extends SwapHandler_1.SwapHandler {
|
|
11
|
-
constructor(storageDirectory, path, chains, bitcoin, swapPricing, bitcoinRpc, config) {
|
|
12
|
-
var _a;
|
|
13
|
-
super(storageDirectory, path, chains, swapPricing);
|
|
14
|
-
this.type = SwapHandler_1.SwapHandlerType.FROM_BTC_TRUSTED;
|
|
15
|
-
this.subscriptions = new Map();
|
|
16
|
-
this.doubleSpendWatchdogSwaps = new Set();
|
|
17
|
-
this.refundedSwaps = new Map();
|
|
18
|
-
this.doubleSpentSwaps = new Map();
|
|
19
|
-
this.processedTxIds = new Map();
|
|
20
|
-
this.AmountAssertions = new FromBtcAmountAssertions_1.FromBtcAmountAssertions(config, swapPricing);
|
|
21
|
-
this.config = config;
|
|
22
|
-
(_a = this.config).recommendFeeMultiplier ?? (_a.recommendFeeMultiplier = 1.25);
|
|
23
|
-
this.bitcoin = bitcoin;
|
|
24
|
-
this.bitcoinRpc = bitcoinRpc;
|
|
25
|
-
}
|
|
26
|
-
getAllAncestors(tx) {
|
|
27
|
-
return Promise.all(tx.ins.map(input => this.bitcoinRpc.getTransaction(input.txid).then(tx => {
|
|
28
|
-
return { tx, vout: input.vout };
|
|
29
|
-
})));
|
|
30
|
-
}
|
|
31
|
-
async refundSwap(swap) {
|
|
32
|
-
if (swap.refundAddress == null) {
|
|
33
|
-
if (swap.state !== FromBtcTrustedSwap_1.FromBtcTrustedSwapState.REFUNDABLE) {
|
|
34
|
-
await swap.setState(FromBtcTrustedSwap_1.FromBtcTrustedSwapState.REFUNDABLE);
|
|
35
|
-
await this.storageManager.saveData(swap.getIdentifierHash(), swap.getSequence(), swap);
|
|
36
|
-
}
|
|
37
|
-
return;
|
|
38
|
-
}
|
|
39
|
-
let unlock = swap.lock(30 * 1000);
|
|
40
|
-
if (unlock == null)
|
|
41
|
-
return;
|
|
42
|
-
const feeRate = await this.bitcoin.getFeeRate();
|
|
43
|
-
const ourOutput = swap.btcTx.outs[swap.vout];
|
|
44
|
-
const resp = await this.bitcoin.drainAll(swap.refundAddress, [{
|
|
45
|
-
type: this.bitcoin.getAddressType(),
|
|
46
|
-
confirmations: swap.btcTx.confirmations,
|
|
47
|
-
outputScript: Buffer.from(ourOutput.scriptPubKey.hex, "hex"),
|
|
48
|
-
value: ourOutput.value,
|
|
49
|
-
txId: swap.btcTx.txid,
|
|
50
|
-
vout: swap.vout
|
|
51
|
-
}], feeRate);
|
|
52
|
-
if (resp == null) {
|
|
53
|
-
this.swapLogger.error(swap, "refundSwap(): cannot refund swap because of dust limit, txId: " + swap.txId);
|
|
54
|
-
unlock();
|
|
55
|
-
return;
|
|
56
|
-
}
|
|
57
|
-
if (swap.metadata != null)
|
|
58
|
-
swap.metadata.times.refundSignPSBT = Date.now();
|
|
59
|
-
this.swapLogger.debug(swap, "refundSwap(): signed raw transaction: " + resp.raw);
|
|
60
|
-
const refundTxId = resp.txId;
|
|
61
|
-
swap.refundTxId = refundTxId;
|
|
62
|
-
//Send the refund TX
|
|
63
|
-
await this.bitcoin.sendRawTransaction(resp.raw);
|
|
64
|
-
this.swapLogger.debug(swap, "refundSwap(): sent refund transaction: " + refundTxId);
|
|
65
|
-
this.refundedSwaps.set(swap.getIdentifierHash(), refundTxId);
|
|
66
|
-
await this.removeSwapData(swap, FromBtcTrustedSwap_1.FromBtcTrustedSwapState.REFUNDED);
|
|
67
|
-
unlock();
|
|
68
|
-
}
|
|
69
|
-
async burn(swap) {
|
|
70
|
-
const ourOutput = swap.btcTx.outs[swap.vout];
|
|
71
|
-
//Check if we can even increase the feeRate by burning
|
|
72
|
-
const txSize = 110;
|
|
73
|
-
const burnTxFeeRate = Math.floor(ourOutput.value / txSize);
|
|
74
|
-
const initialTxFeeRate = Math.ceil(swap.txFee / swap.txSize);
|
|
75
|
-
if (burnTxFeeRate < initialTxFeeRate) {
|
|
76
|
-
this.swapLogger.warn(swap, "burn(): cannot send burn transaction, pays too little fee, " +
|
|
77
|
-
"initialTxId: " + swap.txId + " initialTxFeeRate: " + initialTxFeeRate + " burnTxFeeRate: " + burnTxFeeRate);
|
|
78
|
-
this.doubleSpentSwaps.set(swap.getIdentifierHash(), null);
|
|
79
|
-
await this.removeSwapData(swap, FromBtcTrustedSwap_1.FromBtcTrustedSwapState.DOUBLE_SPENT);
|
|
80
|
-
return;
|
|
81
|
-
}
|
|
82
|
-
//Construct PSBT
|
|
83
|
-
const resp = await this.bitcoin.burnAll([{
|
|
84
|
-
type: this.bitcoin.getAddressType(),
|
|
85
|
-
confirmations: swap.btcTx.confirmations,
|
|
86
|
-
outputScript: Buffer.from(ourOutput.scriptPubKey.hex, "hex"),
|
|
87
|
-
value: ourOutput.value,
|
|
88
|
-
txId: swap.btcTx.txid,
|
|
89
|
-
vout: swap.vout
|
|
90
|
-
}]);
|
|
91
|
-
if (swap.metadata != null)
|
|
92
|
-
swap.metadata.times.burnSignPSBT = Date.now();
|
|
93
|
-
this.swapLogger.debug(swap, "burn(): signed raw transaction: " + resp.raw);
|
|
94
|
-
const burnTxId = resp.txId;
|
|
95
|
-
swap.burnTxId = burnTxId;
|
|
96
|
-
//Send the original TX + our burn TX as a package
|
|
97
|
-
const sendTxns = [swap.btcTx.raw, resp.raw];
|
|
98
|
-
//TODO: We should handle this in a better way
|
|
99
|
-
try {
|
|
100
|
-
await this.bitcoinRpc.sendRawPackage(sendTxns);
|
|
101
|
-
this.swapLogger.debug(swap, "burn(): sent burn transaction: " + burnTxId);
|
|
102
|
-
}
|
|
103
|
-
catch (e) {
|
|
104
|
-
this.swapLogger.error(swap, "burn(): error sending burn package: ", e);
|
|
105
|
-
}
|
|
106
|
-
this.doubleSpentSwaps.set(swap.getIdentifierHash(), burnTxId);
|
|
107
|
-
await this.removeSwapData(swap, FromBtcTrustedSwap_1.FromBtcTrustedSwapState.DOUBLE_SPENT);
|
|
108
|
-
}
|
|
109
|
-
async processPastSwap(swap, tx, vout) {
|
|
110
|
-
const foundVout = tx.outs[vout];
|
|
111
|
-
const { chainInterface, signer } = this.getChain(swap.chainIdentifier);
|
|
112
|
-
const outputScript = this.bitcoin.toOutputScript(swap.btcAddress).toString("hex");
|
|
113
|
-
if (swap.state === FromBtcTrustedSwap_1.FromBtcTrustedSwapState.CREATED) {
|
|
114
|
-
this.subscriptions.set(outputScript, swap);
|
|
115
|
-
if (foundVout == null) {
|
|
116
|
-
//Check expiry
|
|
117
|
-
if (swap.expiresAt < Date.now()) {
|
|
118
|
-
this.subscriptions.delete(outputScript);
|
|
119
|
-
await this.bitcoin.addUnusedAddress(swap.btcAddress);
|
|
120
|
-
await this.removeSwapData(swap, FromBtcTrustedSwap_1.FromBtcTrustedSwapState.EXPIRED);
|
|
121
|
-
return;
|
|
122
|
-
}
|
|
123
|
-
return;
|
|
124
|
-
}
|
|
125
|
-
const sentSats = BigInt(foundVout.value);
|
|
126
|
-
if (sentSats === swap.amount) {
|
|
127
|
-
swap.adjustedInput = swap.amount;
|
|
128
|
-
swap.adjustedOutput = swap.outputTokens;
|
|
129
|
-
}
|
|
130
|
-
else {
|
|
131
|
-
//If lower than minimum then ignore
|
|
132
|
-
if (sentSats < this.config.min)
|
|
133
|
-
return;
|
|
134
|
-
if (sentSats > this.config.max) {
|
|
135
|
-
swap.adjustedInput = sentSats;
|
|
136
|
-
swap.btcTx = tx;
|
|
137
|
-
swap.txId = tx.txid;
|
|
138
|
-
swap.vout = vout;
|
|
139
|
-
this.subscriptions.delete(outputScript);
|
|
140
|
-
await this.refundSwap(swap);
|
|
141
|
-
return;
|
|
142
|
-
}
|
|
143
|
-
//Adjust the amount
|
|
144
|
-
swap.adjustedInput = sentSats;
|
|
145
|
-
swap.adjustedOutput = swap.outputTokens * sentSats / swap.amount;
|
|
146
|
-
}
|
|
147
|
-
swap.btcTx = tx;
|
|
148
|
-
swap.txId = tx.txid;
|
|
149
|
-
swap.vout = vout;
|
|
150
|
-
this.subscriptions.delete(outputScript);
|
|
151
|
-
await swap.setState(FromBtcTrustedSwap_1.FromBtcTrustedSwapState.RECEIVED);
|
|
152
|
-
await this.storageManager.saveData(swap.getIdentifierHash(), swap.getSequence(), swap);
|
|
153
|
-
}
|
|
154
|
-
if (swap.state === FromBtcTrustedSwap_1.FromBtcTrustedSwapState.RECEIVED) {
|
|
155
|
-
//Check if transaction still exists
|
|
156
|
-
if (tx == null || foundVout == null || tx.txid !== swap.txId) {
|
|
157
|
-
await swap.setState(FromBtcTrustedSwap_1.FromBtcTrustedSwapState.CREATED);
|
|
158
|
-
await this.storageManager.saveData(swap.getIdentifierHash(), swap.getSequence(), swap);
|
|
159
|
-
return;
|
|
160
|
-
}
|
|
161
|
-
//Check if it is confirmed
|
|
162
|
-
if (tx.confirmations > 0) {
|
|
163
|
-
await swap.setState(FromBtcTrustedSwap_1.FromBtcTrustedSwapState.BTC_CONFIRMED);
|
|
164
|
-
await this.storageManager.saveData(swap.getIdentifierHash(), swap.getSequence(), swap);
|
|
165
|
-
}
|
|
166
|
-
else {
|
|
167
|
-
//Check if it pays high enough fee AND has confirmed ancestors
|
|
168
|
-
const ancestors = await this.getAllAncestors(tx);
|
|
169
|
-
const allAncestorsConfirmed = ancestors.reduce((prev, curr) => prev && curr.tx.confirmations > 0, true);
|
|
170
|
-
const totalInput = ancestors.reduce((prev, curr) => prev + curr.tx.outs[curr.vout].value, 0);
|
|
171
|
-
const totalOutput = tx.outs.reduce((prev, curr) => prev + curr.value, 0);
|
|
172
|
-
const fee = totalInput - totalOutput;
|
|
173
|
-
const feePerVbyte = Math.ceil(fee / tx.vsize);
|
|
174
|
-
if (allAncestorsConfirmed &&
|
|
175
|
-
(feePerVbyte >= swap.recommendedFee || feePerVbyte >= await this.bitcoin.getFeeRate())) {
|
|
176
|
-
if (swap.state !== FromBtcTrustedSwap_1.FromBtcTrustedSwapState.RECEIVED)
|
|
177
|
-
return;
|
|
178
|
-
swap.txSize = tx.vsize;
|
|
179
|
-
swap.txFee = fee;
|
|
180
|
-
await swap.setState(FromBtcTrustedSwap_1.FromBtcTrustedSwapState.BTC_CONFIRMED);
|
|
181
|
-
await this.storageManager.saveData(swap.getIdentifierHash(), swap.getSequence(), swap);
|
|
182
|
-
}
|
|
183
|
-
else {
|
|
184
|
-
return;
|
|
185
|
-
}
|
|
186
|
-
}
|
|
187
|
-
}
|
|
188
|
-
if (swap.state === FromBtcTrustedSwap_1.FromBtcTrustedSwapState.REFUNDABLE) {
|
|
189
|
-
if (swap.refundAddress != null) {
|
|
190
|
-
await this.refundSwap(swap);
|
|
191
|
-
return;
|
|
192
|
-
}
|
|
193
|
-
}
|
|
194
|
-
if (swap.doubleSpent || tx == null || foundVout == null || tx.txid !== swap.txId) {
|
|
195
|
-
if (swap.state === FromBtcTrustedSwap_1.FromBtcTrustedSwapState.REFUNDABLE) {
|
|
196
|
-
await swap.setState(FromBtcTrustedSwap_1.FromBtcTrustedSwapState.CREATED);
|
|
197
|
-
return;
|
|
198
|
-
}
|
|
199
|
-
if (!swap.doubleSpent) {
|
|
200
|
-
swap.doubleSpent = true;
|
|
201
|
-
try {
|
|
202
|
-
await this.burn(swap);
|
|
203
|
-
this.doubleSpendWatchdogSwaps.delete(swap);
|
|
204
|
-
}
|
|
205
|
-
catch (e) {
|
|
206
|
-
this.swapLogger.error(swap, "processPastSwap(): Error burning swap: ", e);
|
|
207
|
-
swap.doubleSpent = false;
|
|
208
|
-
}
|
|
209
|
-
}
|
|
210
|
-
return;
|
|
211
|
-
}
|
|
212
|
-
else {
|
|
213
|
-
if (tx.confirmations <= 0 && !this.doubleSpendWatchdogSwaps.has(swap)) {
|
|
214
|
-
this.swapLogger.debug(swap, "processPastSwap(): Adding swap transaction to double spend watchdog list: ", swap.txId);
|
|
215
|
-
this.doubleSpendWatchdogSwaps.add(swap);
|
|
216
|
-
}
|
|
217
|
-
}
|
|
218
|
-
if (tx.confirmations > 0 && this.doubleSpendWatchdogSwaps.delete(swap)) {
|
|
219
|
-
this.swapLogger.debug(swap, "processPastSwap(): Removing confirmed swap transaction from double spend watchdog list: ", swap.txId);
|
|
220
|
-
}
|
|
221
|
-
if (swap.state === FromBtcTrustedSwap_1.FromBtcTrustedSwapState.BTC_CONFIRMED) {
|
|
222
|
-
//Send gas token
|
|
223
|
-
const balance = chainInterface.getBalance(signer.getAddress(), swap.token);
|
|
224
|
-
try {
|
|
225
|
-
await this.checkBalance(swap.adjustedOutput, balance, null);
|
|
226
|
-
if (swap.metadata != null)
|
|
227
|
-
swap.metadata.times.receivedBalanceChecked = Date.now();
|
|
228
|
-
}
|
|
229
|
-
catch (e) {
|
|
230
|
-
this.swapLogger.error(swap, "processPastSwap(): Error not enough balance: ", e);
|
|
231
|
-
await this.refundSwap(swap);
|
|
232
|
-
return;
|
|
233
|
-
}
|
|
234
|
-
if (swap.state !== FromBtcTrustedSwap_1.FromBtcTrustedSwapState.BTC_CONFIRMED)
|
|
235
|
-
return;
|
|
236
|
-
let unlock = swap.lock(30 * 1000);
|
|
237
|
-
if (unlock == null)
|
|
238
|
-
return;
|
|
239
|
-
const txns = await chainInterface.txsTransfer(signer.getAddress(), swap.token, swap.adjustedOutput, swap.dstAddress);
|
|
240
|
-
await chainInterface.sendAndConfirm(signer, txns, true, null, false, async (txId, rawTx) => {
|
|
241
|
-
swap.txIds = { init: txId };
|
|
242
|
-
swap.scRawTx = rawTx;
|
|
243
|
-
if (swap.state === FromBtcTrustedSwap_1.FromBtcTrustedSwapState.BTC_CONFIRMED) {
|
|
244
|
-
await swap.setState(FromBtcTrustedSwap_1.FromBtcTrustedSwapState.SENT);
|
|
245
|
-
await this.storageManager.saveData(swap.getIdentifierHash(), swap.getSequence(), swap);
|
|
246
|
-
}
|
|
247
|
-
if (unlock != null)
|
|
248
|
-
unlock();
|
|
249
|
-
unlock = null;
|
|
250
|
-
});
|
|
251
|
-
}
|
|
252
|
-
if (swap.state === FromBtcTrustedSwap_1.FromBtcTrustedSwapState.SENT) {
|
|
253
|
-
const txStatus = await chainInterface.getTxStatus(swap.scRawTx);
|
|
254
|
-
switch (txStatus) {
|
|
255
|
-
case "not_found":
|
|
256
|
-
//Retry
|
|
257
|
-
swap.txIds = { init: null };
|
|
258
|
-
swap.scRawTx = null;
|
|
259
|
-
await swap.setState(FromBtcTrustedSwap_1.FromBtcTrustedSwapState.RECEIVED);
|
|
260
|
-
await this.storageManager.saveData(swap.getIdentifierHash(), swap.getSequence(), swap);
|
|
261
|
-
break;
|
|
262
|
-
case "reverted":
|
|
263
|
-
//Cancel invoice
|
|
264
|
-
await this.refundSwap(swap);
|
|
265
|
-
this.swapLogger.info(swap, "processPastSwap(): transaction reverted, refunding btc on-chain: ", swap.btcAddress);
|
|
266
|
-
break;
|
|
267
|
-
case "success":
|
|
268
|
-
await swap.setState(FromBtcTrustedSwap_1.FromBtcTrustedSwapState.CONFIRMED);
|
|
269
|
-
await this.storageManager.saveData(swap.getIdentifierHash(), swap.getSequence(), swap);
|
|
270
|
-
break;
|
|
271
|
-
}
|
|
272
|
-
}
|
|
273
|
-
if (swap.state === FromBtcTrustedSwap_1.FromBtcTrustedSwapState.CONFIRMED) {
|
|
274
|
-
this.processedTxIds.set(swap.getIdentifierHash(), {
|
|
275
|
-
txId: swap.txId,
|
|
276
|
-
scTxId: swap.txIds.init,
|
|
277
|
-
adjustedAmount: swap.adjustedInput,
|
|
278
|
-
adjustedTotal: swap.adjustedOutput
|
|
279
|
-
});
|
|
280
|
-
if (tx.confirmations > 0)
|
|
281
|
-
await this.removeSwapData(swap, FromBtcTrustedSwap_1.FromBtcTrustedSwapState.FINISHED);
|
|
282
|
-
}
|
|
283
|
-
}
|
|
284
|
-
async processPastSwaps() {
|
|
285
|
-
const queriedData = await this.storageManager.query([
|
|
286
|
-
{
|
|
287
|
-
key: "state",
|
|
288
|
-
value: [
|
|
289
|
-
FromBtcTrustedSwap_1.FromBtcTrustedSwapState.REFUNDABLE,
|
|
290
|
-
FromBtcTrustedSwap_1.FromBtcTrustedSwapState.CREATED,
|
|
291
|
-
FromBtcTrustedSwap_1.FromBtcTrustedSwapState.RECEIVED,
|
|
292
|
-
FromBtcTrustedSwap_1.FromBtcTrustedSwapState.BTC_CONFIRMED,
|
|
293
|
-
FromBtcTrustedSwap_1.FromBtcTrustedSwapState.SENT,
|
|
294
|
-
FromBtcTrustedSwap_1.FromBtcTrustedSwapState.CONFIRMED
|
|
295
|
-
]
|
|
296
|
-
}
|
|
297
|
-
]);
|
|
298
|
-
const startingBlockheight = queriedData.reduce((prev, { obj: swap }) => Math.min(prev, swap.createdHeight), Infinity);
|
|
299
|
-
if (startingBlockheight === Infinity)
|
|
300
|
-
return;
|
|
301
|
-
const transactions = await this.bitcoin.getWalletTransactions(startingBlockheight);
|
|
302
|
-
const map = new Map();
|
|
303
|
-
transactions.forEach(tx => {
|
|
304
|
-
tx.outs.forEach((out, vout) => {
|
|
305
|
-
const existing = map.get(out.scriptPubKey.hex);
|
|
306
|
-
if (existing == null) {
|
|
307
|
-
map.set(out.scriptPubKey.hex, [{ tx, vout }]);
|
|
308
|
-
}
|
|
309
|
-
else {
|
|
310
|
-
existing.push({ tx, vout });
|
|
311
|
-
}
|
|
312
|
-
});
|
|
313
|
-
});
|
|
314
|
-
for (let { obj: swap } of queriedData) {
|
|
315
|
-
const outputScript = this.bitcoin.toOutputScript(swap.btcAddress).toString("hex");
|
|
316
|
-
const txs = map.get(outputScript) ?? [];
|
|
317
|
-
try {
|
|
318
|
-
await this.processPastSwap(swap, txs[0]?.tx, txs[0]?.vout);
|
|
319
|
-
}
|
|
320
|
-
catch (e) {
|
|
321
|
-
this.swapLogger.error(swap, "processPastSwaps(): Error ocurred while processing swap: ", e);
|
|
322
|
-
}
|
|
323
|
-
}
|
|
324
|
-
}
|
|
325
|
-
isValidBitcoinAddress(address) {
|
|
326
|
-
try {
|
|
327
|
-
this.bitcoin.toOutputScript(address);
|
|
328
|
-
return true;
|
|
329
|
-
}
|
|
330
|
-
catch (e) { }
|
|
331
|
-
return false;
|
|
332
|
-
}
|
|
333
|
-
startRestServer(restServer) {
|
|
334
|
-
const getAddress = (0, Utils_1.expressHandlerWrapper)(async (req, res) => {
|
|
335
|
-
var _a;
|
|
336
|
-
const metadata = { request: {}, times: {} };
|
|
337
|
-
const chainIdentifier = req.query.chain ?? this.chains.default;
|
|
338
|
-
const { chainInterface, signer } = this.getChain(chainIdentifier);
|
|
339
|
-
metadata.times.requestReceived = Date.now();
|
|
340
|
-
/**
|
|
341
|
-
* address: string solana address of the recipient
|
|
342
|
-
* refundAddress?: string bitcoin address to use in case of refund
|
|
343
|
-
* amount: string amount (in lamports/smart chain base units) of the invoice
|
|
344
|
-
* exactOut: boolean whether to create and exact output swap
|
|
345
|
-
*/
|
|
346
|
-
(_a = req.query).token ?? (_a.token = chainInterface.getNativeCurrencyAddress());
|
|
347
|
-
const parsedBody = (0, SchemaVerifier_1.verifySchema)(req.query, {
|
|
348
|
-
address: (val) => val != null &&
|
|
349
|
-
typeof (val) === "string" &&
|
|
350
|
-
chainInterface.isValidAddress(val) ? val : null,
|
|
351
|
-
refundAddress: (val) => val == null ? "" :
|
|
352
|
-
typeof (val) === "string" &&
|
|
353
|
-
this.isValidBitcoinAddress(val) ? val : null,
|
|
354
|
-
token: (val) => val != null &&
|
|
355
|
-
typeof (val) === "string" &&
|
|
356
|
-
this.isTokenSupported(chainIdentifier, val) ? val : null,
|
|
357
|
-
amount: SchemaVerifier_1.FieldTypeEnum.BigInt,
|
|
358
|
-
exactIn: (val) => val === "true" ? true :
|
|
359
|
-
(val === "false" || val === undefined) ? false : null
|
|
360
|
-
});
|
|
361
|
-
if (parsedBody == null)
|
|
362
|
-
throw {
|
|
363
|
-
code: 20100,
|
|
364
|
-
msg: "Invalid request body"
|
|
365
|
-
};
|
|
366
|
-
metadata.request = parsedBody;
|
|
367
|
-
const refundAddress = parsedBody.refundAddress === "" ? null : parsedBody.refundAddress;
|
|
368
|
-
const requestedAmount = { input: parsedBody.exactIn, amount: parsedBody.amount, token: parsedBody.token };
|
|
369
|
-
const request = {
|
|
370
|
-
chainIdentifier,
|
|
371
|
-
raw: req,
|
|
372
|
-
parsed: parsedBody,
|
|
373
|
-
metadata
|
|
374
|
-
};
|
|
375
|
-
const useToken = parsedBody.token;
|
|
376
|
-
//Check request params
|
|
377
|
-
const fees = await this.AmountAssertions.preCheckFromBtcAmounts(this.type, request, requestedAmount);
|
|
378
|
-
metadata.times.requestChecked = Date.now();
|
|
379
|
-
//Create abortController for parallel prefetches
|
|
380
|
-
const responseStream = res.responseStream;
|
|
381
|
-
const abortController = (0, Utils_1.getAbortController)(responseStream);
|
|
382
|
-
//Pre-fetch data
|
|
383
|
-
const pricePrefetchPromise = this.swapPricing.preFetchPrice(useToken, chainIdentifier).catch(e => {
|
|
384
|
-
this.logger.error("pricePrefetchPromise(): pricePrefetch error: ", e);
|
|
385
|
-
abortController.abort(e);
|
|
386
|
-
return null;
|
|
387
|
-
});
|
|
388
|
-
const balancePrefetch = chainInterface.getBalance(signer.getAddress(), useToken).catch(e => {
|
|
389
|
-
this.logger.error("getBalancePrefetch(): balancePrefetch error: ", e);
|
|
390
|
-
abortController.abort(e);
|
|
391
|
-
return null;
|
|
392
|
-
});
|
|
393
|
-
//Check valid amount specified (min/max)
|
|
394
|
-
const { amountBD, swapFee, swapFeeInToken, totalInToken } = await this.AmountAssertions.checkFromBtcAmount(this.type, request, { ...requestedAmount, pricePrefetch: pricePrefetchPromise }, fees, abortController.signal);
|
|
395
|
-
metadata.times.priceCalculated = Date.now();
|
|
396
|
-
//Make sure we have MORE THAN ENOUGH to honor the swap request
|
|
397
|
-
await this.checkBalance(totalInToken * 4n, balancePrefetch, abortController.signal);
|
|
398
|
-
metadata.times.balanceChecked = Date.now();
|
|
399
|
-
const blockHeight = await this.bitcoin.getBlockheight();
|
|
400
|
-
const feeRate = await this.bitcoin.getFeeRate();
|
|
401
|
-
const recommendedFee = Math.ceil(feeRate * this.config.recommendFeeMultiplier);
|
|
402
|
-
if (recommendedFee === 0)
|
|
403
|
-
throw {
|
|
404
|
-
_httpStatus: 500,
|
|
405
|
-
code: 21100,
|
|
406
|
-
msg: "Cannot estimate bitcoin fee!"
|
|
407
|
-
};
|
|
408
|
-
metadata.times.feeEstimated = Date.now();
|
|
409
|
-
const receiveAddress = await this.bitcoin.getAddress();
|
|
410
|
-
const outputScript = this.bitcoin.toOutputScript(receiveAddress).toString("hex");
|
|
411
|
-
abortController.signal.throwIfAborted();
|
|
412
|
-
metadata.times.addressCreated = Date.now();
|
|
413
|
-
const createdSwap = new FromBtcTrustedSwap_1.FromBtcTrustedSwap(chainIdentifier, swapFee, swapFeeInToken, receiveAddress, amountBD, parsedBody.address, totalInToken, blockHeight, Date.now() + (this.config.swapAddressExpiry * 1000), recommendedFee, refundAddress, useToken);
|
|
414
|
-
metadata.times.swapCreated = Date.now();
|
|
415
|
-
createdSwap.metadata = metadata;
|
|
416
|
-
await PluginManager_1.PluginManager.swapCreate(createdSwap);
|
|
417
|
-
await this.storageManager.saveData(createdSwap.getIdentifierHash(), createdSwap.getSequence(), createdSwap);
|
|
418
|
-
this.subscriptions.set(outputScript, createdSwap);
|
|
419
|
-
this.swapLogger.info(createdSwap, "REST: /getAddress: Created swap address: " + createdSwap.btcAddress + " amount: " + amountBD.toString(10));
|
|
420
|
-
res.status(200).json({
|
|
421
|
-
code: 10000,
|
|
422
|
-
msg: "Success",
|
|
423
|
-
data: {
|
|
424
|
-
paymentHash: createdSwap.getIdentifierHash(),
|
|
425
|
-
sequence: createdSwap.getSequence().toString(10),
|
|
426
|
-
btcAddress: receiveAddress,
|
|
427
|
-
amountSats: amountBD.toString(10),
|
|
428
|
-
swapFeeSats: swapFee.toString(10),
|
|
429
|
-
swapFee: swapFeeInToken.toString(10),
|
|
430
|
-
total: totalInToken.toString(10),
|
|
431
|
-
intermediaryKey: signer.getAddress(),
|
|
432
|
-
recommendedFee,
|
|
433
|
-
expiresAt: createdSwap.expiresAt
|
|
434
|
-
}
|
|
435
|
-
});
|
|
436
|
-
});
|
|
437
|
-
restServer.get(this.path + "/getAddress", getAddress);
|
|
438
|
-
const getInvoiceStatus = (0, Utils_1.expressHandlerWrapper)(async (req, res) => {
|
|
439
|
-
/**
|
|
440
|
-
* paymentHash: string payment hash of the invoice
|
|
441
|
-
* sequence: BN secret sequence for the swap,
|
|
442
|
-
*/
|
|
443
|
-
const parsedBody = (0, SchemaVerifier_1.verifySchema)(req.query, {
|
|
444
|
-
paymentHash: (val) => val != null &&
|
|
445
|
-
typeof (val) === "string" &&
|
|
446
|
-
val.length === 64 &&
|
|
447
|
-
Utils_1.HEX_REGEX.test(val) ? val : null,
|
|
448
|
-
sequence: SchemaVerifier_1.FieldTypeEnum.BigInt,
|
|
449
|
-
});
|
|
450
|
-
if (parsedBody == null)
|
|
451
|
-
throw {
|
|
452
|
-
code: 20100,
|
|
453
|
-
msg: "Invalid request"
|
|
454
|
-
};
|
|
455
|
-
const processedTxData = this.processedTxIds.get(parsedBody.paymentHash);
|
|
456
|
-
if (processedTxData != null)
|
|
457
|
-
throw {
|
|
458
|
-
_httpStatus: 200,
|
|
459
|
-
code: 10000,
|
|
460
|
-
msg: "Success, tx confirmed",
|
|
461
|
-
data: {
|
|
462
|
-
adjustedAmount: processedTxData.adjustedAmount.toString(10),
|
|
463
|
-
adjustedTotal: processedTxData.adjustedTotal.toString(10),
|
|
464
|
-
txId: processedTxData.txId,
|
|
465
|
-
scTxId: processedTxData.scTxId
|
|
466
|
-
}
|
|
467
|
-
};
|
|
468
|
-
const refundTxId = this.refundedSwaps.get(parsedBody.paymentHash);
|
|
469
|
-
if (refundTxId != null)
|
|
470
|
-
throw {
|
|
471
|
-
_httpStatus: 200,
|
|
472
|
-
code: 10014,
|
|
473
|
-
msg: "Refunded",
|
|
474
|
-
data: {
|
|
475
|
-
txId: refundTxId
|
|
476
|
-
}
|
|
477
|
-
};
|
|
478
|
-
const doubleSpendTxId = this.doubleSpentSwaps.get(parsedBody.paymentHash);
|
|
479
|
-
if (doubleSpendTxId != null)
|
|
480
|
-
throw {
|
|
481
|
-
_httpStatus: 200,
|
|
482
|
-
code: 10015,
|
|
483
|
-
msg: "Double spend detected, deposit burned",
|
|
484
|
-
data: {
|
|
485
|
-
txId: doubleSpendTxId
|
|
486
|
-
}
|
|
487
|
-
};
|
|
488
|
-
const invoiceData = await this.storageManager.getData(parsedBody.paymentHash, parsedBody.sequence);
|
|
489
|
-
if (invoiceData == null)
|
|
490
|
-
throw {
|
|
491
|
-
_httpStatus: 200,
|
|
492
|
-
code: 10001,
|
|
493
|
-
msg: "Swap expired/canceled"
|
|
494
|
-
};
|
|
495
|
-
if (invoiceData.state === FromBtcTrustedSwap_1.FromBtcTrustedSwapState.CREATED)
|
|
496
|
-
throw {
|
|
497
|
-
_httpStatus: 200,
|
|
498
|
-
code: 10010,
|
|
499
|
-
msg: "Bitcoin yet unpaid"
|
|
500
|
-
};
|
|
501
|
-
if (invoiceData.state === FromBtcTrustedSwap_1.FromBtcTrustedSwapState.RECEIVED)
|
|
502
|
-
throw {
|
|
503
|
-
_httpStatus: 200,
|
|
504
|
-
code: 10011,
|
|
505
|
-
msg: "Bitcoin received, payment processing",
|
|
506
|
-
data: {
|
|
507
|
-
adjustedAmount: invoiceData.adjustedInput.toString(10),
|
|
508
|
-
adjustedTotal: invoiceData.adjustedOutput.toString(10),
|
|
509
|
-
txId: invoiceData.txId
|
|
510
|
-
}
|
|
511
|
-
};
|
|
512
|
-
if (invoiceData.state === FromBtcTrustedSwap_1.FromBtcTrustedSwapState.BTC_CONFIRMED)
|
|
513
|
-
throw {
|
|
514
|
-
_httpStatus: 200,
|
|
515
|
-
code: 10013,
|
|
516
|
-
msg: "Bitcoin accepted, payment processing",
|
|
517
|
-
data: {
|
|
518
|
-
adjustedAmount: invoiceData.adjustedInput.toString(10),
|
|
519
|
-
adjustedTotal: invoiceData.adjustedOutput.toString(10),
|
|
520
|
-
txId: invoiceData.txId
|
|
521
|
-
}
|
|
522
|
-
};
|
|
523
|
-
if (invoiceData.state === FromBtcTrustedSwap_1.FromBtcTrustedSwapState.SENT)
|
|
524
|
-
throw {
|
|
525
|
-
_httpStatus: 200,
|
|
526
|
-
code: 10012,
|
|
527
|
-
msg: "Tx sent",
|
|
528
|
-
data: {
|
|
529
|
-
adjustedAmount: invoiceData.adjustedInput.toString(10),
|
|
530
|
-
adjustedTotal: invoiceData.adjustedOutput.toString(10),
|
|
531
|
-
txId: invoiceData.txId,
|
|
532
|
-
scTxId: invoiceData.txIds.init
|
|
533
|
-
}
|
|
534
|
-
};
|
|
535
|
-
if (invoiceData.state === FromBtcTrustedSwap_1.FromBtcTrustedSwapState.CONFIRMED || invoiceData.state === FromBtcTrustedSwap_1.FromBtcTrustedSwapState.FINISHED)
|
|
536
|
-
throw {
|
|
537
|
-
_httpStatus: 200,
|
|
538
|
-
code: 10000,
|
|
539
|
-
msg: "Success, tx confirmed",
|
|
540
|
-
data: {
|
|
541
|
-
adjustedAmount: invoiceData.adjustedInput.toString(10),
|
|
542
|
-
adjustedTotal: invoiceData.adjustedOutput.toString(10),
|
|
543
|
-
txId: invoiceData.txId,
|
|
544
|
-
scTxId: invoiceData.txIds.init
|
|
545
|
-
}
|
|
546
|
-
};
|
|
547
|
-
if (invoiceData.state === FromBtcTrustedSwap_1.FromBtcTrustedSwapState.REFUNDABLE)
|
|
548
|
-
throw {
|
|
549
|
-
_httpStatus: 200,
|
|
550
|
-
code: 10016,
|
|
551
|
-
msg: "Refundable",
|
|
552
|
-
data: {
|
|
553
|
-
adjustedAmount: invoiceData.adjustedInput.toString(10)
|
|
554
|
-
}
|
|
555
|
-
};
|
|
556
|
-
});
|
|
557
|
-
restServer.get(this.path + "/getAddressStatus", getInvoiceStatus);
|
|
558
|
-
const setRefundAddress = (0, Utils_1.expressHandlerWrapper)(async (req, res) => {
|
|
559
|
-
/**
|
|
560
|
-
* paymentHash: string payment hash of the invoice
|
|
561
|
-
* sequence: BN secret sequence for the swap,
|
|
562
|
-
* refundAddress: string valid bitcoin address to be used for refunds
|
|
563
|
-
*/
|
|
564
|
-
const parsedBody = (0, SchemaVerifier_1.verifySchema)({ ...req.body, ...req.query }, {
|
|
565
|
-
paymentHash: (val) => val != null &&
|
|
566
|
-
typeof (val) === "string" &&
|
|
567
|
-
val.length === 64 &&
|
|
568
|
-
Utils_1.HEX_REGEX.test(val) ? val : null,
|
|
569
|
-
sequence: SchemaVerifier_1.FieldTypeEnum.BigInt,
|
|
570
|
-
refundAddress: (val) => val != null &&
|
|
571
|
-
typeof (val) === "string" &&
|
|
572
|
-
this.isValidBitcoinAddress(val) ? val : null
|
|
573
|
-
});
|
|
574
|
-
if (parsedBody == null)
|
|
575
|
-
throw {
|
|
576
|
-
code: 20100,
|
|
577
|
-
msg: "Invalid request"
|
|
578
|
-
};
|
|
579
|
-
const invoiceData = await this.storageManager.getData(parsedBody.paymentHash, null);
|
|
580
|
-
if (invoiceData == null || invoiceData.getSequence() !== parsedBody.sequence)
|
|
581
|
-
throw {
|
|
582
|
-
code: 10001,
|
|
583
|
-
msg: "Swap not found"
|
|
584
|
-
};
|
|
585
|
-
if (invoiceData.refundAddress != null)
|
|
586
|
-
throw {
|
|
587
|
-
code: 10080,
|
|
588
|
-
msg: "Refund address already set!",
|
|
589
|
-
data: {
|
|
590
|
-
refundAddress: invoiceData.refundAddress
|
|
591
|
-
}
|
|
592
|
-
};
|
|
593
|
-
invoiceData.refundAddress = parsedBody.refundAddress;
|
|
594
|
-
if (invoiceData.state === FromBtcTrustedSwap_1.FromBtcTrustedSwapState.REFUNDABLE) {
|
|
595
|
-
this.refundSwap(invoiceData).catch(e => {
|
|
596
|
-
this.swapLogger.error(invoiceData, "/setRefundAddress: Failed to refund!");
|
|
597
|
-
});
|
|
598
|
-
}
|
|
599
|
-
throw {
|
|
600
|
-
_httpStatus: 200,
|
|
601
|
-
code: 10000,
|
|
602
|
-
msg: "Refund address set"
|
|
603
|
-
};
|
|
604
|
-
});
|
|
605
|
-
restServer.get(this.path + "/setRefundAddress", setRefundAddress);
|
|
606
|
-
restServer.post(this.path + "/setRefundAddress", setRefundAddress);
|
|
607
|
-
this.logger.info("started at path: ", this.path);
|
|
608
|
-
}
|
|
609
|
-
async checkDoubleSpends() {
|
|
610
|
-
for (let swap of this.doubleSpendWatchdogSwaps.keys()) {
|
|
611
|
-
const tx = await this.bitcoin.getWalletTransaction(swap.txId);
|
|
612
|
-
if (tx == null) {
|
|
613
|
-
this.swapLogger.debug(swap, "checkDoubleSpends(): Swap was double spent, burning... - original txId: " + swap.txId);
|
|
614
|
-
this.processPastSwap(swap, null, null);
|
|
615
|
-
}
|
|
616
|
-
}
|
|
617
|
-
}
|
|
618
|
-
async startDoubleSpendWatchdog() {
|
|
619
|
-
let rerun;
|
|
620
|
-
rerun = async () => {
|
|
621
|
-
await this.checkDoubleSpends().catch(e => this.logger.error("startDoubleSpendWatchdog(): Error when checking double spends: ", e));
|
|
622
|
-
setTimeout(rerun, this.config.doubleSpendCheckInterval);
|
|
623
|
-
};
|
|
624
|
-
await rerun();
|
|
625
|
-
}
|
|
626
|
-
listenToTxns() {
|
|
627
|
-
this.bitcoin.subscribeToWalletTransactions((btcTx) => {
|
|
628
|
-
for (let out of btcTx.outs) {
|
|
629
|
-
const savedSwap = this.subscriptions.get(out.scriptPubKey.hex);
|
|
630
|
-
if (savedSwap == null)
|
|
631
|
-
continue;
|
|
632
|
-
this.processPastSwap(savedSwap, btcTx, out.n);
|
|
633
|
-
return;
|
|
634
|
-
}
|
|
635
|
-
});
|
|
636
|
-
}
|
|
637
|
-
async startWatchdog() {
|
|
638
|
-
await super.startWatchdog();
|
|
639
|
-
await this.startDoubleSpendWatchdog();
|
|
640
|
-
}
|
|
641
|
-
async init() {
|
|
642
|
-
await this.storageManager.loadData(FromBtcTrustedSwap_1.FromBtcTrustedSwap);
|
|
643
|
-
this.listenToTxns();
|
|
644
|
-
await PluginManager_1.PluginManager.serviceInitialize(this);
|
|
645
|
-
}
|
|
646
|
-
getInfoData() {
|
|
647
|
-
return {};
|
|
648
|
-
}
|
|
649
|
-
}
|
|
650
|
-
exports.FromBtcTrusted = FromBtcTrusted;
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.FromBtcTrusted = void 0;
|
|
4
|
+
const FromBtcTrustedSwap_1 = require("./FromBtcTrustedSwap");
|
|
5
|
+
const SwapHandler_1 = require("../../SwapHandler");
|
|
6
|
+
const PluginManager_1 = require("../../../plugins/PluginManager");
|
|
7
|
+
const Utils_1 = require("../../../utils/Utils");
|
|
8
|
+
const SchemaVerifier_1 = require("../../../utils/paramcoders/SchemaVerifier");
|
|
9
|
+
const FromBtcAmountAssertions_1 = require("../../assertions/FromBtcAmountAssertions");
|
|
10
|
+
class FromBtcTrusted extends SwapHandler_1.SwapHandler {
|
|
11
|
+
constructor(storageDirectory, path, chains, bitcoin, swapPricing, bitcoinRpc, config) {
|
|
12
|
+
var _a;
|
|
13
|
+
super(storageDirectory, path, chains, swapPricing);
|
|
14
|
+
this.type = SwapHandler_1.SwapHandlerType.FROM_BTC_TRUSTED;
|
|
15
|
+
this.subscriptions = new Map();
|
|
16
|
+
this.doubleSpendWatchdogSwaps = new Set();
|
|
17
|
+
this.refundedSwaps = new Map();
|
|
18
|
+
this.doubleSpentSwaps = new Map();
|
|
19
|
+
this.processedTxIds = new Map();
|
|
20
|
+
this.AmountAssertions = new FromBtcAmountAssertions_1.FromBtcAmountAssertions(config, swapPricing);
|
|
21
|
+
this.config = config;
|
|
22
|
+
(_a = this.config).recommendFeeMultiplier ?? (_a.recommendFeeMultiplier = 1.25);
|
|
23
|
+
this.bitcoin = bitcoin;
|
|
24
|
+
this.bitcoinRpc = bitcoinRpc;
|
|
25
|
+
}
|
|
26
|
+
getAllAncestors(tx) {
|
|
27
|
+
return Promise.all(tx.ins.map(input => this.bitcoinRpc.getTransaction(input.txid).then(tx => {
|
|
28
|
+
return { tx, vout: input.vout };
|
|
29
|
+
})));
|
|
30
|
+
}
|
|
31
|
+
async refundSwap(swap) {
|
|
32
|
+
if (swap.refundAddress == null) {
|
|
33
|
+
if (swap.state !== FromBtcTrustedSwap_1.FromBtcTrustedSwapState.REFUNDABLE) {
|
|
34
|
+
await swap.setState(FromBtcTrustedSwap_1.FromBtcTrustedSwapState.REFUNDABLE);
|
|
35
|
+
await this.storageManager.saveData(swap.getIdentifierHash(), swap.getSequence(), swap);
|
|
36
|
+
}
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
let unlock = swap.lock(30 * 1000);
|
|
40
|
+
if (unlock == null)
|
|
41
|
+
return;
|
|
42
|
+
const feeRate = await this.bitcoin.getFeeRate();
|
|
43
|
+
const ourOutput = swap.btcTx.outs[swap.vout];
|
|
44
|
+
const resp = await this.bitcoin.drainAll(swap.refundAddress, [{
|
|
45
|
+
type: this.bitcoin.getAddressType(),
|
|
46
|
+
confirmations: swap.btcTx.confirmations,
|
|
47
|
+
outputScript: Buffer.from(ourOutput.scriptPubKey.hex, "hex"),
|
|
48
|
+
value: ourOutput.value,
|
|
49
|
+
txId: swap.btcTx.txid,
|
|
50
|
+
vout: swap.vout
|
|
51
|
+
}], feeRate);
|
|
52
|
+
if (resp == null) {
|
|
53
|
+
this.swapLogger.error(swap, "refundSwap(): cannot refund swap because of dust limit, txId: " + swap.txId);
|
|
54
|
+
unlock();
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
if (swap.metadata != null)
|
|
58
|
+
swap.metadata.times.refundSignPSBT = Date.now();
|
|
59
|
+
this.swapLogger.debug(swap, "refundSwap(): signed raw transaction: " + resp.raw);
|
|
60
|
+
const refundTxId = resp.txId;
|
|
61
|
+
swap.refundTxId = refundTxId;
|
|
62
|
+
//Send the refund TX
|
|
63
|
+
await this.bitcoin.sendRawTransaction(resp.raw);
|
|
64
|
+
this.swapLogger.debug(swap, "refundSwap(): sent refund transaction: " + refundTxId);
|
|
65
|
+
this.refundedSwaps.set(swap.getIdentifierHash(), refundTxId);
|
|
66
|
+
await this.removeSwapData(swap, FromBtcTrustedSwap_1.FromBtcTrustedSwapState.REFUNDED);
|
|
67
|
+
unlock();
|
|
68
|
+
}
|
|
69
|
+
async burn(swap) {
|
|
70
|
+
const ourOutput = swap.btcTx.outs[swap.vout];
|
|
71
|
+
//Check if we can even increase the feeRate by burning
|
|
72
|
+
const txSize = 110;
|
|
73
|
+
const burnTxFeeRate = Math.floor(ourOutput.value / txSize);
|
|
74
|
+
const initialTxFeeRate = Math.ceil(swap.txFee / swap.txSize);
|
|
75
|
+
if (burnTxFeeRate < initialTxFeeRate) {
|
|
76
|
+
this.swapLogger.warn(swap, "burn(): cannot send burn transaction, pays too little fee, " +
|
|
77
|
+
"initialTxId: " + swap.txId + " initialTxFeeRate: " + initialTxFeeRate + " burnTxFeeRate: " + burnTxFeeRate);
|
|
78
|
+
this.doubleSpentSwaps.set(swap.getIdentifierHash(), null);
|
|
79
|
+
await this.removeSwapData(swap, FromBtcTrustedSwap_1.FromBtcTrustedSwapState.DOUBLE_SPENT);
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
//Construct PSBT
|
|
83
|
+
const resp = await this.bitcoin.burnAll([{
|
|
84
|
+
type: this.bitcoin.getAddressType(),
|
|
85
|
+
confirmations: swap.btcTx.confirmations,
|
|
86
|
+
outputScript: Buffer.from(ourOutput.scriptPubKey.hex, "hex"),
|
|
87
|
+
value: ourOutput.value,
|
|
88
|
+
txId: swap.btcTx.txid,
|
|
89
|
+
vout: swap.vout
|
|
90
|
+
}]);
|
|
91
|
+
if (swap.metadata != null)
|
|
92
|
+
swap.metadata.times.burnSignPSBT = Date.now();
|
|
93
|
+
this.swapLogger.debug(swap, "burn(): signed raw transaction: " + resp.raw);
|
|
94
|
+
const burnTxId = resp.txId;
|
|
95
|
+
swap.burnTxId = burnTxId;
|
|
96
|
+
//Send the original TX + our burn TX as a package
|
|
97
|
+
const sendTxns = [swap.btcTx.raw, resp.raw];
|
|
98
|
+
//TODO: We should handle this in a better way
|
|
99
|
+
try {
|
|
100
|
+
await this.bitcoinRpc.sendRawPackage(sendTxns);
|
|
101
|
+
this.swapLogger.debug(swap, "burn(): sent burn transaction: " + burnTxId);
|
|
102
|
+
}
|
|
103
|
+
catch (e) {
|
|
104
|
+
this.swapLogger.error(swap, "burn(): error sending burn package: ", e);
|
|
105
|
+
}
|
|
106
|
+
this.doubleSpentSwaps.set(swap.getIdentifierHash(), burnTxId);
|
|
107
|
+
await this.removeSwapData(swap, FromBtcTrustedSwap_1.FromBtcTrustedSwapState.DOUBLE_SPENT);
|
|
108
|
+
}
|
|
109
|
+
async processPastSwap(swap, tx, vout) {
|
|
110
|
+
const foundVout = tx.outs[vout];
|
|
111
|
+
const { chainInterface, signer } = this.getChain(swap.chainIdentifier);
|
|
112
|
+
const outputScript = this.bitcoin.toOutputScript(swap.btcAddress).toString("hex");
|
|
113
|
+
if (swap.state === FromBtcTrustedSwap_1.FromBtcTrustedSwapState.CREATED) {
|
|
114
|
+
this.subscriptions.set(outputScript, swap);
|
|
115
|
+
if (foundVout == null) {
|
|
116
|
+
//Check expiry
|
|
117
|
+
if (swap.expiresAt < Date.now()) {
|
|
118
|
+
this.subscriptions.delete(outputScript);
|
|
119
|
+
await this.bitcoin.addUnusedAddress(swap.btcAddress);
|
|
120
|
+
await this.removeSwapData(swap, FromBtcTrustedSwap_1.FromBtcTrustedSwapState.EXPIRED);
|
|
121
|
+
return;
|
|
122
|
+
}
|
|
123
|
+
return;
|
|
124
|
+
}
|
|
125
|
+
const sentSats = BigInt(foundVout.value);
|
|
126
|
+
if (sentSats === swap.amount) {
|
|
127
|
+
swap.adjustedInput = swap.amount;
|
|
128
|
+
swap.adjustedOutput = swap.outputTokens;
|
|
129
|
+
}
|
|
130
|
+
else {
|
|
131
|
+
//If lower than minimum then ignore
|
|
132
|
+
if (sentSats < this.config.min)
|
|
133
|
+
return;
|
|
134
|
+
if (sentSats > this.config.max) {
|
|
135
|
+
swap.adjustedInput = sentSats;
|
|
136
|
+
swap.btcTx = tx;
|
|
137
|
+
swap.txId = tx.txid;
|
|
138
|
+
swap.vout = vout;
|
|
139
|
+
this.subscriptions.delete(outputScript);
|
|
140
|
+
await this.refundSwap(swap);
|
|
141
|
+
return;
|
|
142
|
+
}
|
|
143
|
+
//Adjust the amount
|
|
144
|
+
swap.adjustedInput = sentSats;
|
|
145
|
+
swap.adjustedOutput = swap.outputTokens * sentSats / swap.amount;
|
|
146
|
+
}
|
|
147
|
+
swap.btcTx = tx;
|
|
148
|
+
swap.txId = tx.txid;
|
|
149
|
+
swap.vout = vout;
|
|
150
|
+
this.subscriptions.delete(outputScript);
|
|
151
|
+
await swap.setState(FromBtcTrustedSwap_1.FromBtcTrustedSwapState.RECEIVED);
|
|
152
|
+
await this.storageManager.saveData(swap.getIdentifierHash(), swap.getSequence(), swap);
|
|
153
|
+
}
|
|
154
|
+
if (swap.state === FromBtcTrustedSwap_1.FromBtcTrustedSwapState.RECEIVED) {
|
|
155
|
+
//Check if transaction still exists
|
|
156
|
+
if (tx == null || foundVout == null || tx.txid !== swap.txId) {
|
|
157
|
+
await swap.setState(FromBtcTrustedSwap_1.FromBtcTrustedSwapState.CREATED);
|
|
158
|
+
await this.storageManager.saveData(swap.getIdentifierHash(), swap.getSequence(), swap);
|
|
159
|
+
return;
|
|
160
|
+
}
|
|
161
|
+
//Check if it is confirmed
|
|
162
|
+
if (tx.confirmations > 0) {
|
|
163
|
+
await swap.setState(FromBtcTrustedSwap_1.FromBtcTrustedSwapState.BTC_CONFIRMED);
|
|
164
|
+
await this.storageManager.saveData(swap.getIdentifierHash(), swap.getSequence(), swap);
|
|
165
|
+
}
|
|
166
|
+
else {
|
|
167
|
+
//Check if it pays high enough fee AND has confirmed ancestors
|
|
168
|
+
const ancestors = await this.getAllAncestors(tx);
|
|
169
|
+
const allAncestorsConfirmed = ancestors.reduce((prev, curr) => prev && curr.tx.confirmations > 0, true);
|
|
170
|
+
const totalInput = ancestors.reduce((prev, curr) => prev + curr.tx.outs[curr.vout].value, 0);
|
|
171
|
+
const totalOutput = tx.outs.reduce((prev, curr) => prev + curr.value, 0);
|
|
172
|
+
const fee = totalInput - totalOutput;
|
|
173
|
+
const feePerVbyte = Math.ceil(fee / tx.vsize);
|
|
174
|
+
if (allAncestorsConfirmed &&
|
|
175
|
+
(feePerVbyte >= swap.recommendedFee || feePerVbyte >= await this.bitcoin.getFeeRate())) {
|
|
176
|
+
if (swap.state !== FromBtcTrustedSwap_1.FromBtcTrustedSwapState.RECEIVED)
|
|
177
|
+
return;
|
|
178
|
+
swap.txSize = tx.vsize;
|
|
179
|
+
swap.txFee = fee;
|
|
180
|
+
await swap.setState(FromBtcTrustedSwap_1.FromBtcTrustedSwapState.BTC_CONFIRMED);
|
|
181
|
+
await this.storageManager.saveData(swap.getIdentifierHash(), swap.getSequence(), swap);
|
|
182
|
+
}
|
|
183
|
+
else {
|
|
184
|
+
return;
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
if (swap.state === FromBtcTrustedSwap_1.FromBtcTrustedSwapState.REFUNDABLE) {
|
|
189
|
+
if (swap.refundAddress != null) {
|
|
190
|
+
await this.refundSwap(swap);
|
|
191
|
+
return;
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
if (swap.doubleSpent || tx == null || foundVout == null || tx.txid !== swap.txId) {
|
|
195
|
+
if (swap.state === FromBtcTrustedSwap_1.FromBtcTrustedSwapState.REFUNDABLE) {
|
|
196
|
+
await swap.setState(FromBtcTrustedSwap_1.FromBtcTrustedSwapState.CREATED);
|
|
197
|
+
return;
|
|
198
|
+
}
|
|
199
|
+
if (!swap.doubleSpent) {
|
|
200
|
+
swap.doubleSpent = true;
|
|
201
|
+
try {
|
|
202
|
+
await this.burn(swap);
|
|
203
|
+
this.doubleSpendWatchdogSwaps.delete(swap);
|
|
204
|
+
}
|
|
205
|
+
catch (e) {
|
|
206
|
+
this.swapLogger.error(swap, "processPastSwap(): Error burning swap: ", e);
|
|
207
|
+
swap.doubleSpent = false;
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
return;
|
|
211
|
+
}
|
|
212
|
+
else {
|
|
213
|
+
if (tx.confirmations <= 0 && !this.doubleSpendWatchdogSwaps.has(swap)) {
|
|
214
|
+
this.swapLogger.debug(swap, "processPastSwap(): Adding swap transaction to double spend watchdog list: ", swap.txId);
|
|
215
|
+
this.doubleSpendWatchdogSwaps.add(swap);
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
if (tx.confirmations > 0 && this.doubleSpendWatchdogSwaps.delete(swap)) {
|
|
219
|
+
this.swapLogger.debug(swap, "processPastSwap(): Removing confirmed swap transaction from double spend watchdog list: ", swap.txId);
|
|
220
|
+
}
|
|
221
|
+
if (swap.state === FromBtcTrustedSwap_1.FromBtcTrustedSwapState.BTC_CONFIRMED) {
|
|
222
|
+
//Send gas token
|
|
223
|
+
const balance = chainInterface.getBalance(signer.getAddress(), swap.token);
|
|
224
|
+
try {
|
|
225
|
+
await this.checkBalance(swap.adjustedOutput, balance, null);
|
|
226
|
+
if (swap.metadata != null)
|
|
227
|
+
swap.metadata.times.receivedBalanceChecked = Date.now();
|
|
228
|
+
}
|
|
229
|
+
catch (e) {
|
|
230
|
+
this.swapLogger.error(swap, "processPastSwap(): Error not enough balance: ", e);
|
|
231
|
+
await this.refundSwap(swap);
|
|
232
|
+
return;
|
|
233
|
+
}
|
|
234
|
+
if (swap.state !== FromBtcTrustedSwap_1.FromBtcTrustedSwapState.BTC_CONFIRMED)
|
|
235
|
+
return;
|
|
236
|
+
let unlock = swap.lock(30 * 1000);
|
|
237
|
+
if (unlock == null)
|
|
238
|
+
return;
|
|
239
|
+
const txns = await chainInterface.txsTransfer(signer.getAddress(), swap.token, swap.adjustedOutput, swap.dstAddress);
|
|
240
|
+
await chainInterface.sendAndConfirm(signer, txns, true, null, false, async (txId, rawTx) => {
|
|
241
|
+
swap.txIds = { init: txId };
|
|
242
|
+
swap.scRawTx = rawTx;
|
|
243
|
+
if (swap.state === FromBtcTrustedSwap_1.FromBtcTrustedSwapState.BTC_CONFIRMED) {
|
|
244
|
+
await swap.setState(FromBtcTrustedSwap_1.FromBtcTrustedSwapState.SENT);
|
|
245
|
+
await this.storageManager.saveData(swap.getIdentifierHash(), swap.getSequence(), swap);
|
|
246
|
+
}
|
|
247
|
+
if (unlock != null)
|
|
248
|
+
unlock();
|
|
249
|
+
unlock = null;
|
|
250
|
+
});
|
|
251
|
+
}
|
|
252
|
+
if (swap.state === FromBtcTrustedSwap_1.FromBtcTrustedSwapState.SENT) {
|
|
253
|
+
const txStatus = await chainInterface.getTxStatus(swap.scRawTx);
|
|
254
|
+
switch (txStatus) {
|
|
255
|
+
case "not_found":
|
|
256
|
+
//Retry
|
|
257
|
+
swap.txIds = { init: null };
|
|
258
|
+
swap.scRawTx = null;
|
|
259
|
+
await swap.setState(FromBtcTrustedSwap_1.FromBtcTrustedSwapState.RECEIVED);
|
|
260
|
+
await this.storageManager.saveData(swap.getIdentifierHash(), swap.getSequence(), swap);
|
|
261
|
+
break;
|
|
262
|
+
case "reverted":
|
|
263
|
+
//Cancel invoice
|
|
264
|
+
await this.refundSwap(swap);
|
|
265
|
+
this.swapLogger.info(swap, "processPastSwap(): transaction reverted, refunding btc on-chain: ", swap.btcAddress);
|
|
266
|
+
break;
|
|
267
|
+
case "success":
|
|
268
|
+
await swap.setState(FromBtcTrustedSwap_1.FromBtcTrustedSwapState.CONFIRMED);
|
|
269
|
+
await this.storageManager.saveData(swap.getIdentifierHash(), swap.getSequence(), swap);
|
|
270
|
+
break;
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
if (swap.state === FromBtcTrustedSwap_1.FromBtcTrustedSwapState.CONFIRMED) {
|
|
274
|
+
this.processedTxIds.set(swap.getIdentifierHash(), {
|
|
275
|
+
txId: swap.txId,
|
|
276
|
+
scTxId: swap.txIds.init,
|
|
277
|
+
adjustedAmount: swap.adjustedInput,
|
|
278
|
+
adjustedTotal: swap.adjustedOutput
|
|
279
|
+
});
|
|
280
|
+
if (tx.confirmations > 0)
|
|
281
|
+
await this.removeSwapData(swap, FromBtcTrustedSwap_1.FromBtcTrustedSwapState.FINISHED);
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
async processPastSwaps() {
|
|
285
|
+
const queriedData = await this.storageManager.query([
|
|
286
|
+
{
|
|
287
|
+
key: "state",
|
|
288
|
+
value: [
|
|
289
|
+
FromBtcTrustedSwap_1.FromBtcTrustedSwapState.REFUNDABLE,
|
|
290
|
+
FromBtcTrustedSwap_1.FromBtcTrustedSwapState.CREATED,
|
|
291
|
+
FromBtcTrustedSwap_1.FromBtcTrustedSwapState.RECEIVED,
|
|
292
|
+
FromBtcTrustedSwap_1.FromBtcTrustedSwapState.BTC_CONFIRMED,
|
|
293
|
+
FromBtcTrustedSwap_1.FromBtcTrustedSwapState.SENT,
|
|
294
|
+
FromBtcTrustedSwap_1.FromBtcTrustedSwapState.CONFIRMED
|
|
295
|
+
]
|
|
296
|
+
}
|
|
297
|
+
]);
|
|
298
|
+
const startingBlockheight = queriedData.reduce((prev, { obj: swap }) => Math.min(prev, swap.createdHeight), Infinity);
|
|
299
|
+
if (startingBlockheight === Infinity)
|
|
300
|
+
return;
|
|
301
|
+
const transactions = await this.bitcoin.getWalletTransactions(startingBlockheight);
|
|
302
|
+
const map = new Map();
|
|
303
|
+
transactions.forEach(tx => {
|
|
304
|
+
tx.outs.forEach((out, vout) => {
|
|
305
|
+
const existing = map.get(out.scriptPubKey.hex);
|
|
306
|
+
if (existing == null) {
|
|
307
|
+
map.set(out.scriptPubKey.hex, [{ tx, vout }]);
|
|
308
|
+
}
|
|
309
|
+
else {
|
|
310
|
+
existing.push({ tx, vout });
|
|
311
|
+
}
|
|
312
|
+
});
|
|
313
|
+
});
|
|
314
|
+
for (let { obj: swap } of queriedData) {
|
|
315
|
+
const outputScript = this.bitcoin.toOutputScript(swap.btcAddress).toString("hex");
|
|
316
|
+
const txs = map.get(outputScript) ?? [];
|
|
317
|
+
try {
|
|
318
|
+
await this.processPastSwap(swap, txs[0]?.tx, txs[0]?.vout);
|
|
319
|
+
}
|
|
320
|
+
catch (e) {
|
|
321
|
+
this.swapLogger.error(swap, "processPastSwaps(): Error ocurred while processing swap: ", e);
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
isValidBitcoinAddress(address) {
|
|
326
|
+
try {
|
|
327
|
+
this.bitcoin.toOutputScript(address);
|
|
328
|
+
return true;
|
|
329
|
+
}
|
|
330
|
+
catch (e) { }
|
|
331
|
+
return false;
|
|
332
|
+
}
|
|
333
|
+
startRestServer(restServer) {
|
|
334
|
+
const getAddress = (0, Utils_1.expressHandlerWrapper)(async (req, res) => {
|
|
335
|
+
var _a;
|
|
336
|
+
const metadata = { request: {}, times: {} };
|
|
337
|
+
const chainIdentifier = req.query.chain ?? this.chains.default;
|
|
338
|
+
const { chainInterface, signer } = this.getChain(chainIdentifier);
|
|
339
|
+
metadata.times.requestReceived = Date.now();
|
|
340
|
+
/**
|
|
341
|
+
* address: string solana address of the recipient
|
|
342
|
+
* refundAddress?: string bitcoin address to use in case of refund
|
|
343
|
+
* amount: string amount (in lamports/smart chain base units) of the invoice
|
|
344
|
+
* exactOut: boolean whether to create and exact output swap
|
|
345
|
+
*/
|
|
346
|
+
(_a = req.query).token ?? (_a.token = chainInterface.getNativeCurrencyAddress());
|
|
347
|
+
const parsedBody = (0, SchemaVerifier_1.verifySchema)(req.query, {
|
|
348
|
+
address: (val) => val != null &&
|
|
349
|
+
typeof (val) === "string" &&
|
|
350
|
+
chainInterface.isValidAddress(val) ? val : null,
|
|
351
|
+
refundAddress: (val) => val == null ? "" :
|
|
352
|
+
typeof (val) === "string" &&
|
|
353
|
+
this.isValidBitcoinAddress(val) ? val : null,
|
|
354
|
+
token: (val) => val != null &&
|
|
355
|
+
typeof (val) === "string" &&
|
|
356
|
+
this.isTokenSupported(chainIdentifier, val) ? val : null,
|
|
357
|
+
amount: SchemaVerifier_1.FieldTypeEnum.BigInt,
|
|
358
|
+
exactIn: (val) => val === "true" ? true :
|
|
359
|
+
(val === "false" || val === undefined) ? false : null
|
|
360
|
+
});
|
|
361
|
+
if (parsedBody == null)
|
|
362
|
+
throw {
|
|
363
|
+
code: 20100,
|
|
364
|
+
msg: "Invalid request body"
|
|
365
|
+
};
|
|
366
|
+
metadata.request = parsedBody;
|
|
367
|
+
const refundAddress = parsedBody.refundAddress === "" ? null : parsedBody.refundAddress;
|
|
368
|
+
const requestedAmount = { input: parsedBody.exactIn, amount: parsedBody.amount, token: parsedBody.token };
|
|
369
|
+
const request = {
|
|
370
|
+
chainIdentifier,
|
|
371
|
+
raw: req,
|
|
372
|
+
parsed: parsedBody,
|
|
373
|
+
metadata
|
|
374
|
+
};
|
|
375
|
+
const useToken = parsedBody.token;
|
|
376
|
+
//Check request params
|
|
377
|
+
const fees = await this.AmountAssertions.preCheckFromBtcAmounts(this.type, request, requestedAmount);
|
|
378
|
+
metadata.times.requestChecked = Date.now();
|
|
379
|
+
//Create abortController for parallel prefetches
|
|
380
|
+
const responseStream = res.responseStream;
|
|
381
|
+
const abortController = (0, Utils_1.getAbortController)(responseStream);
|
|
382
|
+
//Pre-fetch data
|
|
383
|
+
const pricePrefetchPromise = this.swapPricing.preFetchPrice(useToken, chainIdentifier).catch(e => {
|
|
384
|
+
this.logger.error("pricePrefetchPromise(): pricePrefetch error: ", e);
|
|
385
|
+
abortController.abort(e);
|
|
386
|
+
return null;
|
|
387
|
+
});
|
|
388
|
+
const balancePrefetch = chainInterface.getBalance(signer.getAddress(), useToken).catch(e => {
|
|
389
|
+
this.logger.error("getBalancePrefetch(): balancePrefetch error: ", e);
|
|
390
|
+
abortController.abort(e);
|
|
391
|
+
return null;
|
|
392
|
+
});
|
|
393
|
+
//Check valid amount specified (min/max)
|
|
394
|
+
const { amountBD, swapFee, swapFeeInToken, totalInToken } = await this.AmountAssertions.checkFromBtcAmount(this.type, request, { ...requestedAmount, pricePrefetch: pricePrefetchPromise }, fees, abortController.signal);
|
|
395
|
+
metadata.times.priceCalculated = Date.now();
|
|
396
|
+
//Make sure we have MORE THAN ENOUGH to honor the swap request
|
|
397
|
+
await this.checkBalance(totalInToken * 4n, balancePrefetch, abortController.signal);
|
|
398
|
+
metadata.times.balanceChecked = Date.now();
|
|
399
|
+
const blockHeight = await this.bitcoin.getBlockheight();
|
|
400
|
+
const feeRate = await this.bitcoin.getFeeRate();
|
|
401
|
+
const recommendedFee = Math.ceil(feeRate * this.config.recommendFeeMultiplier);
|
|
402
|
+
if (recommendedFee === 0)
|
|
403
|
+
throw {
|
|
404
|
+
_httpStatus: 500,
|
|
405
|
+
code: 21100,
|
|
406
|
+
msg: "Cannot estimate bitcoin fee!"
|
|
407
|
+
};
|
|
408
|
+
metadata.times.feeEstimated = Date.now();
|
|
409
|
+
const receiveAddress = await this.bitcoin.getAddress();
|
|
410
|
+
const outputScript = this.bitcoin.toOutputScript(receiveAddress).toString("hex");
|
|
411
|
+
abortController.signal.throwIfAborted();
|
|
412
|
+
metadata.times.addressCreated = Date.now();
|
|
413
|
+
const createdSwap = new FromBtcTrustedSwap_1.FromBtcTrustedSwap(chainIdentifier, swapFee, swapFeeInToken, receiveAddress, amountBD, parsedBody.address, totalInToken, blockHeight, Date.now() + (this.config.swapAddressExpiry * 1000), recommendedFee, refundAddress, useToken);
|
|
414
|
+
metadata.times.swapCreated = Date.now();
|
|
415
|
+
createdSwap.metadata = metadata;
|
|
416
|
+
await PluginManager_1.PluginManager.swapCreate(createdSwap);
|
|
417
|
+
await this.storageManager.saveData(createdSwap.getIdentifierHash(), createdSwap.getSequence(), createdSwap);
|
|
418
|
+
this.subscriptions.set(outputScript, createdSwap);
|
|
419
|
+
this.swapLogger.info(createdSwap, "REST: /getAddress: Created swap address: " + createdSwap.btcAddress + " amount: " + amountBD.toString(10));
|
|
420
|
+
res.status(200).json({
|
|
421
|
+
code: 10000,
|
|
422
|
+
msg: "Success",
|
|
423
|
+
data: {
|
|
424
|
+
paymentHash: createdSwap.getIdentifierHash(),
|
|
425
|
+
sequence: createdSwap.getSequence().toString(10),
|
|
426
|
+
btcAddress: receiveAddress,
|
|
427
|
+
amountSats: amountBD.toString(10),
|
|
428
|
+
swapFeeSats: swapFee.toString(10),
|
|
429
|
+
swapFee: swapFeeInToken.toString(10),
|
|
430
|
+
total: totalInToken.toString(10),
|
|
431
|
+
intermediaryKey: signer.getAddress(),
|
|
432
|
+
recommendedFee,
|
|
433
|
+
expiresAt: createdSwap.expiresAt
|
|
434
|
+
}
|
|
435
|
+
});
|
|
436
|
+
});
|
|
437
|
+
restServer.get(this.path + "/getAddress", getAddress);
|
|
438
|
+
const getInvoiceStatus = (0, Utils_1.expressHandlerWrapper)(async (req, res) => {
|
|
439
|
+
/**
|
|
440
|
+
* paymentHash: string payment hash of the invoice
|
|
441
|
+
* sequence: BN secret sequence for the swap,
|
|
442
|
+
*/
|
|
443
|
+
const parsedBody = (0, SchemaVerifier_1.verifySchema)(req.query, {
|
|
444
|
+
paymentHash: (val) => val != null &&
|
|
445
|
+
typeof (val) === "string" &&
|
|
446
|
+
val.length === 64 &&
|
|
447
|
+
Utils_1.HEX_REGEX.test(val) ? val : null,
|
|
448
|
+
sequence: SchemaVerifier_1.FieldTypeEnum.BigInt,
|
|
449
|
+
});
|
|
450
|
+
if (parsedBody == null)
|
|
451
|
+
throw {
|
|
452
|
+
code: 20100,
|
|
453
|
+
msg: "Invalid request"
|
|
454
|
+
};
|
|
455
|
+
const processedTxData = this.processedTxIds.get(parsedBody.paymentHash);
|
|
456
|
+
if (processedTxData != null)
|
|
457
|
+
throw {
|
|
458
|
+
_httpStatus: 200,
|
|
459
|
+
code: 10000,
|
|
460
|
+
msg: "Success, tx confirmed",
|
|
461
|
+
data: {
|
|
462
|
+
adjustedAmount: processedTxData.adjustedAmount.toString(10),
|
|
463
|
+
adjustedTotal: processedTxData.adjustedTotal.toString(10),
|
|
464
|
+
txId: processedTxData.txId,
|
|
465
|
+
scTxId: processedTxData.scTxId
|
|
466
|
+
}
|
|
467
|
+
};
|
|
468
|
+
const refundTxId = this.refundedSwaps.get(parsedBody.paymentHash);
|
|
469
|
+
if (refundTxId != null)
|
|
470
|
+
throw {
|
|
471
|
+
_httpStatus: 200,
|
|
472
|
+
code: 10014,
|
|
473
|
+
msg: "Refunded",
|
|
474
|
+
data: {
|
|
475
|
+
txId: refundTxId
|
|
476
|
+
}
|
|
477
|
+
};
|
|
478
|
+
const doubleSpendTxId = this.doubleSpentSwaps.get(parsedBody.paymentHash);
|
|
479
|
+
if (doubleSpendTxId != null)
|
|
480
|
+
throw {
|
|
481
|
+
_httpStatus: 200,
|
|
482
|
+
code: 10015,
|
|
483
|
+
msg: "Double spend detected, deposit burned",
|
|
484
|
+
data: {
|
|
485
|
+
txId: doubleSpendTxId
|
|
486
|
+
}
|
|
487
|
+
};
|
|
488
|
+
const invoiceData = await this.storageManager.getData(parsedBody.paymentHash, parsedBody.sequence);
|
|
489
|
+
if (invoiceData == null)
|
|
490
|
+
throw {
|
|
491
|
+
_httpStatus: 200,
|
|
492
|
+
code: 10001,
|
|
493
|
+
msg: "Swap expired/canceled"
|
|
494
|
+
};
|
|
495
|
+
if (invoiceData.state === FromBtcTrustedSwap_1.FromBtcTrustedSwapState.CREATED)
|
|
496
|
+
throw {
|
|
497
|
+
_httpStatus: 200,
|
|
498
|
+
code: 10010,
|
|
499
|
+
msg: "Bitcoin yet unpaid"
|
|
500
|
+
};
|
|
501
|
+
if (invoiceData.state === FromBtcTrustedSwap_1.FromBtcTrustedSwapState.RECEIVED)
|
|
502
|
+
throw {
|
|
503
|
+
_httpStatus: 200,
|
|
504
|
+
code: 10011,
|
|
505
|
+
msg: "Bitcoin received, payment processing",
|
|
506
|
+
data: {
|
|
507
|
+
adjustedAmount: invoiceData.adjustedInput.toString(10),
|
|
508
|
+
adjustedTotal: invoiceData.adjustedOutput.toString(10),
|
|
509
|
+
txId: invoiceData.txId
|
|
510
|
+
}
|
|
511
|
+
};
|
|
512
|
+
if (invoiceData.state === FromBtcTrustedSwap_1.FromBtcTrustedSwapState.BTC_CONFIRMED)
|
|
513
|
+
throw {
|
|
514
|
+
_httpStatus: 200,
|
|
515
|
+
code: 10013,
|
|
516
|
+
msg: "Bitcoin accepted, payment processing",
|
|
517
|
+
data: {
|
|
518
|
+
adjustedAmount: invoiceData.adjustedInput.toString(10),
|
|
519
|
+
adjustedTotal: invoiceData.adjustedOutput.toString(10),
|
|
520
|
+
txId: invoiceData.txId
|
|
521
|
+
}
|
|
522
|
+
};
|
|
523
|
+
if (invoiceData.state === FromBtcTrustedSwap_1.FromBtcTrustedSwapState.SENT)
|
|
524
|
+
throw {
|
|
525
|
+
_httpStatus: 200,
|
|
526
|
+
code: 10012,
|
|
527
|
+
msg: "Tx sent",
|
|
528
|
+
data: {
|
|
529
|
+
adjustedAmount: invoiceData.adjustedInput.toString(10),
|
|
530
|
+
adjustedTotal: invoiceData.adjustedOutput.toString(10),
|
|
531
|
+
txId: invoiceData.txId,
|
|
532
|
+
scTxId: invoiceData.txIds.init
|
|
533
|
+
}
|
|
534
|
+
};
|
|
535
|
+
if (invoiceData.state === FromBtcTrustedSwap_1.FromBtcTrustedSwapState.CONFIRMED || invoiceData.state === FromBtcTrustedSwap_1.FromBtcTrustedSwapState.FINISHED)
|
|
536
|
+
throw {
|
|
537
|
+
_httpStatus: 200,
|
|
538
|
+
code: 10000,
|
|
539
|
+
msg: "Success, tx confirmed",
|
|
540
|
+
data: {
|
|
541
|
+
adjustedAmount: invoiceData.adjustedInput.toString(10),
|
|
542
|
+
adjustedTotal: invoiceData.adjustedOutput.toString(10),
|
|
543
|
+
txId: invoiceData.txId,
|
|
544
|
+
scTxId: invoiceData.txIds.init
|
|
545
|
+
}
|
|
546
|
+
};
|
|
547
|
+
if (invoiceData.state === FromBtcTrustedSwap_1.FromBtcTrustedSwapState.REFUNDABLE)
|
|
548
|
+
throw {
|
|
549
|
+
_httpStatus: 200,
|
|
550
|
+
code: 10016,
|
|
551
|
+
msg: "Refundable",
|
|
552
|
+
data: {
|
|
553
|
+
adjustedAmount: invoiceData.adjustedInput.toString(10)
|
|
554
|
+
}
|
|
555
|
+
};
|
|
556
|
+
});
|
|
557
|
+
restServer.get(this.path + "/getAddressStatus", getInvoiceStatus);
|
|
558
|
+
const setRefundAddress = (0, Utils_1.expressHandlerWrapper)(async (req, res) => {
|
|
559
|
+
/**
|
|
560
|
+
* paymentHash: string payment hash of the invoice
|
|
561
|
+
* sequence: BN secret sequence for the swap,
|
|
562
|
+
* refundAddress: string valid bitcoin address to be used for refunds
|
|
563
|
+
*/
|
|
564
|
+
const parsedBody = (0, SchemaVerifier_1.verifySchema)({ ...req.body, ...req.query }, {
|
|
565
|
+
paymentHash: (val) => val != null &&
|
|
566
|
+
typeof (val) === "string" &&
|
|
567
|
+
val.length === 64 &&
|
|
568
|
+
Utils_1.HEX_REGEX.test(val) ? val : null,
|
|
569
|
+
sequence: SchemaVerifier_1.FieldTypeEnum.BigInt,
|
|
570
|
+
refundAddress: (val) => val != null &&
|
|
571
|
+
typeof (val) === "string" &&
|
|
572
|
+
this.isValidBitcoinAddress(val) ? val : null
|
|
573
|
+
});
|
|
574
|
+
if (parsedBody == null)
|
|
575
|
+
throw {
|
|
576
|
+
code: 20100,
|
|
577
|
+
msg: "Invalid request"
|
|
578
|
+
};
|
|
579
|
+
const invoiceData = await this.storageManager.getData(parsedBody.paymentHash, null);
|
|
580
|
+
if (invoiceData == null || invoiceData.getSequence() !== parsedBody.sequence)
|
|
581
|
+
throw {
|
|
582
|
+
code: 10001,
|
|
583
|
+
msg: "Swap not found"
|
|
584
|
+
};
|
|
585
|
+
if (invoiceData.refundAddress != null)
|
|
586
|
+
throw {
|
|
587
|
+
code: 10080,
|
|
588
|
+
msg: "Refund address already set!",
|
|
589
|
+
data: {
|
|
590
|
+
refundAddress: invoiceData.refundAddress
|
|
591
|
+
}
|
|
592
|
+
};
|
|
593
|
+
invoiceData.refundAddress = parsedBody.refundAddress;
|
|
594
|
+
if (invoiceData.state === FromBtcTrustedSwap_1.FromBtcTrustedSwapState.REFUNDABLE) {
|
|
595
|
+
this.refundSwap(invoiceData).catch(e => {
|
|
596
|
+
this.swapLogger.error(invoiceData, "/setRefundAddress: Failed to refund!");
|
|
597
|
+
});
|
|
598
|
+
}
|
|
599
|
+
throw {
|
|
600
|
+
_httpStatus: 200,
|
|
601
|
+
code: 10000,
|
|
602
|
+
msg: "Refund address set"
|
|
603
|
+
};
|
|
604
|
+
});
|
|
605
|
+
restServer.get(this.path + "/setRefundAddress", setRefundAddress);
|
|
606
|
+
restServer.post(this.path + "/setRefundAddress", setRefundAddress);
|
|
607
|
+
this.logger.info("started at path: ", this.path);
|
|
608
|
+
}
|
|
609
|
+
async checkDoubleSpends() {
|
|
610
|
+
for (let swap of this.doubleSpendWatchdogSwaps.keys()) {
|
|
611
|
+
const tx = await this.bitcoin.getWalletTransaction(swap.txId);
|
|
612
|
+
if (tx == null) {
|
|
613
|
+
this.swapLogger.debug(swap, "checkDoubleSpends(): Swap was double spent, burning... - original txId: " + swap.txId);
|
|
614
|
+
this.processPastSwap(swap, null, null);
|
|
615
|
+
}
|
|
616
|
+
}
|
|
617
|
+
}
|
|
618
|
+
async startDoubleSpendWatchdog() {
|
|
619
|
+
let rerun;
|
|
620
|
+
rerun = async () => {
|
|
621
|
+
await this.checkDoubleSpends().catch(e => this.logger.error("startDoubleSpendWatchdog(): Error when checking double spends: ", e));
|
|
622
|
+
setTimeout(rerun, this.config.doubleSpendCheckInterval);
|
|
623
|
+
};
|
|
624
|
+
await rerun();
|
|
625
|
+
}
|
|
626
|
+
listenToTxns() {
|
|
627
|
+
this.bitcoin.subscribeToWalletTransactions((btcTx) => {
|
|
628
|
+
for (let out of btcTx.outs) {
|
|
629
|
+
const savedSwap = this.subscriptions.get(out.scriptPubKey.hex);
|
|
630
|
+
if (savedSwap == null)
|
|
631
|
+
continue;
|
|
632
|
+
this.processPastSwap(savedSwap, btcTx, out.n);
|
|
633
|
+
return;
|
|
634
|
+
}
|
|
635
|
+
});
|
|
636
|
+
}
|
|
637
|
+
async startWatchdog() {
|
|
638
|
+
await super.startWatchdog();
|
|
639
|
+
await this.startDoubleSpendWatchdog();
|
|
640
|
+
}
|
|
641
|
+
async init() {
|
|
642
|
+
await this.storageManager.loadData(FromBtcTrustedSwap_1.FromBtcTrustedSwap);
|
|
643
|
+
this.listenToTxns();
|
|
644
|
+
await PluginManager_1.PluginManager.serviceInitialize(this);
|
|
645
|
+
}
|
|
646
|
+
getInfoData() {
|
|
647
|
+
return {};
|
|
648
|
+
}
|
|
649
|
+
}
|
|
650
|
+
exports.FromBtcTrusted = FromBtcTrusted;
|