@atomiqlabs/lp-lib 10.3.11
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 -0
- package/dist/fees/IBtcFeeEstimator.d.ts +3 -0
- package/dist/fees/IBtcFeeEstimator.js +2 -0
- package/dist/fees/OneDollarFeeEstimator.d.ts +16 -0
- package/dist/fees/OneDollarFeeEstimator.js +71 -0
- package/dist/index.d.ts +33 -0
- package/dist/index.js +52 -0
- package/dist/info/InfoHandler.d.ts +17 -0
- package/dist/info/InfoHandler.js +70 -0
- package/dist/plugins/IPlugin.d.ts +118 -0
- package/dist/plugins/IPlugin.js +33 -0
- package/dist/plugins/PluginManager.d.ts +89 -0
- package/dist/plugins/PluginManager.js +263 -0
- package/dist/prices/BinanceSwapPrice.d.ts +27 -0
- package/dist/prices/BinanceSwapPrice.js +106 -0
- package/dist/prices/CoinGeckoSwapPrice.d.ts +31 -0
- package/dist/prices/CoinGeckoSwapPrice.js +76 -0
- package/dist/storage/IIntermediaryStorage.d.ts +15 -0
- package/dist/storage/IIntermediaryStorage.js +2 -0
- package/dist/storagemanager/IntermediaryStorageManager.d.ts +15 -0
- package/dist/storagemanager/IntermediaryStorageManager.js +113 -0
- package/dist/storagemanager/StorageManager.d.ts +12 -0
- package/dist/storagemanager/StorageManager.js +74 -0
- package/dist/swaps/FromBtcBaseSwap.d.ts +12 -0
- package/dist/swaps/FromBtcBaseSwap.js +16 -0
- package/dist/swaps/FromBtcBaseSwapHandler.d.ts +118 -0
- package/dist/swaps/FromBtcBaseSwapHandler.js +294 -0
- package/dist/swaps/FromBtcLnBaseSwapHandler.d.ts +25 -0
- package/dist/swaps/FromBtcLnBaseSwapHandler.js +55 -0
- package/dist/swaps/ISwapPrice.d.ts +44 -0
- package/dist/swaps/ISwapPrice.js +73 -0
- package/dist/swaps/SwapHandler.d.ts +186 -0
- package/dist/swaps/SwapHandler.js +292 -0
- package/dist/swaps/SwapHandlerSwap.d.ts +75 -0
- package/dist/swaps/SwapHandlerSwap.js +72 -0
- package/dist/swaps/ToBtcBaseSwap.d.ts +35 -0
- package/dist/swaps/ToBtcBaseSwap.js +61 -0
- package/dist/swaps/ToBtcBaseSwapHandler.d.ts +94 -0
- package/dist/swaps/ToBtcBaseSwapHandler.js +233 -0
- package/dist/swaps/frombtc_abstract/FromBtcAbs.d.ts +92 -0
- package/dist/swaps/frombtc_abstract/FromBtcAbs.js +386 -0
- package/dist/swaps/frombtc_abstract/FromBtcSwapAbs.d.ts +26 -0
- package/dist/swaps/frombtc_abstract/FromBtcSwapAbs.js +63 -0
- package/dist/swaps/frombtc_trusted/FromBtcTrusted.d.ts +55 -0
- package/dist/swaps/frombtc_trusted/FromBtcTrusted.js +586 -0
- package/dist/swaps/frombtc_trusted/FromBtcTrustedSwap.d.ts +43 -0
- package/dist/swaps/frombtc_trusted/FromBtcTrustedSwap.js +99 -0
- package/dist/swaps/frombtcln_abstract/FromBtcLnAbs.d.ts +105 -0
- package/dist/swaps/frombtcln_abstract/FromBtcLnAbs.js +731 -0
- package/dist/swaps/frombtcln_abstract/FromBtcLnSwapAbs.d.ts +29 -0
- package/dist/swaps/frombtcln_abstract/FromBtcLnSwapAbs.js +64 -0
- package/dist/swaps/frombtcln_trusted/FromBtcLnTrusted.d.ts +79 -0
- package/dist/swaps/frombtcln_trusted/FromBtcLnTrusted.js +514 -0
- package/dist/swaps/frombtcln_trusted/FromBtcLnTrustedSwap.d.ts +28 -0
- package/dist/swaps/frombtcln_trusted/FromBtcLnTrustedSwap.js +66 -0
- package/dist/swaps/tobtc_abstract/ToBtcAbs.d.ts +290 -0
- package/dist/swaps/tobtc_abstract/ToBtcAbs.js +1056 -0
- package/dist/swaps/tobtc_abstract/ToBtcSwapAbs.d.ts +29 -0
- package/dist/swaps/tobtc_abstract/ToBtcSwapAbs.js +70 -0
- package/dist/swaps/tobtcln_abstract/ToBtcLnAbs.d.ts +246 -0
- package/dist/swaps/tobtcln_abstract/ToBtcLnAbs.js +1169 -0
- package/dist/swaps/tobtcln_abstract/ToBtcLnSwapAbs.d.ts +27 -0
- package/dist/swaps/tobtcln_abstract/ToBtcLnSwapAbs.js +65 -0
- package/dist/utils/Utils.d.ts +32 -0
- package/dist/utils/Utils.js +109 -0
- package/dist/utils/coinselect2/accumulative.d.ts +6 -0
- package/dist/utils/coinselect2/accumulative.js +44 -0
- package/dist/utils/coinselect2/blackjack.d.ts +6 -0
- package/dist/utils/coinselect2/blackjack.js +41 -0
- package/dist/utils/coinselect2/index.d.ts +16 -0
- package/dist/utils/coinselect2/index.js +40 -0
- package/dist/utils/coinselect2/utils.d.ts +64 -0
- package/dist/utils/coinselect2/utils.js +121 -0
- package/dist/utils/paramcoders/IParamReader.d.ts +5 -0
- package/dist/utils/paramcoders/IParamReader.js +2 -0
- package/dist/utils/paramcoders/IParamWriter.d.ts +4 -0
- package/dist/utils/paramcoders/IParamWriter.js +2 -0
- package/dist/utils/paramcoders/LegacyParamEncoder.d.ts +10 -0
- package/dist/utils/paramcoders/LegacyParamEncoder.js +33 -0
- package/dist/utils/paramcoders/ParamDecoder.d.ts +25 -0
- package/dist/utils/paramcoders/ParamDecoder.js +234 -0
- package/dist/utils/paramcoders/ParamEncoder.d.ts +9 -0
- package/dist/utils/paramcoders/ParamEncoder.js +22 -0
- package/dist/utils/paramcoders/SchemaVerifier.d.ts +22 -0
- package/dist/utils/paramcoders/SchemaVerifier.js +85 -0
- package/dist/utils/paramcoders/server/ServerParamDecoder.d.ts +8 -0
- package/dist/utils/paramcoders/server/ServerParamDecoder.js +105 -0
- package/dist/utils/paramcoders/server/ServerParamEncoder.d.ts +11 -0
- package/dist/utils/paramcoders/server/ServerParamEncoder.js +76 -0
- package/package.json +43 -0
- package/src/fees/IBtcFeeEstimator.ts +7 -0
- package/src/fees/OneDollarFeeEstimator.ts +95 -0
- package/src/index.ts +46 -0
- package/src/info/InfoHandler.ts +106 -0
- package/src/plugins/IPlugin.ts +155 -0
- package/src/plugins/PluginManager.ts +310 -0
- package/src/prices/BinanceSwapPrice.ts +114 -0
- package/src/prices/CoinGeckoSwapPrice.ts +88 -0
- package/src/storage/IIntermediaryStorage.ts +21 -0
- package/src/storagemanager/IntermediaryStorageManager.ts +101 -0
- package/src/storagemanager/StorageManager.ts +68 -0
- package/src/swaps/FromBtcBaseSwap.ts +21 -0
- package/src/swaps/FromBtcBaseSwapHandler.ts +375 -0
- package/src/swaps/FromBtcLnBaseSwapHandler.ts +48 -0
- package/src/swaps/ISwapPrice.ts +94 -0
- package/src/swaps/SwapHandler.ts +404 -0
- package/src/swaps/SwapHandlerSwap.ts +133 -0
- package/src/swaps/ToBtcBaseSwap.ts +76 -0
- package/src/swaps/ToBtcBaseSwapHandler.ts +309 -0
- package/src/swaps/frombtc_abstract/FromBtcAbs.ts +484 -0
- package/src/swaps/frombtc_abstract/FromBtcSwapAbs.ts +77 -0
- package/src/swaps/frombtc_trusted/FromBtcTrusted.ts +661 -0
- package/src/swaps/frombtc_trusted/FromBtcTrustedSwap.ts +158 -0
- package/src/swaps/frombtcln_abstract/FromBtcLnAbs.ts +864 -0
- package/src/swaps/frombtcln_abstract/FromBtcLnSwapAbs.ts +82 -0
- package/src/swaps/frombtcln_trusted/FromBtcLnTrusted.ts +592 -0
- package/src/swaps/frombtcln_trusted/FromBtcLnTrustedSwap.ts +90 -0
- package/src/swaps/tobtc_abstract/ToBtcAbs.ts +1249 -0
- package/src/swaps/tobtc_abstract/ToBtcSwapAbs.ts +112 -0
- package/src/swaps/tobtcln_abstract/ToBtcLnAbs.ts +1422 -0
- package/src/swaps/tobtcln_abstract/ToBtcLnSwapAbs.ts +87 -0
- package/src/utils/Utils.ts +108 -0
- package/src/utils/coinselect2/accumulative.js +32 -0
- package/src/utils/coinselect2/accumulative.ts +58 -0
- package/src/utils/coinselect2/blackjack.js +29 -0
- package/src/utils/coinselect2/blackjack.ts +54 -0
- package/src/utils/coinselect2/index.js +16 -0
- package/src/utils/coinselect2/index.ts +50 -0
- package/src/utils/coinselect2/utils.js +110 -0
- package/src/utils/coinselect2/utils.ts +183 -0
- package/src/utils/paramcoders/IParamReader.ts +8 -0
- package/src/utils/paramcoders/IParamWriter.ts +8 -0
- package/src/utils/paramcoders/LegacyParamEncoder.ts +28 -0
- package/src/utils/paramcoders/ParamDecoder.ts +219 -0
- package/src/utils/paramcoders/ParamEncoder.ts +30 -0
- package/src/utils/paramcoders/SchemaVerifier.ts +97 -0
- package/src/utils/paramcoders/server/ServerParamDecoder.ts +115 -0
- package/src/utils/paramcoders/server/ServerParamEncoder.ts +76 -0
|
@@ -0,0 +1,1056 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
3
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
4
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
5
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
6
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
7
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
8
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
9
|
+
});
|
|
10
|
+
};
|
|
11
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
|
+
exports.ToBtcAbs = void 0;
|
|
13
|
+
const BN = require("bn.js");
|
|
14
|
+
const bitcoin = require("bitcoinjs-lib");
|
|
15
|
+
const lncli = require("ln-service");
|
|
16
|
+
const ToBtcSwapAbs_1 = require("./ToBtcSwapAbs");
|
|
17
|
+
const SwapHandler_1 = require("../SwapHandler");
|
|
18
|
+
const base_1 = require("@atomiqlabs/base");
|
|
19
|
+
const Utils_1 = require("../../utils/Utils");
|
|
20
|
+
const PluginManager_1 = require("../../plugins/PluginManager");
|
|
21
|
+
const coinselect2_1 = require("../../utils/coinselect2");
|
|
22
|
+
const utils_1 = require("../../utils/coinselect2/utils");
|
|
23
|
+
const crypto_1 = require("crypto");
|
|
24
|
+
const SchemaVerifier_1 = require("../../utils/paramcoders/SchemaVerifier");
|
|
25
|
+
const ServerParamDecoder_1 = require("../../utils/paramcoders/server/ServerParamDecoder");
|
|
26
|
+
const ToBtcBaseSwapHandler_1 = require("../ToBtcBaseSwapHandler");
|
|
27
|
+
const promise_queue_ts_1 = require("promise-queue-ts");
|
|
28
|
+
const OUTPUT_SCRIPT_MAX_LENGTH = 200;
|
|
29
|
+
/**
|
|
30
|
+
* Handler for to BTC swaps, utilizing PTLCs (proof-time locked contracts) using btc relay (on-chain bitcoin SPV)
|
|
31
|
+
*/
|
|
32
|
+
class ToBtcAbs extends ToBtcBaseSwapHandler_1.ToBtcBaseSwapHandler {
|
|
33
|
+
constructor(storageDirectory, path, chainData, lnd, swapPricing, bitcoinRpc, config) {
|
|
34
|
+
super(storageDirectory, path, chainData, lnd, swapPricing);
|
|
35
|
+
this.CONFIRMATIONS_REQUIRED = 1;
|
|
36
|
+
this.ADDRESS_FORMAT_MAP = {
|
|
37
|
+
"p2wpkh": "p2wpkh",
|
|
38
|
+
"np2wpkh": "p2sh-p2wpkh",
|
|
39
|
+
"p2tr": "p2tr"
|
|
40
|
+
};
|
|
41
|
+
this.LND_CHANGE_OUTPUT_TYPE = "p2tr";
|
|
42
|
+
this.UTXO_CACHE_TIMEOUT = 5 * 1000;
|
|
43
|
+
this.CHANNEL_COUNT_CACHE_TIMEOUT = 30 * 1000;
|
|
44
|
+
this.type = SwapHandler_1.SwapHandlerType.TO_BTC;
|
|
45
|
+
this.activeSubscriptions = {};
|
|
46
|
+
this.sendBtcQueue = new promise_queue_ts_1.PromiseQueue();
|
|
47
|
+
this.bitcoinRpc = bitcoinRpc;
|
|
48
|
+
this.config = config;
|
|
49
|
+
this.config.onchainReservedPerChannel = this.config.onchainReservedPerChannel || 40000;
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Returns the payment hash of the swap, takes swap nonce into account. Payment hash is chain-specific.
|
|
53
|
+
*
|
|
54
|
+
* @param chainIdentifier
|
|
55
|
+
* @param address
|
|
56
|
+
* @param nonce
|
|
57
|
+
* @param amount
|
|
58
|
+
* @param bitcoinNetwork
|
|
59
|
+
*/
|
|
60
|
+
getHash(chainIdentifier, address, nonce, amount, bitcoinNetwork) {
|
|
61
|
+
const parsedOutputScript = bitcoin.address.toOutputScript(address, bitcoinNetwork);
|
|
62
|
+
const { swapContract } = this.getChain(chainIdentifier);
|
|
63
|
+
return swapContract.getHashForOnchain(parsedOutputScript, amount, nonce);
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Returns spendable UTXOs, these are either confirmed UTXOs, or unconfirmed ones that are either whitelisted,
|
|
67
|
+
* or created by our transactions (and therefore only we could doublespend)
|
|
68
|
+
*
|
|
69
|
+
* @private
|
|
70
|
+
*/
|
|
71
|
+
getSpendableUtxos() {
|
|
72
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
73
|
+
const resBlockheight = yield lncli.getHeight({
|
|
74
|
+
lnd: this.LND
|
|
75
|
+
});
|
|
76
|
+
const blockheight = resBlockheight.current_block_height;
|
|
77
|
+
const resChainTxns = yield lncli.getChainTransactions({
|
|
78
|
+
lnd: this.LND,
|
|
79
|
+
after: blockheight - this.CONFIRMATIONS_REQUIRED
|
|
80
|
+
});
|
|
81
|
+
const selfUTXOs = PluginManager_1.PluginManager.getWhitelistedTxIds();
|
|
82
|
+
const transactions = resChainTxns.transactions;
|
|
83
|
+
for (let tx of transactions) {
|
|
84
|
+
if (tx.is_outgoing) {
|
|
85
|
+
selfUTXOs.add(tx.id);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
const resUtxos = yield lncli.getUtxos({
|
|
89
|
+
lnd: this.LND
|
|
90
|
+
});
|
|
91
|
+
return resUtxos.utxos.filter(utxo => utxo.confirmation_count >= this.CONFIRMATIONS_REQUIRED || selfUTXOs.has(utxo.transaction_id));
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* Returns utxo pool to be used by the coinselection algorithm
|
|
96
|
+
*
|
|
97
|
+
* @private
|
|
98
|
+
*/
|
|
99
|
+
getUtxoPool(useCached = false) {
|
|
100
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
101
|
+
if (!useCached || this.cachedUtxos == null || this.cachedUtxos.timestamp < Date.now() - this.UTXO_CACHE_TIMEOUT) {
|
|
102
|
+
const utxos = yield this.getSpendableUtxos();
|
|
103
|
+
let totalSpendable = 0;
|
|
104
|
+
const utxoPool = utxos.map(utxo => {
|
|
105
|
+
totalSpendable += utxo.tokens;
|
|
106
|
+
return {
|
|
107
|
+
vout: utxo.transaction_vout,
|
|
108
|
+
txId: utxo.transaction_id,
|
|
109
|
+
value: utxo.tokens,
|
|
110
|
+
type: this.ADDRESS_FORMAT_MAP[utxo.address_format],
|
|
111
|
+
outputScript: Buffer.from(utxo.output_script, "hex"),
|
|
112
|
+
address: utxo.address,
|
|
113
|
+
confirmations: utxo.confirmation_count
|
|
114
|
+
};
|
|
115
|
+
});
|
|
116
|
+
this.cachedUtxos = {
|
|
117
|
+
utxos: utxoPool,
|
|
118
|
+
timestamp: Date.now()
|
|
119
|
+
};
|
|
120
|
+
this.logger.info("getUtxoPool(): total spendable value: " + totalSpendable + " num utxos: " + utxoPool.length);
|
|
121
|
+
}
|
|
122
|
+
return this.cachedUtxos.utxos;
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* Checks whether a coinselect result leaves enough funds to cover potential lightning anchor transaction fees
|
|
127
|
+
*
|
|
128
|
+
* @param utxoPool
|
|
129
|
+
* @param obj
|
|
130
|
+
* @param satsPerVbyte
|
|
131
|
+
* @param useCached Whether to use a cached channel count
|
|
132
|
+
* @param initialOutputLength
|
|
133
|
+
* @private
|
|
134
|
+
* @returns true if alright, false if the coinselection doesn't leave enough funds for anchor fees
|
|
135
|
+
*/
|
|
136
|
+
isLeavingEnoughForLightningAnchors(utxoPool, obj, satsPerVbyte, useCached = false, initialOutputLength = 1) {
|
|
137
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
138
|
+
if (obj.inputs == null || obj.outputs == null)
|
|
139
|
+
return false;
|
|
140
|
+
const spentInputs = new Set();
|
|
141
|
+
obj.inputs.forEach(txIn => {
|
|
142
|
+
spentInputs.add(txIn.txId + ":" + txIn.vout);
|
|
143
|
+
});
|
|
144
|
+
let leavesValue = new BN(0);
|
|
145
|
+
utxoPool.forEach(val => {
|
|
146
|
+
const utxoEconomicalValue = new BN(val.value).sub(satsPerVbyte.mul(new BN(utils_1.utils.inputBytes(val).length)));
|
|
147
|
+
if (
|
|
148
|
+
//Utxo not spent
|
|
149
|
+
!spentInputs.has(val.txId + ":" + val.vout) &&
|
|
150
|
+
//Only economical utxos at current fees
|
|
151
|
+
!utxoEconomicalValue.isNeg()) {
|
|
152
|
+
leavesValue = leavesValue.add(utxoEconomicalValue);
|
|
153
|
+
}
|
|
154
|
+
});
|
|
155
|
+
if (obj.outputs.length > initialOutputLength) {
|
|
156
|
+
const changeUtxo = obj.outputs[obj.outputs.length - 1];
|
|
157
|
+
leavesValue = leavesValue.add(new BN(changeUtxo.value).sub(satsPerVbyte.mul(new BN(utils_1.utils.inputBytes(changeUtxo).length))));
|
|
158
|
+
}
|
|
159
|
+
if (!useCached || this.cachedChannelCount == null || this.cachedChannelCount.timestamp < Date.now() - this.CHANNEL_COUNT_CACHE_TIMEOUT) {
|
|
160
|
+
const { channels } = yield lncli.getChannels({ lnd: this.LND });
|
|
161
|
+
this.cachedChannelCount = {
|
|
162
|
+
count: channels.length,
|
|
163
|
+
timestamp: Date.now()
|
|
164
|
+
};
|
|
165
|
+
}
|
|
166
|
+
return leavesValue.gt(new BN(this.config.onchainReservedPerChannel).mul(new BN(this.cachedChannelCount.count)));
|
|
167
|
+
});
|
|
168
|
+
}
|
|
169
|
+
/**
|
|
170
|
+
* Gets the change address from the underlying LND instance
|
|
171
|
+
*
|
|
172
|
+
* @private
|
|
173
|
+
*/
|
|
174
|
+
getChangeAddress() {
|
|
175
|
+
return new Promise((resolve, reject) => {
|
|
176
|
+
this.LND.wallet.nextAddr({
|
|
177
|
+
type: 4,
|
|
178
|
+
change: true
|
|
179
|
+
}, (err, res) => {
|
|
180
|
+
if (err != null) {
|
|
181
|
+
reject([503, 'UnexpectedErrGettingNextAddr', { err }]);
|
|
182
|
+
return;
|
|
183
|
+
}
|
|
184
|
+
resolve(res.addr);
|
|
185
|
+
});
|
|
186
|
+
});
|
|
187
|
+
}
|
|
188
|
+
/**
|
|
189
|
+
* Computes bitcoin on-chain network fee, takes channel reserve & network fee multiplier into consideration
|
|
190
|
+
*
|
|
191
|
+
* @param targetAddress Bitcoin address to send the funds to
|
|
192
|
+
* @param targetAmount Amount of funds to send to the address
|
|
193
|
+
* @param estimate Whether the chain fee should be just estimated and therefore cached utxo set could be used
|
|
194
|
+
* @param multiplierPPM Multiplier for the sats/vB returned from the fee estimator in PPM (parts per million)
|
|
195
|
+
* @private
|
|
196
|
+
* @returns Fee estimate & inputs/outputs to use when constructing transaction, or null in case of not enough funds
|
|
197
|
+
*/
|
|
198
|
+
getChainFee(targetAddress, targetAmount, estimate = false, multiplierPPM) {
|
|
199
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
200
|
+
let feeRate = this.config.feeEstimator == null
|
|
201
|
+
? yield lncli.getChainFeeRate({ lnd: this.LND })
|
|
202
|
+
.then(res => res.tokens_per_vbyte)
|
|
203
|
+
.catch(e => this.logger.error("getChainFee(): LND getChainFeeRate error", e))
|
|
204
|
+
: yield this.config.feeEstimator.estimateFee();
|
|
205
|
+
if (feeRate == null)
|
|
206
|
+
return null;
|
|
207
|
+
let satsPerVbyte = new BN(Math.ceil(feeRate));
|
|
208
|
+
if (multiplierPPM != null)
|
|
209
|
+
satsPerVbyte = satsPerVbyte.mul(multiplierPPM).div(new BN(1000000));
|
|
210
|
+
const utxoPool = yield this.getUtxoPool(estimate);
|
|
211
|
+
let obj = (0, coinselect2_1.coinSelect)(utxoPool, [{
|
|
212
|
+
address: targetAddress,
|
|
213
|
+
value: targetAmount,
|
|
214
|
+
script: bitcoin.address.toOutputScript(targetAddress, this.config.bitcoinNetwork)
|
|
215
|
+
}], satsPerVbyte.toNumber(), this.LND_CHANGE_OUTPUT_TYPE);
|
|
216
|
+
if (obj.inputs == null || obj.outputs == null)
|
|
217
|
+
return null;
|
|
218
|
+
if (!(yield this.isLeavingEnoughForLightningAnchors(utxoPool, obj, satsPerVbyte, estimate)))
|
|
219
|
+
return null;
|
|
220
|
+
this.logger.info("getChainFee(): fee estimated," +
|
|
221
|
+
" target: " + targetAddress +
|
|
222
|
+
" amount: " + targetAmount.toString(10) +
|
|
223
|
+
" fee: " + obj.fee +
|
|
224
|
+
" sats/vB: " + satsPerVbyte +
|
|
225
|
+
" inputs: " + obj.inputs.length +
|
|
226
|
+
" outputs: " + obj.outputs.length +
|
|
227
|
+
" multiplier: " + (multiplierPPM == null ? 1 : multiplierPPM.toNumber() / 1000000));
|
|
228
|
+
return {
|
|
229
|
+
networkFee: new BN(obj.fee),
|
|
230
|
+
satsPerVbyte,
|
|
231
|
+
outputs: obj.outputs,
|
|
232
|
+
inputs: obj.inputs
|
|
233
|
+
};
|
|
234
|
+
});
|
|
235
|
+
}
|
|
236
|
+
/**
|
|
237
|
+
* Tries to claim the swap after our transaction was confirmed
|
|
238
|
+
*
|
|
239
|
+
* @param tx
|
|
240
|
+
* @param payment
|
|
241
|
+
* @param vout
|
|
242
|
+
*/
|
|
243
|
+
tryClaimSwap(tx, swap, vout) {
|
|
244
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
245
|
+
const { swapContract, signer } = this.getChain(swap.chainIdentifier);
|
|
246
|
+
const blockHeader = yield this.bitcoinRpc.getBlockHeader(tx.blockhash);
|
|
247
|
+
//Set flag that we are sending the transaction already, so we don't end up with race condition
|
|
248
|
+
const unlock = swap.lock(swapContract.claimWithTxDataTimeout);
|
|
249
|
+
if (unlock == null)
|
|
250
|
+
return false;
|
|
251
|
+
try {
|
|
252
|
+
this.swapLogger.debug(swap, "tryClaimSwap(): initiate claim of swap, height: " + blockHeader.getHeight() + " utxo: " + tx.txid + ":" + vout);
|
|
253
|
+
const result = yield swapContract.claimWithTxData(signer, swap.data, blockHeader.getHeight(), tx, vout, null, null, false, {
|
|
254
|
+
waitForConfirmation: true
|
|
255
|
+
});
|
|
256
|
+
this.swapLogger.info(swap, "tryClaimSwap(): swap claimed successfully, height: " + blockHeader.getHeight() + " utxo: " + tx.txid + ":" + vout + " address: " + swap.address);
|
|
257
|
+
if (swap.metadata != null)
|
|
258
|
+
swap.metadata.times.txClaimed = Date.now();
|
|
259
|
+
unlock();
|
|
260
|
+
return true;
|
|
261
|
+
}
|
|
262
|
+
catch (e) {
|
|
263
|
+
this.swapLogger.error(swap, "tryClaimSwap(): error occurred claiming swap, height: " + blockHeader.getHeight() + " utxo: " + tx.txid + ":" + vout + " address: " + swap.address, e);
|
|
264
|
+
return false;
|
|
265
|
+
}
|
|
266
|
+
});
|
|
267
|
+
}
|
|
268
|
+
processPastSwap(swap) {
|
|
269
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
270
|
+
const { swapContract, signer } = this.getChain(swap.chainIdentifier);
|
|
271
|
+
const timestamp = new BN(Math.floor(Date.now() / 1000)).sub(new BN(this.config.maxSkew));
|
|
272
|
+
if (swap.state === ToBtcSwapAbs_1.ToBtcSwapState.SAVED && swap.signatureExpiry != null) {
|
|
273
|
+
const isSignatureExpired = swap.signatureExpiry.lt(timestamp);
|
|
274
|
+
if (isSignatureExpired) {
|
|
275
|
+
const isCommitted = yield swapContract.isCommited(swap.data);
|
|
276
|
+
if (!isCommitted) {
|
|
277
|
+
this.swapLogger.info(swap, "processPastSwap(state=SAVED): authorization expired & swap not committed, cancelling swap, address: " + swap.address);
|
|
278
|
+
yield this.removeSwapData(swap, ToBtcSwapAbs_1.ToBtcSwapState.CANCELED);
|
|
279
|
+
}
|
|
280
|
+
else {
|
|
281
|
+
this.swapLogger.info(swap, "processPastSwap(state=SAVED): swap committed (detected from processPastSwap), address: " + swap.address);
|
|
282
|
+
yield swap.setState(ToBtcSwapAbs_1.ToBtcSwapState.COMMITED);
|
|
283
|
+
yield this.storageManager.saveData(swap.getHash(), swap.data.getSequence(), swap);
|
|
284
|
+
}
|
|
285
|
+
return;
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
if (swap.state === ToBtcSwapAbs_1.ToBtcSwapState.NON_PAYABLE || swap.state === ToBtcSwapAbs_1.ToBtcSwapState.SAVED) {
|
|
289
|
+
const isSwapExpired = swap.data.getExpiry().lt(timestamp);
|
|
290
|
+
if (isSwapExpired) {
|
|
291
|
+
this.swapLogger.info(swap, "processPastSwap(state=NON_PAYABLE|SAVED): swap expired, cancelling, address: " + swap.address);
|
|
292
|
+
yield this.removeSwapData(swap, ToBtcSwapAbs_1.ToBtcSwapState.CANCELED);
|
|
293
|
+
return;
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
//Sanity check for sent swaps
|
|
297
|
+
if (swap.state === ToBtcSwapAbs_1.ToBtcSwapState.BTC_SENT) {
|
|
298
|
+
const isCommited = yield swapContract.isCommited(swap.data);
|
|
299
|
+
if (!isCommited) {
|
|
300
|
+
const status = yield swapContract.getCommitStatus(signer.getAddress(), swap.data);
|
|
301
|
+
if (status === base_1.SwapCommitStatus.PAID) {
|
|
302
|
+
this.swapLogger.info(swap, "processPastSwap(state=BTC_SENT): swap claimed (detected from processPastSwap), address: " + swap.address);
|
|
303
|
+
this.unsubscribePayment(swap);
|
|
304
|
+
yield this.removeSwapData(swap, ToBtcSwapAbs_1.ToBtcSwapState.CLAIMED);
|
|
305
|
+
}
|
|
306
|
+
else if (status === base_1.SwapCommitStatus.EXPIRED) {
|
|
307
|
+
this.swapLogger.warn(swap, "processPastSwap(state=BTC_SENT): swap expired, but bitcoin was probably already sent, txId: " + swap.txId + " address: " + swap.address);
|
|
308
|
+
this.unsubscribePayment(swap);
|
|
309
|
+
yield this.removeSwapData(swap, ToBtcSwapAbs_1.ToBtcSwapState.REFUNDED);
|
|
310
|
+
}
|
|
311
|
+
return;
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
if (swap.state === ToBtcSwapAbs_1.ToBtcSwapState.COMMITED || swap.state === ToBtcSwapAbs_1.ToBtcSwapState.BTC_SENDING || swap.state === ToBtcSwapAbs_1.ToBtcSwapState.BTC_SENT) {
|
|
315
|
+
yield this.processInitialized(swap);
|
|
316
|
+
return;
|
|
317
|
+
}
|
|
318
|
+
});
|
|
319
|
+
}
|
|
320
|
+
/**
|
|
321
|
+
* Checks past swaps, deletes ones that are already expired.
|
|
322
|
+
*/
|
|
323
|
+
processPastSwaps() {
|
|
324
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
325
|
+
const queriedData = yield this.storageManager.query([
|
|
326
|
+
{
|
|
327
|
+
key: "state",
|
|
328
|
+
values: [
|
|
329
|
+
ToBtcSwapAbs_1.ToBtcSwapState.SAVED,
|
|
330
|
+
ToBtcSwapAbs_1.ToBtcSwapState.NON_PAYABLE,
|
|
331
|
+
ToBtcSwapAbs_1.ToBtcSwapState.COMMITED,
|
|
332
|
+
ToBtcSwapAbs_1.ToBtcSwapState.BTC_SENDING,
|
|
333
|
+
ToBtcSwapAbs_1.ToBtcSwapState.BTC_SENT,
|
|
334
|
+
]
|
|
335
|
+
}
|
|
336
|
+
]);
|
|
337
|
+
for (let swap of queriedData) {
|
|
338
|
+
yield this.processPastSwap(swap);
|
|
339
|
+
}
|
|
340
|
+
});
|
|
341
|
+
}
|
|
342
|
+
processBtcTx(swap, tx) {
|
|
343
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
344
|
+
tx.confirmations = tx.confirmations || 0;
|
|
345
|
+
//Check transaction has enough confirmations
|
|
346
|
+
const hasEnoughConfirmations = tx.confirmations >= swap.data.getConfirmations();
|
|
347
|
+
if (!hasEnoughConfirmations) {
|
|
348
|
+
return false;
|
|
349
|
+
}
|
|
350
|
+
this.swapLogger.debug(swap, "processBtcTx(): address: " + swap.address + " amount: " + swap.amount.toString(10) + " btcTx: " + tx);
|
|
351
|
+
//Search for required transaction output (vout)
|
|
352
|
+
const outputScript = bitcoin.address.toOutputScript(swap.address, this.config.bitcoinNetwork);
|
|
353
|
+
const vout = tx.outs.find(e => new BN(e.value).eq(swap.amount) && Buffer.from(e.scriptPubKey.hex, "hex").equals(outputScript));
|
|
354
|
+
if (vout == null) {
|
|
355
|
+
this.swapLogger.warn(swap, "processBtcTx(): cannot find correct vout," +
|
|
356
|
+
" required output script: " + outputScript.toString("hex") +
|
|
357
|
+
" required amount: " + swap.amount.toString(10) +
|
|
358
|
+
" vouts: ", tx.outs);
|
|
359
|
+
return false;
|
|
360
|
+
}
|
|
361
|
+
if (swap.metadata != null)
|
|
362
|
+
swap.metadata.times.payTxConfirmed = Date.now();
|
|
363
|
+
const success = yield this.tryClaimSwap(tx, swap, vout.n);
|
|
364
|
+
return success;
|
|
365
|
+
});
|
|
366
|
+
}
|
|
367
|
+
/**
|
|
368
|
+
* Checks active sent out bitcoin transactions
|
|
369
|
+
*/
|
|
370
|
+
processBtcTxs() {
|
|
371
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
372
|
+
const unsubscribeSwaps = [];
|
|
373
|
+
for (let txId in this.activeSubscriptions) {
|
|
374
|
+
const swap = this.activeSubscriptions[txId];
|
|
375
|
+
//TODO: RBF the transaction if it's already taking too long to confirm
|
|
376
|
+
try {
|
|
377
|
+
let tx = yield this.bitcoinRpc.getTransaction(txId);
|
|
378
|
+
if (tx == null)
|
|
379
|
+
continue;
|
|
380
|
+
if (yield this.processBtcTx(swap, tx)) {
|
|
381
|
+
this.swapLogger.info(swap, "processBtcTxs(): swap claimed successfully, txId: " + tx.txid + " address: " + swap.address);
|
|
382
|
+
unsubscribeSwaps.push(swap);
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
catch (e) {
|
|
386
|
+
this.swapLogger.error(swap, "processBtcTxs(): error processing btc transaction", e);
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
unsubscribeSwaps.forEach(swap => {
|
|
390
|
+
this.unsubscribePayment(swap);
|
|
391
|
+
});
|
|
392
|
+
});
|
|
393
|
+
}
|
|
394
|
+
/**
|
|
395
|
+
* Subscribes to and periodically checks txId used to send out funds for the swap for enough confirmations
|
|
396
|
+
*
|
|
397
|
+
* @param payment
|
|
398
|
+
*/
|
|
399
|
+
subscribeToPayment(payment) {
|
|
400
|
+
this.swapLogger.info(payment, "subscribeToPayment(): subscribing to swap, txId: " + payment.txId + " address: " + payment.address);
|
|
401
|
+
this.activeSubscriptions[payment.txId] = payment;
|
|
402
|
+
}
|
|
403
|
+
unsubscribePayment(payment) {
|
|
404
|
+
if (payment.txId != null) {
|
|
405
|
+
if (this.activeSubscriptions[payment.txId] != null) {
|
|
406
|
+
this.swapLogger.info(payment, "unsubscribePayment(): unsubscribing swap, txId: " + payment.txId + " address: " + payment.address);
|
|
407
|
+
delete this.activeSubscriptions[payment.txId];
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
/**
|
|
412
|
+
* Checks if expiry time on the swap leaves us enough room to send a transaction and for the transaction to confirm
|
|
413
|
+
*
|
|
414
|
+
* @param swap
|
|
415
|
+
* @private
|
|
416
|
+
* @throws DefinedRuntimeError will throw an error in case there isn't enough time for us to send a BTC payout tx
|
|
417
|
+
*/
|
|
418
|
+
checkExpiresTooSoon(swap) {
|
|
419
|
+
const currentTimestamp = new BN(Math.floor(Date.now() / 1000));
|
|
420
|
+
const tsDelta = swap.data.getExpiry().sub(currentTimestamp);
|
|
421
|
+
const minRequiredCLTV = this.getExpiryFromCLTV(swap.preferedConfirmationTarget, swap.data.getConfirmations());
|
|
422
|
+
const hasRequiredCLTVDelta = tsDelta.gte(minRequiredCLTV);
|
|
423
|
+
if (!hasRequiredCLTVDelta)
|
|
424
|
+
throw {
|
|
425
|
+
code: 90001,
|
|
426
|
+
msg: "TS delta too low",
|
|
427
|
+
data: {
|
|
428
|
+
required: minRequiredCLTV.toString(10),
|
|
429
|
+
actual: tsDelta.toString(10)
|
|
430
|
+
}
|
|
431
|
+
};
|
|
432
|
+
}
|
|
433
|
+
/**
|
|
434
|
+
* Checks if the actual fee for the swap is no higher than the quoted estimate
|
|
435
|
+
*
|
|
436
|
+
* @param quotedSatsPerVbyte
|
|
437
|
+
* @param actualSatsPerVbyte
|
|
438
|
+
* @private
|
|
439
|
+
* @throws DefinedRuntimeError will throw an error in case the actual fee is higher than quoted fee
|
|
440
|
+
*/
|
|
441
|
+
checkCalculatedTxFee(quotedSatsPerVbyte, actualSatsPerVbyte) {
|
|
442
|
+
const swapPaysEnoughNetworkFee = quotedSatsPerVbyte.gte(actualSatsPerVbyte);
|
|
443
|
+
if (!swapPaysEnoughNetworkFee)
|
|
444
|
+
throw {
|
|
445
|
+
code: 90003,
|
|
446
|
+
msg: "Fee changed too much!",
|
|
447
|
+
data: {
|
|
448
|
+
quotedFee: actualSatsPerVbyte.toString(10),
|
|
449
|
+
actualFee: quotedSatsPerVbyte.toString(10)
|
|
450
|
+
}
|
|
451
|
+
};
|
|
452
|
+
}
|
|
453
|
+
/**
|
|
454
|
+
* Runs sanity check on the calculated fee for the transaction
|
|
455
|
+
*
|
|
456
|
+
* @param psbt
|
|
457
|
+
* @param tx
|
|
458
|
+
* @param maxAllowedSatsPerVbyte
|
|
459
|
+
* @param actualSatsPerVbyte
|
|
460
|
+
* @private
|
|
461
|
+
* @throws {Error} Will throw an error if the fee sanity check doesn't pass
|
|
462
|
+
*/
|
|
463
|
+
checkPsbtFee(psbt, tx, maxAllowedSatsPerVbyte, actualSatsPerVbyte) {
|
|
464
|
+
const txFee = new BN(psbt.getFee());
|
|
465
|
+
//Sanity check on sats/vB
|
|
466
|
+
const maxAllowedFee = new BN(tx.virtualSize())
|
|
467
|
+
//Considering the extra output was not added, because was detrminetal
|
|
468
|
+
.add(new BN(utils_1.utils.outputBytes({ type: this.LND_CHANGE_OUTPUT_TYPE })))
|
|
469
|
+
//Multiply by maximum allowed feerate
|
|
470
|
+
.mul(maxAllowedSatsPerVbyte)
|
|
471
|
+
//Possibility that extra output was not added due to it being lower than dust
|
|
472
|
+
.add(new BN(utils_1.utils.dustThreshold({ type: this.LND_CHANGE_OUTPUT_TYPE })));
|
|
473
|
+
if (txFee.gt(maxAllowedFee))
|
|
474
|
+
throw new Error("Generated tx fee too high: " + JSON.stringify({
|
|
475
|
+
maxAllowedFee: maxAllowedFee.toString(10),
|
|
476
|
+
actualFee: txFee.toString(10),
|
|
477
|
+
psbtHex: psbt.toHex(),
|
|
478
|
+
maxAllowedSatsPerVbyte: maxAllowedSatsPerVbyte.toString(10),
|
|
479
|
+
actualSatsPerVbyte: actualSatsPerVbyte.toString(10)
|
|
480
|
+
}));
|
|
481
|
+
return txFee;
|
|
482
|
+
}
|
|
483
|
+
/**
|
|
484
|
+
* Create PSBT for swap payout from coinselection result
|
|
485
|
+
*
|
|
486
|
+
* @param address
|
|
487
|
+
* @param amount
|
|
488
|
+
* @param escrowNonce
|
|
489
|
+
* @param coinselectResult
|
|
490
|
+
* @private
|
|
491
|
+
*/
|
|
492
|
+
getPsbt(address, amount, escrowNonce, coinselectResult) {
|
|
493
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
494
|
+
let psbt = new bitcoin.Psbt();
|
|
495
|
+
//Apply nonce
|
|
496
|
+
const nonceBuffer = Buffer.from(escrowNonce.toArray("be", 8));
|
|
497
|
+
const locktimeBN = new BN(nonceBuffer.slice(0, 5), "be");
|
|
498
|
+
let locktime = locktimeBN.toNumber() + 500000000;
|
|
499
|
+
psbt.setLocktime(locktime);
|
|
500
|
+
const sequenceBN = new BN(nonceBuffer.slice(5, 8), "be");
|
|
501
|
+
const sequence = 0xFE000000 + sequenceBN.toNumber();
|
|
502
|
+
psbt.addInputs(coinselectResult.inputs.map(input => {
|
|
503
|
+
return {
|
|
504
|
+
hash: input.txId,
|
|
505
|
+
index: input.vout,
|
|
506
|
+
witnessUtxo: {
|
|
507
|
+
script: input.outputScript,
|
|
508
|
+
value: input.value
|
|
509
|
+
},
|
|
510
|
+
sighashType: 0x01,
|
|
511
|
+
sequence
|
|
512
|
+
};
|
|
513
|
+
}));
|
|
514
|
+
psbt.addOutput({
|
|
515
|
+
script: bitcoin.address.toOutputScript(address, this.config.bitcoinNetwork),
|
|
516
|
+
value: amount.toNumber()
|
|
517
|
+
});
|
|
518
|
+
//Add change output
|
|
519
|
+
if (coinselectResult.outputs.length > 1)
|
|
520
|
+
psbt.addOutput({
|
|
521
|
+
script: bitcoin.address.toOutputScript(yield this.getChangeAddress(), this.config.bitcoinNetwork),
|
|
522
|
+
value: coinselectResult.outputs[1].value
|
|
523
|
+
});
|
|
524
|
+
return psbt;
|
|
525
|
+
});
|
|
526
|
+
}
|
|
527
|
+
/**
|
|
528
|
+
* Signs provided PSBT and also returns a raw signed transaction
|
|
529
|
+
*
|
|
530
|
+
* @param psbt
|
|
531
|
+
* @private
|
|
532
|
+
*/
|
|
533
|
+
signPsbt(psbt) {
|
|
534
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
535
|
+
const signedPsbt = yield lncli.signPsbt({
|
|
536
|
+
lnd: this.LND,
|
|
537
|
+
psbt: psbt.toHex()
|
|
538
|
+
});
|
|
539
|
+
return {
|
|
540
|
+
psbt: bitcoin.Psbt.fromHex(signedPsbt.psbt),
|
|
541
|
+
rawTx: signedPsbt.transaction
|
|
542
|
+
};
|
|
543
|
+
});
|
|
544
|
+
}
|
|
545
|
+
/**
|
|
546
|
+
* Sends raw bitcoin transaction
|
|
547
|
+
*
|
|
548
|
+
* @param rawTx
|
|
549
|
+
* @private
|
|
550
|
+
*/
|
|
551
|
+
sendRawTransaction(rawTx) {
|
|
552
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
553
|
+
yield lncli.broadcastChainTransaction({
|
|
554
|
+
lnd: this.LND,
|
|
555
|
+
transaction: rawTx
|
|
556
|
+
});
|
|
557
|
+
});
|
|
558
|
+
}
|
|
559
|
+
/**
|
|
560
|
+
* Sends a bitcoin transaction to payout BTC for a swap
|
|
561
|
+
*
|
|
562
|
+
* @param swap
|
|
563
|
+
* @private
|
|
564
|
+
* @throws DefinedRuntimeError will throw an error in case the payment cannot be initiated
|
|
565
|
+
*/
|
|
566
|
+
sendBitcoinPayment(swap) {
|
|
567
|
+
//Make sure that bitcoin payouts are processed sequentially to avoid race conditions between multiple payouts,
|
|
568
|
+
// e.g. that 2 payouts share the same input and would effectively double-spend each other
|
|
569
|
+
return this.sendBtcQueue.enqueue(() => __awaiter(this, void 0, void 0, function* () {
|
|
570
|
+
//Run checks
|
|
571
|
+
this.checkExpiresTooSoon(swap);
|
|
572
|
+
if (swap.metadata != null)
|
|
573
|
+
swap.metadata.times.payCLTVChecked = Date.now();
|
|
574
|
+
const coinselectResult = yield this.getChainFee(swap.address, swap.amount.toNumber());
|
|
575
|
+
if (coinselectResult == null)
|
|
576
|
+
throw {
|
|
577
|
+
code: 90002,
|
|
578
|
+
msg: "Failed to run coinselect algorithm (not enough funds?)"
|
|
579
|
+
};
|
|
580
|
+
if (swap.metadata != null)
|
|
581
|
+
swap.metadata.times.payChainFee = Date.now();
|
|
582
|
+
this.checkCalculatedTxFee(swap.satsPerVbyte, coinselectResult.satsPerVbyte);
|
|
583
|
+
//Construct payment PSBT
|
|
584
|
+
let unsignedPsbt = yield this.getPsbt(swap.address, swap.amount, swap.data.getEscrowNonce(), coinselectResult);
|
|
585
|
+
this.swapLogger.debug(swap, "sendBitcoinPayment(): generated psbt: " + unsignedPsbt.toHex());
|
|
586
|
+
//Sign the PSBT
|
|
587
|
+
const { psbt, rawTx } = yield this.signPsbt(unsignedPsbt);
|
|
588
|
+
if (swap.metadata != null)
|
|
589
|
+
swap.metadata.times.paySignPSBT = Date.now();
|
|
590
|
+
this.swapLogger.debug(swap, "sendBitcoinPayment(): signed raw transaction: " + rawTx);
|
|
591
|
+
const tx = bitcoin.Transaction.fromHex(rawTx);
|
|
592
|
+
const txFee = this.checkPsbtFee(psbt, tx, swap.satsPerVbyte, coinselectResult.satsPerVbyte);
|
|
593
|
+
swap.txId = tx.getId();
|
|
594
|
+
swap.setRealNetworkFee(txFee);
|
|
595
|
+
yield swap.setState(ToBtcSwapAbs_1.ToBtcSwapState.BTC_SENDING);
|
|
596
|
+
yield this.storageManager.saveData(swap.getHash(), swap.getSequence(), swap);
|
|
597
|
+
yield this.sendRawTransaction(rawTx);
|
|
598
|
+
if (swap.metadata != null)
|
|
599
|
+
swap.metadata.times.payTxSent = Date.now();
|
|
600
|
+
this.swapLogger.info(swap, "sendBitcoinPayment(): btc transaction generated, signed & broadcasted, txId: " + tx.getId() + " address: " + swap.address);
|
|
601
|
+
//Invalidate the UTXO cache
|
|
602
|
+
this.cachedUtxos = null;
|
|
603
|
+
yield swap.setState(ToBtcSwapAbs_1.ToBtcSwapState.BTC_SENT);
|
|
604
|
+
yield this.storageManager.saveData(swap.getHash(), swap.getSequence(), swap);
|
|
605
|
+
}));
|
|
606
|
+
}
|
|
607
|
+
/**
|
|
608
|
+
* Called after swap was successfully committed, will check if bitcoin tx is already sent, if not tries to send it and subscribes to it
|
|
609
|
+
*
|
|
610
|
+
* @param swap
|
|
611
|
+
*/
|
|
612
|
+
processInitialized(swap) {
|
|
613
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
614
|
+
if (swap.state === ToBtcSwapAbs_1.ToBtcSwapState.BTC_SENDING) {
|
|
615
|
+
//Bitcoin transaction was signed (maybe also sent)
|
|
616
|
+
const tx = yield this.bitcoinRpc.getTransaction(swap.txId);
|
|
617
|
+
const isTxSent = tx != null;
|
|
618
|
+
if (!isTxSent) {
|
|
619
|
+
//Reset the state to COMMITED
|
|
620
|
+
this.swapLogger.info(swap, "processInitialized(state=BTC_SENDING): btc transaction not found, resetting to COMMITED state, txId: " + swap.txId + " address: " + swap.address);
|
|
621
|
+
yield swap.setState(ToBtcSwapAbs_1.ToBtcSwapState.COMMITED);
|
|
622
|
+
}
|
|
623
|
+
else {
|
|
624
|
+
this.swapLogger.info(swap, "processInitialized(state=BTC_SENDING): btc transaction found, advancing to BTC_SENT state, txId: " + swap.txId + " address: " + swap.address);
|
|
625
|
+
yield swap.setState(ToBtcSwapAbs_1.ToBtcSwapState.BTC_SENT);
|
|
626
|
+
yield this.storageManager.saveData(swap.getHash(), swap.data.getSequence(), swap);
|
|
627
|
+
}
|
|
628
|
+
}
|
|
629
|
+
if (swap.state === ToBtcSwapAbs_1.ToBtcSwapState.SAVED) {
|
|
630
|
+
this.swapLogger.info(swap, "processInitialized(state=SAVED): advancing to COMMITED state, address: " + swap.address);
|
|
631
|
+
yield swap.setState(ToBtcSwapAbs_1.ToBtcSwapState.COMMITED);
|
|
632
|
+
yield this.storageManager.saveData(swap.getHash(), swap.data.getSequence(), swap);
|
|
633
|
+
}
|
|
634
|
+
if (swap.state === ToBtcSwapAbs_1.ToBtcSwapState.COMMITED) {
|
|
635
|
+
const unlock = swap.lock(60);
|
|
636
|
+
if (unlock == null)
|
|
637
|
+
return;
|
|
638
|
+
this.swapLogger.debug(swap, "processInitialized(state=COMMITED): sending bitcoin transaction, address: " + swap.address);
|
|
639
|
+
try {
|
|
640
|
+
yield this.sendBitcoinPayment(swap);
|
|
641
|
+
this.swapLogger.info(swap, "processInitialized(state=COMMITED): btc transaction sent, address: " + swap.address);
|
|
642
|
+
}
|
|
643
|
+
catch (e) {
|
|
644
|
+
if ((0, Utils_1.isDefinedRuntimeError)(e)) {
|
|
645
|
+
this.swapLogger.error(swap, "processInitialized(state=COMMITED): setting state to NON_PAYABLE due to send bitcoin payment error", e);
|
|
646
|
+
if (swap.metadata != null)
|
|
647
|
+
swap.metadata.payError = e;
|
|
648
|
+
yield swap.setState(ToBtcSwapAbs_1.ToBtcSwapState.NON_PAYABLE);
|
|
649
|
+
yield this.storageManager.saveData(swap.getHash(), swap.data.getSequence(), swap);
|
|
650
|
+
}
|
|
651
|
+
else {
|
|
652
|
+
this.swapLogger.error(swap, "processInitialized(state=COMMITED): send bitcoin payment error", e);
|
|
653
|
+
throw e;
|
|
654
|
+
}
|
|
655
|
+
}
|
|
656
|
+
unlock();
|
|
657
|
+
}
|
|
658
|
+
if (swap.state === ToBtcSwapAbs_1.ToBtcSwapState.NON_PAYABLE)
|
|
659
|
+
return;
|
|
660
|
+
this.subscribeToPayment(swap);
|
|
661
|
+
});
|
|
662
|
+
}
|
|
663
|
+
processInitializeEvent(chainIdentifier, event) {
|
|
664
|
+
var _a;
|
|
665
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
666
|
+
if (event.swapType !== base_1.ChainSwapType.CHAIN_NONCED)
|
|
667
|
+
return;
|
|
668
|
+
const paymentHash = event.paymentHash;
|
|
669
|
+
const swap = yield this.storageManager.getData(paymentHash, event.sequence);
|
|
670
|
+
if (swap == null || swap.chainIdentifier !== chainIdentifier)
|
|
671
|
+
return;
|
|
672
|
+
swap.txIds.init = (_a = event.meta) === null || _a === void 0 ? void 0 : _a.txId;
|
|
673
|
+
if (swap.metadata != null)
|
|
674
|
+
swap.metadata.times.txReceived = Date.now();
|
|
675
|
+
this.swapLogger.info(swap, "SC: InitializeEvent: swap initialized by the client, address: " + swap.address);
|
|
676
|
+
yield this.processInitialized(swap);
|
|
677
|
+
});
|
|
678
|
+
}
|
|
679
|
+
processClaimEvent(chainIdentifier, event) {
|
|
680
|
+
var _a;
|
|
681
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
682
|
+
const paymentHash = event.paymentHash;
|
|
683
|
+
const swap = yield this.storageManager.getData(paymentHash, event.sequence);
|
|
684
|
+
if (swap == null || swap.chainIdentifier !== chainIdentifier)
|
|
685
|
+
return;
|
|
686
|
+
swap.txIds.claim = (_a = event.meta) === null || _a === void 0 ? void 0 : _a.txId;
|
|
687
|
+
this.swapLogger.info(swap, "SC: ClaimEvent: swap successfully claimed to us, address: " + swap.address);
|
|
688
|
+
//Also remove transaction from active subscriptions
|
|
689
|
+
this.unsubscribePayment(swap);
|
|
690
|
+
yield this.removeSwapData(swap, ToBtcSwapAbs_1.ToBtcSwapState.CLAIMED);
|
|
691
|
+
});
|
|
692
|
+
}
|
|
693
|
+
processRefundEvent(chainIdentifier, event) {
|
|
694
|
+
var _a;
|
|
695
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
696
|
+
const paymentHash = event.paymentHash;
|
|
697
|
+
const swap = yield this.storageManager.getData(paymentHash, event.sequence);
|
|
698
|
+
if (swap == null || swap.chainIdentifier !== chainIdentifier)
|
|
699
|
+
return;
|
|
700
|
+
swap.txIds.refund = (_a = event.meta) === null || _a === void 0 ? void 0 : _a.txId;
|
|
701
|
+
this.swapLogger.info(swap, "SC: RefundEvent: swap successfully refunded by the user, address: " + swap.address);
|
|
702
|
+
//Also remove transaction from active subscriptions
|
|
703
|
+
this.unsubscribePayment(swap);
|
|
704
|
+
yield this.removeSwapData(swap, ToBtcSwapAbs_1.ToBtcSwapState.REFUNDED);
|
|
705
|
+
});
|
|
706
|
+
}
|
|
707
|
+
/**
|
|
708
|
+
* Returns required expiry delta for swap params
|
|
709
|
+
*
|
|
710
|
+
* @param confirmationTarget
|
|
711
|
+
* @param confirmations
|
|
712
|
+
*/
|
|
713
|
+
getExpiryFromCLTV(confirmationTarget, confirmations) {
|
|
714
|
+
//Blocks = 10 + (confirmations + confirmationTarget)*2
|
|
715
|
+
//Time = 3600 + (600*blocks*2)
|
|
716
|
+
const cltv = this.config.minChainCltv.add(new BN(confirmations).add(new BN(confirmationTarget)).mul(this.config.sendSafetyFactor));
|
|
717
|
+
return this.config.gracePeriod.add(this.config.bitcoinBlocktime.mul(cltv).mul(this.config.safetyFactor));
|
|
718
|
+
}
|
|
719
|
+
/**
|
|
720
|
+
* Checks if the requested nonce is valid
|
|
721
|
+
*
|
|
722
|
+
* @param nonce
|
|
723
|
+
* @throws {DefinedRuntimeError} will throw an error if the nonce is invalid
|
|
724
|
+
*/
|
|
725
|
+
checkNonceValid(nonce) {
|
|
726
|
+
if (nonce.isNeg() || nonce.gte(new BN(2).pow(new BN(64))))
|
|
727
|
+
throw {
|
|
728
|
+
code: 20021,
|
|
729
|
+
msg: "Invalid request body (nonce - cannot be parsed)"
|
|
730
|
+
};
|
|
731
|
+
const nonceBuffer = Buffer.from(nonce.toArray("be", 8));
|
|
732
|
+
const firstPart = new BN(nonceBuffer.slice(0, 5), "be");
|
|
733
|
+
const maxAllowedValue = new BN(Math.floor(Date.now() / 1000) - 600000000);
|
|
734
|
+
if (firstPart.gt(maxAllowedValue))
|
|
735
|
+
throw {
|
|
736
|
+
code: 20022,
|
|
737
|
+
msg: "Invalid request body (nonce - too high)"
|
|
738
|
+
};
|
|
739
|
+
}
|
|
740
|
+
/**
|
|
741
|
+
* Checks if confirmation target is within configured bounds
|
|
742
|
+
*
|
|
743
|
+
* @param confirmationTarget
|
|
744
|
+
* @throws {DefinedRuntimeError} will throw an error if the confirmationTarget is out of bounds
|
|
745
|
+
*/
|
|
746
|
+
checkConfirmationTarget(confirmationTarget) {
|
|
747
|
+
if (confirmationTarget > this.config.maxConfTarget)
|
|
748
|
+
throw {
|
|
749
|
+
code: 20023,
|
|
750
|
+
msg: "Invalid request body (confirmationTarget - too high)"
|
|
751
|
+
};
|
|
752
|
+
if (confirmationTarget < this.config.minConfTarget)
|
|
753
|
+
throw {
|
|
754
|
+
code: 20024,
|
|
755
|
+
msg: "Invalid request body (confirmationTarget - too low)"
|
|
756
|
+
};
|
|
757
|
+
}
|
|
758
|
+
/**
|
|
759
|
+
* Checks if the required confirmations are within configured bounds
|
|
760
|
+
*
|
|
761
|
+
* @param confirmations
|
|
762
|
+
* @throws {DefinedRuntimeError} will throw an error if the confirmations are out of bounds
|
|
763
|
+
*/
|
|
764
|
+
checkRequiredConfirmations(confirmations) {
|
|
765
|
+
if (confirmations > this.config.maxConfirmations)
|
|
766
|
+
throw {
|
|
767
|
+
code: 20025,
|
|
768
|
+
msg: "Invalid request body (confirmations - too high)"
|
|
769
|
+
};
|
|
770
|
+
if (confirmations < this.config.minConfirmations)
|
|
771
|
+
throw {
|
|
772
|
+
code: 20026,
|
|
773
|
+
msg: "Invalid request body (confirmations - too low)"
|
|
774
|
+
};
|
|
775
|
+
}
|
|
776
|
+
/**
|
|
777
|
+
* Checks the validity of the provided address, also checks if the resulting output script isn't too large
|
|
778
|
+
*
|
|
779
|
+
* @param address
|
|
780
|
+
* @throws {DefinedRuntimeError} will throw an error if the address is invalid
|
|
781
|
+
*/
|
|
782
|
+
checkAddress(address) {
|
|
783
|
+
let parsedOutputScript;
|
|
784
|
+
try {
|
|
785
|
+
parsedOutputScript = bitcoin.address.toOutputScript(address, this.config.bitcoinNetwork);
|
|
786
|
+
}
|
|
787
|
+
catch (e) {
|
|
788
|
+
throw {
|
|
789
|
+
code: 20031,
|
|
790
|
+
msg: "Invalid request body (address - cannot be parsed)"
|
|
791
|
+
};
|
|
792
|
+
}
|
|
793
|
+
if (parsedOutputScript.length > OUTPUT_SCRIPT_MAX_LENGTH)
|
|
794
|
+
throw {
|
|
795
|
+
code: 20032,
|
|
796
|
+
msg: "Invalid request body (address's output script - too long)"
|
|
797
|
+
};
|
|
798
|
+
}
|
|
799
|
+
/**
|
|
800
|
+
* Checks if the swap is expired, taking into consideration on-chain time skew
|
|
801
|
+
*
|
|
802
|
+
* @param swap
|
|
803
|
+
* @throws {DefinedRuntimeError} will throw an error if the swap is expired
|
|
804
|
+
*/
|
|
805
|
+
checkExpired(swap) {
|
|
806
|
+
const isExpired = swap.data.getExpiry().lt(new BN(Math.floor(Date.now() / 1000)).sub(new BN(this.config.maxSkew)));
|
|
807
|
+
if (isExpired)
|
|
808
|
+
throw {
|
|
809
|
+
_httpStatus: 200,
|
|
810
|
+
code: 20010,
|
|
811
|
+
msg: "Payment expired"
|
|
812
|
+
};
|
|
813
|
+
}
|
|
814
|
+
/**
|
|
815
|
+
* Checks & returns the network fee needed for a transaction
|
|
816
|
+
*
|
|
817
|
+
* @param address
|
|
818
|
+
* @param amount
|
|
819
|
+
* @throws {DefinedRuntimeError} will throw an error if there are not enough BTC funds
|
|
820
|
+
*/
|
|
821
|
+
checkAndGetNetworkFee(address, amount) {
|
|
822
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
823
|
+
let chainFeeResp = yield this.getChainFee(address, amount.toNumber(), true, this.config.networkFeeMultiplierPPM);
|
|
824
|
+
const hasEnoughFunds = chainFeeResp != null;
|
|
825
|
+
if (!hasEnoughFunds)
|
|
826
|
+
throw {
|
|
827
|
+
code: 20002,
|
|
828
|
+
msg: "Not enough liquidity"
|
|
829
|
+
};
|
|
830
|
+
return chainFeeResp;
|
|
831
|
+
});
|
|
832
|
+
}
|
|
833
|
+
startRestServer(restServer) {
|
|
834
|
+
restServer.use(this.path + "/payInvoice", (0, ServerParamDecoder_1.serverParamDecoder)(10 * 1000));
|
|
835
|
+
restServer.post(this.path + "/payInvoice", (0, Utils_1.expressHandlerWrapper)((req, res) => __awaiter(this, void 0, void 0, function* () {
|
|
836
|
+
var _a;
|
|
837
|
+
const metadata = { request: {}, times: {} };
|
|
838
|
+
const chainIdentifier = (_a = req.query.chain) !== null && _a !== void 0 ? _a : this.chains.default;
|
|
839
|
+
const { swapContract, signer } = this.getChain(chainIdentifier);
|
|
840
|
+
metadata.times.requestReceived = Date.now();
|
|
841
|
+
/**
|
|
842
|
+
*Sent initially:
|
|
843
|
+
* address: string Bitcoin destination address
|
|
844
|
+
* amount: string Amount to send (in satoshis)
|
|
845
|
+
* confirmationTarget: number Desired confirmation target for the swap, how big of a fee should be assigned to TX
|
|
846
|
+
* confirmations: number Required number of confirmations for us to claim the swap
|
|
847
|
+
* nonce: string Nonce for the swap (used for replay protection)
|
|
848
|
+
* token: string Desired token to use
|
|
849
|
+
* offerer: string Address of the caller
|
|
850
|
+
* exactIn: boolean Whether the swap should be an exact in instead of exact out swap
|
|
851
|
+
*
|
|
852
|
+
*Sent later:
|
|
853
|
+
* feeRate: string Fee rate to use for the init signature
|
|
854
|
+
*/
|
|
855
|
+
const parsedBody = yield req.paramReader.getParams({
|
|
856
|
+
address: SchemaVerifier_1.FieldTypeEnum.String,
|
|
857
|
+
amount: SchemaVerifier_1.FieldTypeEnum.BN,
|
|
858
|
+
confirmationTarget: SchemaVerifier_1.FieldTypeEnum.Number,
|
|
859
|
+
confirmations: SchemaVerifier_1.FieldTypeEnum.Number,
|
|
860
|
+
nonce: SchemaVerifier_1.FieldTypeEnum.BN,
|
|
861
|
+
token: (val) => val != null &&
|
|
862
|
+
typeof (val) === "string" &&
|
|
863
|
+
this.isTokenSupported(chainIdentifier, val) ? val : null,
|
|
864
|
+
offerer: (val) => val != null &&
|
|
865
|
+
typeof (val) === "string" &&
|
|
866
|
+
swapContract.isValidAddress(val) ? val : null,
|
|
867
|
+
exactIn: SchemaVerifier_1.FieldTypeEnum.BooleanOptional
|
|
868
|
+
});
|
|
869
|
+
if (parsedBody == null)
|
|
870
|
+
throw {
|
|
871
|
+
code: 20100,
|
|
872
|
+
msg: "Invalid request body"
|
|
873
|
+
};
|
|
874
|
+
metadata.request = parsedBody;
|
|
875
|
+
const requestedAmount = { input: !!parsedBody.exactIn, amount: parsedBody.amount };
|
|
876
|
+
const request = {
|
|
877
|
+
chainIdentifier,
|
|
878
|
+
raw: req,
|
|
879
|
+
parsed: parsedBody,
|
|
880
|
+
metadata
|
|
881
|
+
};
|
|
882
|
+
const useToken = parsedBody.token;
|
|
883
|
+
const responseStream = res.responseStream;
|
|
884
|
+
this.checkNonceValid(parsedBody.nonce);
|
|
885
|
+
this.checkConfirmationTarget(parsedBody.confirmationTarget);
|
|
886
|
+
this.checkRequiredConfirmations(parsedBody.confirmations);
|
|
887
|
+
this.checkAddress(parsedBody.address);
|
|
888
|
+
yield this.checkVaultInitialized(chainIdentifier, parsedBody.token);
|
|
889
|
+
const fees = yield this.preCheckAmounts(request, requestedAmount, useToken);
|
|
890
|
+
metadata.times.requestChecked = Date.now();
|
|
891
|
+
//Initialize abort controller for the parallel async operations
|
|
892
|
+
const abortController = this.getAbortController(responseStream);
|
|
893
|
+
const { pricePrefetchPromise, signDataPrefetchPromise } = this.getToBtcPrefetches(chainIdentifier, useToken, responseStream, abortController);
|
|
894
|
+
const { amountBD, networkFeeData, totalInToken, swapFee, swapFeeInToken, networkFeeInToken } = yield this.checkToBtcAmount(request, requestedAmount, fees, useToken, (amount) => __awaiter(this, void 0, void 0, function* () {
|
|
895
|
+
metadata.times.amountsChecked = Date.now();
|
|
896
|
+
const resp = yield this.checkAndGetNetworkFee(parsedBody.address, amount);
|
|
897
|
+
metadata.times.chainFeeCalculated = Date.now();
|
|
898
|
+
return resp;
|
|
899
|
+
}), abortController.signal, pricePrefetchPromise);
|
|
900
|
+
metadata.times.priceCalculated = Date.now();
|
|
901
|
+
const paymentHash = this.getHash(chainIdentifier, parsedBody.address, parsedBody.nonce, amountBD, this.config.bitcoinNetwork).toString("hex");
|
|
902
|
+
//Add grace period another time, so the user has 1 hour to commit
|
|
903
|
+
const expirySeconds = this.getExpiryFromCLTV(parsedBody.confirmationTarget, parsedBody.confirmations).add(new BN(this.config.gracePeriod));
|
|
904
|
+
const currentTimestamp = new BN(Math.floor(Date.now() / 1000));
|
|
905
|
+
const minRequiredExpiry = currentTimestamp.add(expirySeconds);
|
|
906
|
+
const sequence = new BN((0, crypto_1.randomBytes)(8));
|
|
907
|
+
const payObject = yield swapContract.createSwapData(base_1.ChainSwapType.CHAIN_NONCED, parsedBody.offerer, signer.getAddress(), useToken, totalInToken, paymentHash, sequence, minRequiredExpiry, parsedBody.nonce, parsedBody.confirmations, true, false, new BN(0), new BN(0));
|
|
908
|
+
abortController.signal.throwIfAborted();
|
|
909
|
+
metadata.times.swapCreated = Date.now();
|
|
910
|
+
const sigData = yield this.getToBtcSignatureData(chainIdentifier, payObject, req, abortController.signal, signDataPrefetchPromise);
|
|
911
|
+
metadata.times.swapSigned = Date.now();
|
|
912
|
+
const createdSwap = new ToBtcSwapAbs_1.ToBtcSwapAbs(chainIdentifier, parsedBody.address, amountBD, swapFee, swapFeeInToken, networkFeeData.networkFee, networkFeeInToken, networkFeeData.satsPerVbyte, parsedBody.nonce, parsedBody.confirmationTarget, new BN(sigData.timeout));
|
|
913
|
+
createdSwap.data = payObject;
|
|
914
|
+
createdSwap.metadata = metadata;
|
|
915
|
+
yield PluginManager_1.PluginManager.swapCreate(createdSwap);
|
|
916
|
+
yield this.storageManager.saveData(paymentHash, sequence, createdSwap);
|
|
917
|
+
this.swapLogger.info(createdSwap, "REST: /payInvoice: created swap address: " + createdSwap.address + " amount: " + amountBD.toString(10));
|
|
918
|
+
yield responseStream.writeParamsAndEnd({
|
|
919
|
+
code: 20000,
|
|
920
|
+
msg: "Success",
|
|
921
|
+
data: {
|
|
922
|
+
amount: amountBD.toString(10),
|
|
923
|
+
address: signer.getAddress(),
|
|
924
|
+
satsPervByte: networkFeeData.satsPerVbyte.toString(10),
|
|
925
|
+
networkFee: networkFeeInToken.toString(10),
|
|
926
|
+
swapFee: swapFeeInToken.toString(10),
|
|
927
|
+
totalFee: swapFeeInToken.add(networkFeeInToken).toString(10),
|
|
928
|
+
total: totalInToken.toString(10),
|
|
929
|
+
minRequiredExpiry: minRequiredExpiry.toString(10),
|
|
930
|
+
data: payObject.serialize(),
|
|
931
|
+
prefix: sigData.prefix,
|
|
932
|
+
timeout: sigData.timeout,
|
|
933
|
+
signature: sigData.signature
|
|
934
|
+
}
|
|
935
|
+
});
|
|
936
|
+
})));
|
|
937
|
+
const getRefundAuthorization = (0, Utils_1.expressHandlerWrapper)((req, res) => __awaiter(this, void 0, void 0, function* () {
|
|
938
|
+
/**
|
|
939
|
+
* paymentHash: string Payment hash identifier of the swap
|
|
940
|
+
* sequence: BN Sequence identifier of the swap
|
|
941
|
+
*/
|
|
942
|
+
const parsedBody = (0, SchemaVerifier_1.verifySchema)(Object.assign(Object.assign({}, req.body), req.query), {
|
|
943
|
+
paymentHash: (val) => val != null &&
|
|
944
|
+
typeof (val) === "string" &&
|
|
945
|
+
val.length === 64 &&
|
|
946
|
+
Utils_1.HEX_REGEX.test(val) ? val : null,
|
|
947
|
+
sequence: SchemaVerifier_1.FieldTypeEnum.BN
|
|
948
|
+
});
|
|
949
|
+
if (parsedBody == null)
|
|
950
|
+
throw {
|
|
951
|
+
code: 20100,
|
|
952
|
+
msg: "Invalid request body/query (paymentHash/sequence)"
|
|
953
|
+
};
|
|
954
|
+
this.checkSequence(parsedBody.sequence);
|
|
955
|
+
const payment = yield this.storageManager.getData(parsedBody.paymentHash, parsedBody.sequence);
|
|
956
|
+
if (payment == null || payment.state === ToBtcSwapAbs_1.ToBtcSwapState.SAVED)
|
|
957
|
+
throw {
|
|
958
|
+
_httpStatus: 200,
|
|
959
|
+
code: 20007,
|
|
960
|
+
msg: "Payment not found"
|
|
961
|
+
};
|
|
962
|
+
const { swapContract, signer } = this.getChain(payment.chainIdentifier);
|
|
963
|
+
this.checkExpired(payment);
|
|
964
|
+
if (payment.state === ToBtcSwapAbs_1.ToBtcSwapState.COMMITED)
|
|
965
|
+
throw {
|
|
966
|
+
_httpStatus: 200,
|
|
967
|
+
code: 20008,
|
|
968
|
+
msg: "Payment processing"
|
|
969
|
+
};
|
|
970
|
+
if (payment.state === ToBtcSwapAbs_1.ToBtcSwapState.BTC_SENT || payment.state === ToBtcSwapAbs_1.ToBtcSwapState.BTC_SENDING)
|
|
971
|
+
throw {
|
|
972
|
+
_httpStatus: 200,
|
|
973
|
+
code: 20006,
|
|
974
|
+
msg: "Already paid",
|
|
975
|
+
data: {
|
|
976
|
+
txId: payment.txId
|
|
977
|
+
}
|
|
978
|
+
};
|
|
979
|
+
if (payment.state === ToBtcSwapAbs_1.ToBtcSwapState.NON_PAYABLE) {
|
|
980
|
+
const isCommited = yield swapContract.isCommited(payment.data);
|
|
981
|
+
if (!isCommited)
|
|
982
|
+
throw {
|
|
983
|
+
code: 20005,
|
|
984
|
+
msg: "Not committed"
|
|
985
|
+
};
|
|
986
|
+
const refundResponse = yield swapContract.getRefundSignature(signer, payment.data, this.config.authorizationTimeout);
|
|
987
|
+
//Double check the state after promise result
|
|
988
|
+
if (payment.state !== ToBtcSwapAbs_1.ToBtcSwapState.NON_PAYABLE)
|
|
989
|
+
throw {
|
|
990
|
+
code: 20005,
|
|
991
|
+
msg: "Not committed"
|
|
992
|
+
};
|
|
993
|
+
this.swapLogger.info(payment, "REST: /getRefundAuthorization: returning refund authorization, because swap is in NON_PAYABLE state, address: " + payment.address);
|
|
994
|
+
res.status(200).json({
|
|
995
|
+
code: 20000,
|
|
996
|
+
msg: "Success",
|
|
997
|
+
data: {
|
|
998
|
+
address: signer.getAddress(),
|
|
999
|
+
prefix: refundResponse.prefix,
|
|
1000
|
+
timeout: refundResponse.timeout,
|
|
1001
|
+
signature: refundResponse.signature
|
|
1002
|
+
}
|
|
1003
|
+
});
|
|
1004
|
+
return;
|
|
1005
|
+
}
|
|
1006
|
+
throw {
|
|
1007
|
+
_httpStatus: 500,
|
|
1008
|
+
code: 20009,
|
|
1009
|
+
msg: "Invalid payment status"
|
|
1010
|
+
};
|
|
1011
|
+
}));
|
|
1012
|
+
restServer.post(this.path + "/getRefundAuthorization", getRefundAuthorization);
|
|
1013
|
+
restServer.get(this.path + "/getRefundAuthorization", getRefundAuthorization);
|
|
1014
|
+
this.logger.info("started at path: ", this.path);
|
|
1015
|
+
}
|
|
1016
|
+
/**
|
|
1017
|
+
* Starts watchdog checking sent bitcoin transactions
|
|
1018
|
+
*/
|
|
1019
|
+
startTxTimer() {
|
|
1020
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
1021
|
+
let rerun;
|
|
1022
|
+
rerun = () => __awaiter(this, void 0, void 0, function* () {
|
|
1023
|
+
yield this.processBtcTxs().catch(e => this.logger.error("startTxTimer(): call to processBtcTxs() errored", e));
|
|
1024
|
+
setTimeout(rerun, this.config.txCheckInterval);
|
|
1025
|
+
});
|
|
1026
|
+
yield rerun();
|
|
1027
|
+
});
|
|
1028
|
+
}
|
|
1029
|
+
startWatchdog() {
|
|
1030
|
+
const _super = Object.create(null, {
|
|
1031
|
+
startWatchdog: { get: () => super.startWatchdog }
|
|
1032
|
+
});
|
|
1033
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
1034
|
+
yield _super.startWatchdog.call(this);
|
|
1035
|
+
yield this.startTxTimer();
|
|
1036
|
+
});
|
|
1037
|
+
}
|
|
1038
|
+
init() {
|
|
1039
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
1040
|
+
yield this.storageManager.loadData(ToBtcSwapAbs_1.ToBtcSwapAbs);
|
|
1041
|
+
this.subscribeToEvents();
|
|
1042
|
+
yield PluginManager_1.PluginManager.serviceInitialize(this);
|
|
1043
|
+
});
|
|
1044
|
+
}
|
|
1045
|
+
getInfoData() {
|
|
1046
|
+
return {
|
|
1047
|
+
minCltv: this.config.minChainCltv.toNumber(),
|
|
1048
|
+
minConfirmations: this.config.minConfirmations,
|
|
1049
|
+
maxConfirmations: this.config.maxConfirmations,
|
|
1050
|
+
minConfTarget: this.config.minConfTarget,
|
|
1051
|
+
maxConfTarget: this.config.maxConfTarget,
|
|
1052
|
+
maxOutputScriptLen: OUTPUT_SCRIPT_MAX_LENGTH
|
|
1053
|
+
};
|
|
1054
|
+
}
|
|
1055
|
+
}
|
|
1056
|
+
exports.ToBtcAbs = ToBtcAbs;
|