@xchainjs/xchain-thorchain-query 0.1.0-alpha

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/lib/index.js ADDED
@@ -0,0 +1,1735 @@
1
+ 'use strict';
2
+
3
+ Object.defineProperty(exports, '__esModule', { value: true });
4
+
5
+ var xchainThornode = require('@xchainjs/xchain-thornode');
6
+ var xchainUtil = require('@xchainjs/xchain-util');
7
+ var BigNumber = require('bignumber.js');
8
+ var xchainClient = require('@xchainjs/xchain-client');
9
+ var xchainMidgard = require('@xchainjs/xchain-midgard');
10
+ var axios = require('axios');
11
+ var axiosRetry = require('axios-retry');
12
+
13
+ function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; }
14
+
15
+ var BigNumber__default = /*#__PURE__*/_interopDefaultLegacy(BigNumber);
16
+ var axios__default = /*#__PURE__*/_interopDefaultLegacy(axios);
17
+ var axiosRetry__default = /*#__PURE__*/_interopDefaultLegacy(axiosRetry);
18
+
19
+ /*! *****************************************************************************
20
+ Copyright (c) Microsoft Corporation.
21
+
22
+ Permission to use, copy, modify, and/or distribute this software for any
23
+ purpose with or without fee is hereby granted.
24
+
25
+ THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
26
+ REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
27
+ AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
28
+ INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
29
+ LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
30
+ OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
31
+ PERFORMANCE OF THIS SOFTWARE.
32
+ ***************************************************************************** */
33
+
34
+ function __awaiter(thisArg, _arguments, P, generator) {
35
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
36
+ return new (P || (P = Promise))(function (resolve, reject) {
37
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
38
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
39
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
40
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
41
+ });
42
+ }
43
+
44
+ const DefaultChainAttributes = {
45
+ BCH: {
46
+ blockReward: 6.25,
47
+ avgBlockTimeInSecs: 600,
48
+ },
49
+ BTC: {
50
+ blockReward: 6.25,
51
+ avgBlockTimeInSecs: 600,
52
+ },
53
+ ETH: {
54
+ blockReward: 2,
55
+ avgBlockTimeInSecs: 13,
56
+ },
57
+ AVAX: {
58
+ blockReward: 2,
59
+ avgBlockTimeInSecs: 3,
60
+ },
61
+ LTC: {
62
+ blockReward: 12.5,
63
+ avgBlockTimeInSecs: 150,
64
+ },
65
+ DOGE: {
66
+ blockReward: 10000,
67
+ avgBlockTimeInSecs: 60,
68
+ },
69
+ GAIA: {
70
+ blockReward: 0,
71
+ avgBlockTimeInSecs: 6,
72
+ },
73
+ TERRA: {
74
+ blockReward: 0,
75
+ avgBlockTimeInSecs: 0,
76
+ },
77
+ BNB: {
78
+ blockReward: 0,
79
+ avgBlockTimeInSecs: 6,
80
+ },
81
+ THOR: {
82
+ blockReward: 0,
83
+ avgBlockTimeInSecs: 6,
84
+ },
85
+ };
86
+
87
+ /**
88
+ * Utility Class to combine an amount (asset/base) with the Asset
89
+ *
90
+ */
91
+ class CryptoAmount {
92
+ constructor(amount, asset) {
93
+ this.asset = asset;
94
+ this.baseAmount = amount;
95
+ }
96
+ plus(v) {
97
+ this.check(v);
98
+ const baseAmountResult = this.baseAmount.plus(v.baseAmount);
99
+ return new CryptoAmount(baseAmountResult, this.asset);
100
+ }
101
+ minus(v) {
102
+ this.check(v);
103
+ const baseAmountResult = this.baseAmount.minus(v.baseAmount);
104
+ return new CryptoAmount(baseAmountResult, this.asset);
105
+ }
106
+ times(v) {
107
+ this.check(v);
108
+ if (v instanceof CryptoAmount) {
109
+ const baseAmountResult = this.baseAmount.times(v.baseAmount);
110
+ return new CryptoAmount(baseAmountResult, this.asset);
111
+ }
112
+ else {
113
+ const baseAmountResult = this.baseAmount.times(v);
114
+ return new CryptoAmount(baseAmountResult, this.asset);
115
+ }
116
+ }
117
+ div(v) {
118
+ this.check(v);
119
+ if (v instanceof CryptoAmount) {
120
+ const baseAmountResult = this.baseAmount.div(v.baseAmount);
121
+ return new CryptoAmount(baseAmountResult, this.asset);
122
+ }
123
+ else {
124
+ const baseAmountResult = this.baseAmount.div(v);
125
+ return new CryptoAmount(baseAmountResult, this.asset);
126
+ }
127
+ }
128
+ lt(v) {
129
+ this.check(v);
130
+ return this.baseAmount.lt(v.baseAmount);
131
+ }
132
+ lte(v) {
133
+ this.check(v);
134
+ return this.baseAmount.lte(v.baseAmount);
135
+ }
136
+ gt(v) {
137
+ this.check(v);
138
+ return this.baseAmount.gt(v.baseAmount);
139
+ }
140
+ gte(v) {
141
+ this.check(v);
142
+ return this.baseAmount.gte(v.baseAmount);
143
+ }
144
+ eq(v) {
145
+ this.check(v);
146
+ return this.baseAmount.eq(v.baseAmount);
147
+ }
148
+ formatedAssetString() {
149
+ return xchainUtil.formatAssetAmountCurrency({
150
+ amount: this.assetAmount,
151
+ asset: this.asset,
152
+ trimZeros: true,
153
+ });
154
+ }
155
+ assetAmountFixedString() {
156
+ return this.assetAmount.amount().toFixed();
157
+ }
158
+ get assetAmount() {
159
+ return xchainUtil.baseToAsset(this.baseAmount);
160
+ }
161
+ /**
162
+ * This guard protects against trying to perform math with different assets
163
+ *
164
+ * Example.
165
+ * const x = new CryptoAmount(baseAmount(1),AssetBTC)
166
+ * const y = new CryptoAmount(baseAmount(1),AssetETH)
167
+ *
168
+ * x.plus(y) <- will throw error "cannot perform math on 2 diff assets BTC.BTC ETH.ETH
169
+ *
170
+ * @param v - CryptoNumeric
171
+ */
172
+ check(v) {
173
+ if (v instanceof CryptoAmount) {
174
+ if (!xchainUtil.eqAsset(this.asset, v.asset)) {
175
+ throw Error(`cannot perform math on 2 diff assets ${xchainUtil.assetToString(this.asset)} ${xchainUtil.assetToString(v.asset)}`);
176
+ }
177
+ }
178
+ }
179
+ }
180
+
181
+ (function (TxStage) {
182
+ TxStage[TxStage["INBOUND_CHAIN_UNCONFIRMED"] = 0] = "INBOUND_CHAIN_UNCONFIRMED";
183
+ TxStage[TxStage["CONF_COUNTING"] = 1] = "CONF_COUNTING";
184
+ TxStage[TxStage["TC_PROCESSING"] = 2] = "TC_PROCESSING";
185
+ TxStage[TxStage["OUTBOUND_QUEUED"] = 3] = "OUTBOUND_QUEUED";
186
+ TxStage[TxStage["OUTBOUND_CHAIN_UNCONFIRMED"] = 4] = "OUTBOUND_CHAIN_UNCONFIRMED";
187
+ TxStage[TxStage["OUTBOUND_CHAIN_CONFIRMED"] = 5] = "OUTBOUND_CHAIN_CONFIRMED";
188
+ })(exports.TxStage || (exports.TxStage = {}));
189
+
190
+ /**
191
+ *
192
+ * @param inputAmount - amount to swap
193
+ * @param pool - Pool Data, RUNE and ASSET Depths
194
+ * @param toRune - Direction of Swap. True if swapping to RUNE.
195
+ * @returns
196
+ */
197
+ const getSwapFee = (inputAmount, pool, toRune) => {
198
+ // formula: (x * x * Y) / (x + X) ^ 2
199
+ // const isInputRune = isAssetRuneNative(inputAmount.asset)
200
+ const x = inputAmount.baseAmount.amount();
201
+ const X = toRune ? pool.assetBalance.amount() : pool.runeBalance.amount(); // input is asset if toRune
202
+ const Y = toRune ? pool.runeBalance.amount() : pool.assetBalance.amount(); // output is rune if toRune
203
+ const units = toRune ? xchainUtil.AssetRuneNative : pool.asset;
204
+ const decimals = toRune || !pool.decimals ? 8 : pool.decimals;
205
+ const numerator = x.times(x).multipliedBy(Y);
206
+ const denominator = x.plus(X).pow(2);
207
+ const result = numerator.div(denominator);
208
+ const swapFee = new CryptoAmount(xchainUtil.baseAmount(result, decimals), units);
209
+ // console.log(` swapFee ${swapFee.assetAmountFixedString()} ${assetToString(units)}`)
210
+ return swapFee;
211
+ };
212
+ /**
213
+ * Works out the swap slip for a given swap.
214
+ *
215
+ * @param inputAmount - amount to swap
216
+ * @param pool - Pool Data, RUNE and ASSET Depths
217
+ * @param toRune - Direction of Swap. True if swapping to RUNE.
218
+ * @returns The amount of slip. Needs to * 100 to get percentage.
219
+ */
220
+ const getSwapSlip = (inputAmount, pool, toRune) => {
221
+ // formula: (x) / (x + X)
222
+ const x = inputAmount.baseAmount.amount();
223
+ const X = toRune ? pool.assetBalance.amount() : pool.runeBalance.amount(); // input is asset if toRune
224
+ const result = x.div(x.plus(X));
225
+ return new BigNumber.BigNumber(result);
226
+ };
227
+ /**
228
+ *
229
+ * @param inputAmount - amount to swap
230
+ * @param pool - Pool Data, RUNE and ASSET Depths
231
+ * @param toRune - Direction of Swap. True if swapping to RUNE.
232
+ * @returns The output amount
233
+ */
234
+ const getSwapOutput = (inputAmount, pool, toRune) => {
235
+ // formula: (x * X * Y) / (x + X) ^ 2
236
+ const x = inputAmount.baseAmount.amount();
237
+ const X = toRune ? pool.assetBalance.amount() : pool.runeBalance.amount(); // input is asset if toRune
238
+ const Y = toRune ? pool.runeBalance.amount() : pool.assetBalance.amount(); // output is rune if toRune
239
+ const units = toRune ? xchainUtil.AssetRuneNative : pool.asset;
240
+ const decimals = toRune || !pool.decimals ? 8 : pool.decimals;
241
+ const numerator = x.times(X).times(Y);
242
+ const denominator = x.plus(X).pow(2);
243
+ const result = numerator.div(denominator);
244
+ return new CryptoAmount(xchainUtil.baseAmount(result, decimals), units);
245
+ };
246
+ const getDoubleSwapOutput = (inputAmount, pool1, pool2) => {
247
+ // formula: getSwapOutput(pool1) => getSwapOutput(pool2)
248
+ const r = getSwapOutput(inputAmount, pool1, true);
249
+ const output = getSwapOutput(r, pool2, false);
250
+ return output;
251
+ };
252
+ /**
253
+ *
254
+ * @param inputAmount - amount to swap
255
+ * @param pool - Pool Data, RUNE and ASSET Depths
256
+ * @returns swap output object - output - fee - slip
257
+ */
258
+ const getSingleSwap = (inputAmount, pool, toRune) => {
259
+ const output = getSwapOutput(inputAmount, pool, toRune);
260
+ const fee = getSwapFee(inputAmount, pool, toRune);
261
+ const slip = getSwapSlip(inputAmount, pool, toRune);
262
+ const swapOutput = {
263
+ output: output,
264
+ swapFee: fee,
265
+ slip: slip,
266
+ };
267
+ return swapOutput;
268
+ };
269
+ const getDoubleSwapSlip = (inputAmount, pool1, pool2) => {
270
+ // formula: getSwapSlip1(input1) + getSwapSlip2(getSwapOutput1 => input2)
271
+ const swapOutput1 = getSingleSwap(inputAmount, pool1, true);
272
+ const swapOutput2 = getSingleSwap(swapOutput1.output, pool2, false);
273
+ const result = swapOutput2.slip.plus(swapOutput1.slip);
274
+ return result;
275
+ };
276
+ const getDoubleSwapFee = (inputAmount, pool1, pool2, thorchainCache) => __awaiter(void 0, void 0, void 0, function* () {
277
+ // formula: getSwapFee1 + getSwapFee2
278
+ const fee1InRune = getSwapFee(inputAmount, pool1, true);
279
+ const swapOutput = getSwapOutput(inputAmount, pool1, true);
280
+ const fee2InAsset = getSwapFee(swapOutput, pool2, false);
281
+ const fee2InRune = yield thorchainCache.convert(fee2InAsset, xchainUtil.AssetRuneNative);
282
+ const result = fee1InRune.plus(fee2InRune);
283
+ return result;
284
+ });
285
+ /**
286
+ *
287
+ * @param inputAmount - amount to swap
288
+ * @param pool - Pool Data, RUNE and ASSET Depths
289
+ * @param toRune - Direction of Swap. True if swapping to RUNE.
290
+ * @returns swap output object - output - fee - slip
291
+ */
292
+ const getDoubleSwap = (inputAmount, pool1, pool2, thorchainCache) => __awaiter(void 0, void 0, void 0, function* () {
293
+ const doubleOutput = getDoubleSwapOutput(inputAmount, pool1, pool2);
294
+ const doubleFee = yield getDoubleSwapFee(inputAmount, pool1, pool2, thorchainCache);
295
+ const doubleSlip = getDoubleSwapSlip(inputAmount, pool1, pool2);
296
+ const SwapOutput = {
297
+ output: doubleOutput,
298
+ swapFee: doubleFee,
299
+ slip: doubleSlip,
300
+ };
301
+ return SwapOutput;
302
+ });
303
+ /**
304
+ * Works out the required inbound or outbound fee based on the chain.
305
+ * Call getInboundDetails to get the current gasRate
306
+ *
307
+ * @param sourceAsset
308
+ * @param gasRate
309
+ * @see https://dev.thorchain.org/thorchain-dev/thorchain-and-fees#fee-calcuation-by-chain
310
+ * @returns
311
+ */
312
+ const calcNetworkFee = (asset, gasRate) => {
313
+ if (asset.synth)
314
+ return new CryptoAmount(xchainUtil.baseAmount(2000000), xchainUtil.AssetRuneNative);
315
+ switch (asset.chain) {
316
+ case xchainUtil.Chain.Bitcoin:
317
+ return new CryptoAmount(xchainUtil.baseAmount(gasRate.multipliedBy(1000)), xchainUtil.AssetBTC);
318
+ case xchainUtil.Chain.BitcoinCash:
319
+ return new CryptoAmount(xchainUtil.baseAmount(gasRate.multipliedBy(1500)), xchainUtil.AssetBCH);
320
+ case xchainUtil.Chain.Litecoin:
321
+ return new CryptoAmount(xchainUtil.baseAmount(gasRate.multipliedBy(250)), xchainUtil.AssetLTC);
322
+ case xchainUtil.Chain.Doge:
323
+ // NOTE: UTXO chains estimate fees with a 250 byte size
324
+ return new CryptoAmount(xchainUtil.baseAmount(gasRate.multipliedBy(1000)), xchainUtil.AssetDOGE);
325
+ case xchainUtil.Chain.Binance:
326
+ //flat fee
327
+ return new CryptoAmount(xchainUtil.baseAmount(gasRate), xchainUtil.AssetBNB);
328
+ case xchainUtil.Chain.Ethereum:
329
+ const gasRateinETHGwei = gasRate;
330
+ const gasRateinETHWei = xchainUtil.baseAmount(gasRateinETHGwei.multipliedBy(Math.pow(10, 9)), 18);
331
+ if (xchainUtil.eqAsset(asset, xchainUtil.AssetETH)) {
332
+ return new CryptoAmount(gasRateinETHWei.times(21000), xchainUtil.AssetETH);
333
+ }
334
+ else {
335
+ return new CryptoAmount(gasRateinETHWei.times(70000), xchainUtil.AssetETH);
336
+ }
337
+ case xchainUtil.Chain.Avalanche:
338
+ const gasRateinAVAXGwei = gasRate;
339
+ const gasRateinAVAXWei = xchainUtil.baseAmount(gasRateinAVAXGwei.multipliedBy(Math.pow(10, 9)), 18);
340
+ if (xchainUtil.eqAsset(asset, xchainUtil.AssetAVAX)) {
341
+ return new CryptoAmount(gasRateinAVAXWei.times(21000), xchainUtil.AssetETH);
342
+ }
343
+ else {
344
+ return new CryptoAmount(gasRateinAVAXWei.times(70000), xchainUtil.AssetETH);
345
+ }
346
+ case xchainUtil.Chain.Terra:
347
+ return new CryptoAmount(xchainUtil.baseAmount(gasRate), xchainUtil.AssetLUNA);
348
+ case xchainUtil.Chain.Cosmos:
349
+ return new CryptoAmount(xchainUtil.baseAmount(gasRate), xchainUtil.AssetAtom);
350
+ case xchainUtil.Chain.THORChain:
351
+ return new CryptoAmount(xchainUtil.baseAmount(2000000), xchainUtil.AssetRuneNative);
352
+ }
353
+ throw new Error(`could not calculate inbound fee for ${asset.chain}`);
354
+ };
355
+ /**
356
+ * Return the chain for a given Asset This method should live somewhere else.
357
+ * @param chain
358
+ * @returns the gas asset type for the given chain
359
+ */
360
+ const getChainAsset = (chain) => {
361
+ switch (chain) {
362
+ case xchainUtil.BNBChain:
363
+ return xchainUtil.AssetBNB;
364
+ case xchainUtil.BTCChain:
365
+ return xchainUtil.AssetBTC;
366
+ case xchainUtil.ETHChain:
367
+ return xchainUtil.AssetETH;
368
+ case xchainUtil.THORChain:
369
+ return xchainUtil.AssetRuneNative;
370
+ case xchainUtil.CosmosChain:
371
+ return xchainUtil.AssetAtom;
372
+ case xchainUtil.BCHChain:
373
+ return xchainUtil.AssetBCH;
374
+ case xchainUtil.LTCChain:
375
+ return xchainUtil.AssetLTC;
376
+ case xchainUtil.DOGEChain:
377
+ return xchainUtil.AssetDOGE;
378
+ case xchainUtil.TerraChain:
379
+ return xchainUtil.AssetLUNA;
380
+ case xchainUtil.AvalancheChain:
381
+ return xchainUtil.AssetAVAX;
382
+ default:
383
+ throw Error('Unknown chain');
384
+ }
385
+ };
386
+ /**
387
+ *
388
+ * @param chain - input chain string
389
+ * @returns - returns correct chain from string
390
+ */
391
+ const getChain = (chain) => {
392
+ switch (chain) {
393
+ case 'BNB':
394
+ return xchainUtil.BNBChain;
395
+ case 'BTC':
396
+ return xchainUtil.BTCChain;
397
+ case 'ETH':
398
+ return xchainUtil.ETHChain;
399
+ case 'THOR':
400
+ return xchainUtil.THORChain;
401
+ case 'GAIA':
402
+ return xchainUtil.CosmosChain;
403
+ case 'BCH':
404
+ return xchainUtil.BCHChain;
405
+ case 'LTC':
406
+ return xchainUtil.LTCChain;
407
+ case 'DOGE':
408
+ return xchainUtil.DOGEChain;
409
+ case 'TERRA':
410
+ return xchainUtil.TerraChain;
411
+ default:
412
+ throw Error('Unknown chain');
413
+ }
414
+ };
415
+
416
+ // import { Network } from '@xchainjs/xchain-client'
417
+ const BN_1 = new BigNumber.BigNumber(1);
418
+ /**
419
+ * THORChain Class for interacting with THORChain.
420
+ * Recommended main class to use for swapping with THORChain
421
+ * Has access to Midgard and THORNode data
422
+ */
423
+ class ThorchainQuery {
424
+ /**
425
+ * Contructor to create a ThorchainAMM
426
+ *
427
+ * @param thorchainCache - an instance of the ThorchainCache (could be pointing to stagenet,testnet,mainnet)
428
+ * @param chainAttributes - atrributes used to calculate waitTime & conf counting
429
+ * @returns ThorchainAMM
430
+ */
431
+ constructor(thorchainCache, chainAttributes = DefaultChainAttributes) {
432
+ this.thorchainCache = thorchainCache;
433
+ this.chainAttributes = chainAttributes;
434
+ }
435
+ /**
436
+ * Provides a swap estimate for the given swap detail. Will check the params for errors before trying to get the estimate.
437
+ * Uses current pool data, works out inbound and outboud fee, affiliate fees and works out the expected wait time for the swap (in and out)
438
+ *
439
+ * @param params - amount to swap
440
+
441
+ * @returns The SwapEstimate
442
+ */
443
+ estimateSwap(params, destinationAddress = '', affiliateAddress = '', interfaceID = 999) {
444
+ return __awaiter(this, void 0, void 0, function* () {
445
+ this.isValidSwap(params);
446
+ const inboundDetails = yield this.thorchainCache.getInboundDetails();
447
+ const sourceInboundDetails = inboundDetails[params.input.asset.chain];
448
+ // console.log(JSON.stringify(sourceInboundDetails, null, 2))
449
+ const destinationInboundDetails = inboundDetails[params.destinationAsset.chain];
450
+ // console.log(JSON.stringify(destinationInboundDetails, null, 2))
451
+ const swapEstimate = yield this.calcSwapEstimate(params, sourceInboundDetails, destinationInboundDetails);
452
+ // Remove any affiliateFee. netInput * affiliateFee (%age) of the destination asset type
453
+ const affiliateFee = params.input.baseAmount.times(params.affiliateFeePercent || 0);
454
+ // Calculate expiry time
455
+ const currentDatetime = new Date();
456
+ const minutesToAdd = 15;
457
+ const expiryDatetime = new Date(currentDatetime.getTime() + minutesToAdd * 60000);
458
+ // Check for errors
459
+ const errors = yield this.getSwapEstimateErrors(params, swapEstimate, sourceInboundDetails, destinationInboundDetails);
460
+ const txDetails = {
461
+ memo: '',
462
+ toAddress: '',
463
+ expiry: expiryDatetime,
464
+ txEstimate: swapEstimate,
465
+ };
466
+ if (errors.length > 0) {
467
+ txDetails.txEstimate.canSwap = false;
468
+ txDetails.txEstimate.errors = errors;
469
+ }
470
+ else {
471
+ txDetails.txEstimate.canSwap = true;
472
+ // Retrieve inbound Asgard address.
473
+ const inboundAsgard = (yield this.thorchainCache.getInboundAddressesItems())[params.input.asset.chain];
474
+ txDetails.toAddress = (inboundAsgard === null || inboundAsgard === void 0 ? void 0 : inboundAsgard.address) || '';
475
+ // Work out LIM from the slip percentage
476
+ let limPercentage = BN_1;
477
+ if (params.slipLimit) {
478
+ limPercentage = BN_1.minus(params.slipLimit || 1);
479
+ } // else allowed slip is 100%
480
+ const limAssetAmount = swapEstimate.netOutput.times(limPercentage);
481
+ const inboundDelay = yield this.confCounting(params.input);
482
+ const outboundDelay = yield this.outboundDelay(limAssetAmount);
483
+ txDetails.txEstimate.waitTimeSeconds = outboundDelay + inboundDelay;
484
+ // Construct memo
485
+ txDetails.memo = this.constructSwapMemo({
486
+ input: params.input,
487
+ destinationAsset: params.destinationAsset,
488
+ limit: limAssetAmount.baseAmount,
489
+ destinationAddress,
490
+ affiliateAddress,
491
+ affiliateFee,
492
+ interfaceID,
493
+ });
494
+ }
495
+ return txDetails;
496
+ });
497
+ }
498
+ /**
499
+ * Basic Checks for swap information
500
+ * @param params
501
+ */
502
+ isValidSwap(params) {
503
+ // TODO validate all input fields
504
+ if (xchainUtil.eqAsset(params.input.asset, params.destinationAsset))
505
+ throw Error(`sourceAsset and destinationAsset cannot be the same`);
506
+ if (params.input.baseAmount.lte(0))
507
+ throw Error('inputAmount must be greater than 0');
508
+ if (params.affiliateFeePercent && (params.affiliateFeePercent < 0 || params.affiliateFeePercent > 0.1))
509
+ throw Error(`affiliateFee must be between 0 and 1000`);
510
+ }
511
+ /**
512
+ * Does the calculations for the swap.
513
+ * Used by estimateSwap
514
+ *
515
+ * @param params
516
+ * @param sourceInboundDetails
517
+ * @param destinationInboundDetails
518
+ * @param sourcePool
519
+ * @param destinationPool
520
+ * @returns
521
+ */
522
+ calcSwapEstimate(params, sourceInboundDetails, destinationInboundDetails) {
523
+ return __awaiter(this, void 0, void 0, function* () {
524
+ //NOTE need to convert the asset to 8 decimals places for all calcs
525
+ const input = yield this.thorchainCache.convert(params.input, params.input.asset);
526
+ const inputInRune = yield this.thorchainCache.convert(input, xchainUtil.AssetRuneNative);
527
+ const inboundFeeInAsset = calcNetworkFee(input.asset, sourceInboundDetails.gas_rate);
528
+ let outboundFeeInAsset = calcNetworkFee(params.destinationAsset, destinationInboundDetails.gas_rate);
529
+ outboundFeeInAsset = outboundFeeInAsset.times(3);
530
+ const inboundFeeInRune = yield this.thorchainCache.convert(inboundFeeInAsset, xchainUtil.AssetRuneNative);
531
+ let outboundFeeInRune = yield this.thorchainCache.convert(outboundFeeInAsset, xchainUtil.AssetRuneNative);
532
+ // ---------- Remove Fees from inbound before doing the swap -----------
533
+ // TODO confirm with chris about this change
534
+ // const inputMinusInboundFeeInRune = inputInRune.minus(inboundFeeInRune)
535
+ const inputMinusInboundFeeInRune = inputInRune;
536
+ // remove any affiliateFee. netInput * affiliateFee (%age) of the destination asset type
537
+ const affiliateFeeInRune = inputMinusInboundFeeInRune.times(params.affiliateFeePercent || 0);
538
+ // remove the affiliate fee from the input.
539
+ const inputNetAmountInRune = inputMinusInboundFeeInRune.minus(affiliateFeeInRune);
540
+ // convert back to input asset
541
+ const inputNetInAsset = yield this.thorchainCache.convert(inputNetAmountInRune, input.asset);
542
+ // Check outbound fee is equal too or greater than 1 USD * need to find a more permanent solution to this. referencing just 1 stable coin pool has problems
543
+ if (params.destinationAsset.chain !== xchainUtil.Chain.THORChain && !params.destinationAsset.synth) {
544
+ const deepestUSDPOOL = yield this.thorchainCache.getDeepestUSDPool();
545
+ const usdAsset = deepestUSDPOOL.asset;
546
+ const networkValues = yield this.thorchainCache.midgard.getNetworkValues();
547
+ const usdMinFee = new CryptoAmount(xchainUtil.baseAmount(networkValues['MINIMUML1OUTBOUNDFEEUSD']), usdAsset);
548
+ // const FeeInUSD = await this.convert(outboundFeeInRune, usdAsset)
549
+ const checkOutboundFee = (yield this.convert(outboundFeeInRune, usdAsset)).gte(usdMinFee);
550
+ if (!checkOutboundFee) {
551
+ const newFee = usdMinFee;
552
+ outboundFeeInRune = yield this.convert(newFee, xchainUtil.AssetRuneNative);
553
+ }
554
+ }
555
+ // Now calculate swapfee based on inputNetAmount
556
+ const swapOutput = yield this.thorchainCache.getExpectedSwapOutput(inputNetInAsset, params.destinationAsset);
557
+ const swapFeeInRune = yield this.thorchainCache.convert(swapOutput.swapFee, xchainUtil.AssetRuneNative);
558
+ const outputInRune = yield this.thorchainCache.convert(swapOutput.output, xchainUtil.AssetRuneNative);
559
+ // ---------------- Remove Outbound Fee ---------------------- /
560
+ const netOutputInRune = outputInRune.minus(outboundFeeInRune);
561
+ const netOutputInAsset = yield this.thorchainCache.convert(netOutputInRune, params.destinationAsset);
562
+ const totalFees = {
563
+ inboundFee: inboundFeeInRune,
564
+ swapFee: swapFeeInRune,
565
+ outboundFee: outboundFeeInRune,
566
+ affiliateFee: affiliateFeeInRune,
567
+ };
568
+ const swapEstimate = {
569
+ totalFees: totalFees,
570
+ slipPercentage: swapOutput.slip,
571
+ netOutput: netOutputInAsset,
572
+ waitTimeSeconds: 0,
573
+ canSwap: false,
574
+ errors: [],
575
+ };
576
+ return swapEstimate;
577
+ });
578
+ }
579
+ /**
580
+ *
581
+ * @param params - swap object
582
+ * @returns - constructed memo string
583
+ */
584
+ constructSwapMemo(params) {
585
+ const limstring = params.limit.amount().toFixed();
586
+ // create LIM with interface ID
587
+ const lim = limstring.substring(0, limstring.length - 3).concat(params.interfaceID.toString());
588
+ // create the full memo
589
+ let memo = `=:${xchainUtil.assetToString(params.destinationAsset)}`;
590
+ if (params.affiliateAddress != '' || params.affiliateFee == undefined) {
591
+ memo = memo.concat(`:${params.destinationAddress}:${lim}:${params.affiliateAddress}:${params.affiliateFee.amount().toFixed()}`);
592
+ }
593
+ else {
594
+ memo = memo.concat(`:${params.destinationAddress}:${lim}`);
595
+ }
596
+ // If memo length is too long for BTC, trim it
597
+ if (xchainUtil.eqAsset(params.input.asset, xchainUtil.AssetBTC) && memo.length > 80) {
598
+ memo = `=:${xchainUtil.assetToString(params.destinationAsset)}:${params.destinationAddress}`;
599
+ }
600
+ return memo;
601
+ }
602
+ /**
603
+ * Looks for errors or issues within swap prams before doing the swap.
604
+ *
605
+ *
606
+ * @param params
607
+ * @param estimate
608
+ * @param sourcePool
609
+ * @param sourceInboundDetails
610
+ * @param destinationPool
611
+ * @param destinationInboundDetails
612
+ * @returns
613
+ */
614
+ getSwapEstimateErrors(params, estimate, sourceInboundDetails, destinationInboundDetails) {
615
+ var _a;
616
+ return __awaiter(this, void 0, void 0, function* () {
617
+ const errors = [];
618
+ const sourceAsset = params.input.asset;
619
+ const destAsset = params.destinationAsset;
620
+ if (!xchainUtil.isAssetRuneNative(sourceAsset)) {
621
+ const sourcePool = yield this.thorchainCache.getPoolForAsset(sourceAsset);
622
+ if (!sourcePool.isAvailable())
623
+ errors.push(`sourceAsset ${sourceAsset.ticker} does not have a valid liquidity pool`);
624
+ }
625
+ if (!xchainUtil.isAssetRuneNative(destAsset)) {
626
+ const destPool = yield this.thorchainCache.getPoolForAsset(destAsset);
627
+ if (!destPool.isAvailable())
628
+ errors.push(`destinationAsset ${destAsset.ticker} does not have a valid liquidity pool`);
629
+ }
630
+ if (sourceInboundDetails.haltedChain)
631
+ errors.push(`source chain is halted`);
632
+ if (sourceInboundDetails.haltedTrading)
633
+ errors.push(`source pool is halted trading`);
634
+ if (destinationInboundDetails.haltedChain)
635
+ errors.push(`destination chain is halted`);
636
+ if (destinationInboundDetails.haltedTrading)
637
+ errors.push(`destination pool is halted trading`);
638
+ if (estimate.slipPercentage.gte(params.slipLimit || 1))
639
+ errors.push(`expected slip: ${estimate.slipPercentage.toFixed()} is greater than your slip limit:${(_a = params.slipLimit) === null || _a === void 0 ? void 0 : _a.toFixed()} `);
640
+ // only proceed to check fees if there are no errors so far
641
+ if (errors.length > 0)
642
+ return errors;
643
+ // Check if the inputAmount value is enough to cover all the fees.
644
+ const canCoverFeesError = yield this.checkCoverFees(params, estimate);
645
+ if (canCoverFeesError)
646
+ errors.push(canCoverFeesError);
647
+ return errors;
648
+ });
649
+ }
650
+ /**
651
+ *
652
+ * @param params
653
+ * @param estimate
654
+ * @returns
655
+ */
656
+ checkCoverFees(params, estimate) {
657
+ return __awaiter(this, void 0, void 0, function* () {
658
+ let result = undefined;
659
+ const inputInRune = yield this.thorchainCache.convert(params.input, xchainUtil.AssetRuneNative);
660
+ const feesInRune = yield this.getFeesIn(estimate.totalFees, xchainUtil.AssetRuneNative);
661
+ const totalSwapFeesInRune = feesInRune.inboundFee
662
+ .plus(feesInRune.outboundFee)
663
+ .plus(feesInRune.swapFee)
664
+ .plus(feesInRune.affiliateFee);
665
+ const totalSwapFeesInAsset = yield this.thorchainCache.convert(totalSwapFeesInRune, params.input.asset);
666
+ if (totalSwapFeesInRune.gte(inputInRune))
667
+ result = `Input amount ${params.input.formatedAssetString()}(${inputInRune.formatedAssetString()}) is less than or equal to total swap fees ${totalSwapFeesInAsset.formatedAssetString()}(${totalSwapFeesInRune.formatedAssetString()}) `;
668
+ return result;
669
+ });
670
+ }
671
+ /**
672
+ * Convenience method to convert TotalFees to a different CryptoAmount
673
+ *
674
+ * TotalFees are always calculated and returned in RUNE, this method can
675
+ * be used to show the equivalent fees in another Asset Type
676
+ *
677
+ * @param fees: TotalFees - the fees you want to convert
678
+ * @param asset: Asset - the asset you want the fees converted to
679
+ * @returns TotalFees in asset
680
+ */
681
+ getFeesIn(fees, asset) {
682
+ return __awaiter(this, void 0, void 0, function* () {
683
+ return {
684
+ inboundFee: yield this.convert(fees.inboundFee, asset),
685
+ swapFee: yield this.convert(fees.swapFee, asset),
686
+ outboundFee: yield this.convert(fees.outboundFee, asset),
687
+ affiliateFee: yield this.convert(fees.affiliateFee, asset),
688
+ };
689
+ });
690
+ }
691
+ /**
692
+ * Returns the exchange of a CryptoAmount to a different Asset
693
+ *
694
+ * Ex. convert(input:100 BUSD, outAsset: BTC) -> 0.0001234 BTC
695
+ *
696
+ * @param input - amount/asset to convert to outAsset
697
+ * @param ouAsset - the Asset you want to convert to
698
+ * @returns CryptoAmount of input
699
+ */
700
+ convert(input, outAsset) {
701
+ return __awaiter(this, void 0, void 0, function* () {
702
+ return yield this.thorchainCache.convert(input, outAsset);
703
+ });
704
+ }
705
+ /**
706
+ * Finds the required confCount required for an inbound or outbound Tx to THORChain. Estimate based on Midgard data only.
707
+ *
708
+ * Finds the gas asset of the given asset (e.g. BUSD is on BNB), finds the value of asset in Gas Asset then finds the required confirmation count.
709
+ * ConfCount is then times by 6 seconds.
710
+ *
711
+ * @param inbound: CryptoAmount - amount/asset of the outbound amount.
712
+ * @returns time in seconds before a Tx is confirmed by THORChain
713
+ * @see https://docs.thorchain.org/chain-clients/overview
714
+ */
715
+ confCounting(inbound) {
716
+ return __awaiter(this, void 0, void 0, function* () {
717
+ // RUNE, BNB and Synths have near instant finality, so no conf counting required. - need to make a BFT only case.
718
+ if (xchainUtil.isAssetRuneNative(inbound.asset) ||
719
+ inbound.asset.chain == xchainUtil.AssetBNB.chain ||
720
+ inbound.asset.chain == xchainUtil.AssetAtom.chain ||
721
+ inbound.asset.synth) {
722
+ return this.chainAttributes[xchainUtil.Chain.THORChain].avgBlockTimeInSecs;
723
+ }
724
+ // Get the gas asset for the inbound.asset.chain
725
+ const chainGasAsset = getChainAsset(inbound.asset.chain);
726
+ // check for chain asset, else need to convert asset value to chain asset.
727
+ const amountInGasAsset = yield this.thorchainCache.convert(inbound, chainGasAsset);
728
+ // Convert to Asset Amount
729
+ const amountInGasAssetInAsset = amountInGasAsset.assetAmount;
730
+ const confConfig = this.chainAttributes[inbound.asset.chain];
731
+ // find the required confs
732
+ const requiredConfs = Math.ceil(amountInGasAssetInAsset.amount().div(confConfig.blockReward).toNumber());
733
+ // convert that into seconds
734
+ return requiredConfs * confConfig.avgBlockTimeInSecs;
735
+ });
736
+ }
737
+ /**
738
+ * Works out how long an outbound Tx will be held by THORChain before sending.
739
+ *
740
+ * @param outboundAmount: CryptoAmount being sent.
741
+ * @returns required delay in seconds
742
+ * @see https://gitlab.com/thorchain/thornode/-/blob/develop/x/thorchain/manager_txout_current.go#L548
743
+ */
744
+ outboundDelay(outboundAmount) {
745
+ return __awaiter(this, void 0, void 0, function* () {
746
+ const networkValues = yield this.thorchainCache.getNetworkValues();
747
+ const minTxOutVolumeThreshold = new CryptoAmount(xchainUtil.baseAmount(networkValues['MINTXOUTVOLUMETHRESHOLD']), xchainUtil.AssetRuneNative);
748
+ const maxTxOutOffset = networkValues['MAXTXOUTOFFSET'];
749
+ let txOutDelayRate = new CryptoAmount(xchainUtil.baseAmount(networkValues['TXOUTDELAYRATE']), xchainUtil.AssetRuneNative);
750
+ const getScheduledOutboundValue = yield this.thorchainCache.midgard.getScheduledOutboundValue();
751
+ const thorChainblocktime = this.chainAttributes[xchainUtil.Chain.THORChain].avgBlockTimeInSecs; // blocks required to confirm tx
752
+ // If asset is equal to Rune set runeValue as outbound amount else set it to the asset's value in rune
753
+ const runeValue = yield this.thorchainCache.convert(outboundAmount, xchainUtil.AssetRuneNative);
754
+ // Check rune value amount
755
+ if (runeValue.lt(minTxOutVolumeThreshold)) {
756
+ return thorChainblocktime;
757
+ }
758
+ // Rune value in the outbound queue
759
+ if (getScheduledOutboundValue == undefined) {
760
+ throw new Error(`Could not return Scheduled Outbound Value`);
761
+ }
762
+ // Add OutboundAmount in rune to the oubound queue
763
+ const outboundAmountTotal = runeValue.plus(getScheduledOutboundValue);
764
+ // calculate the if outboundAmountTotal is over the volume threshold
765
+ const volumeThreshold = outboundAmountTotal.div(minTxOutVolumeThreshold);
766
+ // check delay rate
767
+ txOutDelayRate = txOutDelayRate.minus(volumeThreshold).baseAmount.amount().lt(1)
768
+ ? new CryptoAmount(xchainUtil.baseAmount(1), xchainUtil.AssetRuneNative)
769
+ : txOutDelayRate;
770
+ // calculate the minimum number of blocks in the future the txn has to be
771
+ let minBlocks = runeValue.div(txOutDelayRate).baseAmount.amount().toNumber();
772
+ minBlocks = minBlocks > maxTxOutOffset ? maxTxOutOffset : minBlocks;
773
+ return minBlocks * thorChainblocktime;
774
+ });
775
+ }
776
+ /**
777
+ * For a given in Tx Hash (as returned by THORChainAMM.DoSwap()), finds the status of any THORChain transaction
778
+ * This function should be polled.
779
+ * @param
780
+ * @param inboundTxHash - needed to determine transactions stage
781
+ * @param sourceChain - extra parameter
782
+ * @returns - object tx status
783
+ */
784
+ checkTx(inboundTxHash, sourceChain) {
785
+ var _a, _b, _c, _d;
786
+ return __awaiter(this, void 0, void 0, function* () {
787
+ let txStatus = { stage: exports.TxStage.INBOUND_CHAIN_UNCONFIRMED, seconds: 0 };
788
+ const txData = yield this.thorchainCache.thornode.getTxData(inboundTxHash);
789
+ const scheduledQueueItem = (yield this.thorchainCache.thornode.getscheduledQueue()).find((item) => item.in_hash === inboundTxHash);
790
+ //console.log(`Tx stage ${txStatus.stage}\nTx seconds left ${txStatus.seconds}`)
791
+ // Check to see if the transaction has been observed
792
+ if (txData.observed_tx == undefined) {
793
+ txStatus = yield this.checkTxDefined(txStatus, sourceChain);
794
+ return txStatus;
795
+ }
796
+ // If its scheduled and observed
797
+ if (scheduledQueueItem && txData.observed_tx) {
798
+ txStatus = yield this.checkObservedOnly(txStatus, scheduledQueueItem, txData.observed_tx, sourceChain);
799
+ }
800
+ //console.log(`Tx stage ${txStatus.stage}\nTx seconds left ${txStatus.seconds}`)
801
+ // Retrieve asset and chain from memo
802
+ const pool = (_a = txData.observed_tx.tx.memo) === null || _a === void 0 ? void 0 : _a.split(`:`);
803
+ if (!pool)
804
+ throw Error(`No pool found from memo`);
805
+ const getAsset = xchainUtil.assetFromString(pool[1].toUpperCase());
806
+ // Retrieve thorchain blockHeight for the tx
807
+ if (!((_b = txData.observed_tx.tx) === null || _b === void 0 ? void 0 : _b.id))
808
+ throw Error('No action observed');
809
+ const recordedAction = yield this.thorchainCache.midgard.getActions(txData.observed_tx.tx.id);
810
+ const recordedTCBlock = recordedAction.find((block) => {
811
+ return block;
812
+ });
813
+ if (!(recordedTCBlock === null || recordedTCBlock === void 0 ? void 0 : recordedTCBlock.height))
814
+ throw Error('No recorded block height');
815
+ // Retrieve thorchains last observed block height
816
+ const lastBlock = yield this.thorchainCache.thornode.getLastBlock();
817
+ const lastBlockHeight = lastBlock.find((obj) => obj.chain === (getAsset === null || getAsset === void 0 ? void 0 : getAsset.chain));
818
+ // Check to see if its in the outbound queue
819
+ if (scheduledQueueItem) {
820
+ txStatus = yield this.checkOutboundQueue(txStatus, scheduledQueueItem, lastBlockHeight);
821
+ // Check to see if there is an outbound wait
822
+ if ((scheduledQueueItem === null || scheduledQueueItem === void 0 ? void 0 : scheduledQueueItem.height) != undefined && txStatus.stage < exports.TxStage.OUTBOUND_CHAIN_CONFIRMED) {
823
+ txStatus = yield this.checkOutboundTx(txStatus, scheduledQueueItem, lastBlockHeight);
824
+ }
825
+ //console.log(`Tx stage ${txStatus.stage}\nTx seconds left ${txStatus.seconds}`)
826
+ return txStatus;
827
+ }
828
+ // If not in queue, outbound Tx sent // check synth // check it status == done
829
+ if (!scheduledQueueItem && getAsset) {
830
+ txStatus.stage = exports.TxStage.OUTBOUND_CHAIN_UNCONFIRMED;
831
+ if (getAsset === null || getAsset === void 0 ? void 0 : getAsset.synth) {
832
+ if (((_c = txData.observed_tx) === null || _c === void 0 ? void 0 : _c.status) == xchainThornode.ObservedTxStatusEnum.Done) {
833
+ txStatus.stage = exports.TxStage.OUTBOUND_CHAIN_CONFIRMED;
834
+ txStatus.seconds = 0;
835
+ }
836
+ else {
837
+ txStatus.seconds = 6;
838
+ }
839
+ //console.log(`Tx stage ${txStatus.stage}\nTx seconds left ${txStatus.seconds}`)
840
+ return txStatus;
841
+ }
842
+ if (((_d = txData.observed_tx) === null || _d === void 0 ? void 0 : _d.status) == xchainThornode.ObservedTxStatusEnum.Done && getAsset.chain != xchainUtil.Chain.THORChain) {
843
+ // Retrieve recorded asset block height for the Outbound asset
844
+ const recordedBlockHeight = yield this.thorchainCache.thornode.getLastBlock(+recordedTCBlock.height);
845
+ // Match outbound asset to block record
846
+ const assetBlockHeight = recordedBlockHeight.find((obj) => obj.chain === (getAsset === null || getAsset === void 0 ? void 0 : getAsset.chain));
847
+ if ((lastBlockHeight === null || lastBlockHeight === void 0 ? void 0 : lastBlockHeight.last_observed_in) && (assetBlockHeight === null || assetBlockHeight === void 0 ? void 0 : assetBlockHeight.last_observed_in)) {
848
+ const chainblockTime = this.chainAttributes[getAsset.chain].avgBlockTimeInSecs;
849
+ // Difference between current block and the recorded tx block for the outbound asset
850
+ const blockDifference = lastBlockHeight.last_observed_in - assetBlockHeight.last_observed_in;
851
+ const timeElapsed = blockDifference * chainblockTime;
852
+ // If the time elapsed since the tx is greater than the chains block time, assume tx has 1 ocnfirmation else return time left to wait
853
+ txStatus.seconds = timeElapsed > chainblockTime ? 0 : chainblockTime - timeElapsed;
854
+ console.log(timeElapsed);
855
+ }
856
+ else if (txData.observed_tx.tx.id && (lastBlockHeight === null || lastBlockHeight === void 0 ? void 0 : lastBlockHeight.thorchain)) {
857
+ const recordedAction = yield this.thorchainCache.midgard.getActions(txData.observed_tx.tx.id);
858
+ const recordedBlockheight = recordedAction.find((block) => {
859
+ return block;
860
+ });
861
+ if (!recordedBlockheight)
862
+ throw Error(`No height recorded`);
863
+ const chainblockTime = this.chainAttributes[getAsset.chain].avgBlockTimeInSecs;
864
+ console.log(chainblockTime);
865
+ const blockDifference = (lastBlockHeight === null || lastBlockHeight === void 0 ? void 0 : lastBlockHeight.thorchain) - +recordedBlockheight.height;
866
+ console.log(blockDifference);
867
+ const timeElapsed = (blockDifference * chainblockTime) / this.chainAttributes[getAsset.chain].avgBlockTimeInSecs;
868
+ txStatus.seconds = timeElapsed > chainblockTime ? 0 : chainblockTime - timeElapsed;
869
+ console.log(txStatus.seconds);
870
+ txStatus.stage = exports.TxStage.OUTBOUND_CHAIN_CONFIRMED;
871
+ }
872
+ //console.log(`Tx stage ${txStatus.stage}\nTx seconds left ${txStatus.seconds}`)
873
+ return txStatus;
874
+ }
875
+ else {
876
+ txStatus.seconds = 0;
877
+ txStatus.stage = exports.TxStage.OUTBOUND_CHAIN_CONFIRMED;
878
+ }
879
+ //console.log(`Tx stage ${txStatus.stage}\nTx seconds left ${txStatus.seconds}`)
880
+ return txStatus;
881
+ }
882
+ else {
883
+ // case example "memo": "OUT:08BC062B248F6F27D0FECEF1650843585A1496BFFEAF7CB17A1CBC30D8D58F9C" where no asset is found its a thorchain tx. Confirms in ~6 seconds
884
+ txStatus.seconds = 0;
885
+ txStatus.stage = exports.TxStage.OUTBOUND_CHAIN_CONFIRMED;
886
+ }
887
+ return txStatus;
888
+ });
889
+ }
890
+ /** Stage 1 */
891
+ checkTxDefined(txStatus, sourceChain) {
892
+ return __awaiter(this, void 0, void 0, function* () {
893
+ // If there is an error Thornode does not know about it. wait 60 seconds
894
+ // If a long block time like BTC, can check or poll to see if the status changes.
895
+ if (sourceChain) {
896
+ txStatus.seconds = this.chainAttributes[sourceChain].avgBlockTimeInSecs;
897
+ }
898
+ else {
899
+ txStatus.seconds = 60;
900
+ }
901
+ return txStatus;
902
+ });
903
+ }
904
+ /** Stage 2, THORNode has seen it. See if observed only (conf counting) or it has been processed by THORChain */
905
+ // e.g. https://thornode.ninerealms.com/thorchain/tx/365AC447BE6CE4A55D14143975EE3823A93A0D8DE2B70AECDD63B6A905C3D72B
906
+ checkObservedOnly(txStatus, scheduledQueueItem, observed_tx, sourceChain) {
907
+ var _a, _b;
908
+ return __awaiter(this, void 0, void 0, function* () {
909
+ if (((_a = observed_tx === null || observed_tx === void 0 ? void 0 : observed_tx.tx) === null || _a === void 0 ? void 0 : _a.chain) != undefined) {
910
+ sourceChain = getChain(observed_tx.tx.chain);
911
+ }
912
+ else {
913
+ throw new Error(`Cannot get source chain ${(_b = observed_tx === null || observed_tx === void 0 ? void 0 : observed_tx.tx) === null || _b === void 0 ? void 0 : _b.chain}`);
914
+ }
915
+ //If observed by not final, need to wait till the finalised block before moving to the next stage, blocks in source chain
916
+ if ((observed_tx === null || observed_tx === void 0 ? void 0 : observed_tx.block_height) && (observed_tx === null || observed_tx === void 0 ? void 0 : observed_tx.finalise_height) && scheduledQueueItem.height) {
917
+ if (observed_tx.block_height < observed_tx.finalise_height) {
918
+ txStatus.stage = exports.TxStage.CONF_COUNTING;
919
+ const blocksToWait = observed_tx.finalise_height - scheduledQueueItem.height;
920
+ txStatus.seconds = blocksToWait * this.chainAttributes[sourceChain].avgBlockTimeInSecs;
921
+ }
922
+ else if (observed_tx.status != xchainThornode.ObservedTxStatusEnum.Done) {
923
+ // processed but not yet full final, e.g. not 2/3 nodes signed
924
+ txStatus.seconds = this.chainAttributes[xchainUtil.THORChain].avgBlockTimeInSecs; // wait one more TC block
925
+ txStatus.stage = exports.TxStage.TC_PROCESSING;
926
+ }
927
+ }
928
+ return txStatus;
929
+ });
930
+ }
931
+ /**
932
+ * Stage 3
933
+ * @param txStatus
934
+ * @param txData
935
+ * @param scheduledQueue
936
+ * @param scheduledQueueItem
937
+ * @param lastBlockHeight
938
+ * @returns
939
+ */
940
+ checkOutboundQueue(txStatus, scheduledQueueItem, lastBlockHeight) {
941
+ return __awaiter(this, void 0, void 0, function* () {
942
+ // If the scheduled block is greater than the current block, need to wait that amount of blocks till outbound is sent
943
+ if ((scheduledQueueItem === null || scheduledQueueItem === void 0 ? void 0 : scheduledQueueItem.height) && (lastBlockHeight === null || lastBlockHeight === void 0 ? void 0 : lastBlockHeight.thorchain)) {
944
+ if (lastBlockHeight.thorchain < (scheduledQueueItem === null || scheduledQueueItem === void 0 ? void 0 : scheduledQueueItem.height)) {
945
+ const blocksToWait = scheduledQueueItem.height - lastBlockHeight.thorchain;
946
+ txStatus.stage = exports.TxStage.OUTBOUND_QUEUED;
947
+ txStatus.seconds = blocksToWait * this.chainAttributes[xchainUtil.THORChain].avgBlockTimeInSecs;
948
+ return txStatus;
949
+ }
950
+ else {
951
+ txStatus.stage = exports.TxStage.OUTBOUND_CHAIN_UNCONFIRMED;
952
+ return txStatus;
953
+ }
954
+ }
955
+ return txStatus;
956
+ });
957
+ }
958
+ /** Stage 4 */
959
+ checkOutboundTx(txStatus, scheduledQueueItem, lastBlockHeight) {
960
+ return __awaiter(this, void 0, void 0, function* () {
961
+ if ((scheduledQueueItem === null || scheduledQueueItem === void 0 ? void 0 : scheduledQueueItem.height) && (lastBlockHeight === null || lastBlockHeight === void 0 ? void 0 : lastBlockHeight.thorchain)) {
962
+ const blockDifference = scheduledQueueItem.height - (lastBlockHeight === null || lastBlockHeight === void 0 ? void 0 : lastBlockHeight.thorchain);
963
+ const timeElapsed = blockDifference * this.chainAttributes[xchainUtil.THORChain].avgBlockTimeInSecs;
964
+ if (blockDifference == 0) {
965
+ // If Tx has just been sent, Stage 3 should pick this up really
966
+ txStatus.stage = exports.TxStage.OUTBOUND_CHAIN_UNCONFIRMED;
967
+ txStatus.seconds = this.chainAttributes[xchainUtil.THORChain].avgBlockTimeInSecs;
968
+ }
969
+ else if (timeElapsed < txStatus.seconds) {
970
+ // if the time passed since the outbound TX was sent is less than the outbound block time, outbound Tx unconfirmed, wait a bit longer.
971
+ txStatus.stage = exports.TxStage.OUTBOUND_CHAIN_UNCONFIRMED;
972
+ txStatus.seconds = this.chainAttributes[xchainUtil.THORChain].avgBlockTimeInSecs - timeElapsed; // workout how long to wait
973
+ }
974
+ else {
975
+ // time passed is greater than outbound Tx time, Tx is confirmed. Thus stage 5
976
+ txStatus.stage = exports.TxStage.OUTBOUND_CHAIN_CONFIRMED;
977
+ txStatus.seconds = 0;
978
+ }
979
+ }
980
+ return txStatus;
981
+ });
982
+ }
983
+ }
984
+
985
+ /**
986
+ * Represent a Liquidity Pool in Thorchain
987
+ */
988
+ class LiquidityPool {
989
+ constructor(pool, decimals) {
990
+ this.pool = pool;
991
+ const asset = xchainUtil.assetFromString(this.pool.asset);
992
+ if (!asset)
993
+ throw new Error(`could not parse ${this.pool.asset}`);
994
+ this.asset = asset;
995
+ this.decimals = decimals;
996
+ this.assetString = this.pool.asset;
997
+ this.assetBalance = xchainUtil.baseAmount(this.pool.assetDepth);
998
+ this.runeBalance = xchainUtil.baseAmount(this.pool.runeDepth);
999
+ this.runeToAssetRatio = this.runeBalance.amount().div(this.assetBalance.amount());
1000
+ this.assetToRuneRatio = this.assetBalance.amount().div(this.runeBalance.amount());
1001
+ }
1002
+ isAvailable() {
1003
+ return this.pool.status.toLowerCase() === 'available';
1004
+ }
1005
+ }
1006
+
1007
+ const SAME_ASSET_EXCHANGE_RATE = new BigNumber.BigNumber(1);
1008
+ const TEN_MINUTES = 10 * 60 * 1000;
1009
+ const DEFAULT_THORCHAIN_DECIMALS = 8;
1010
+ const USD_ASSETS = {
1011
+ mainnet: [
1012
+ xchainUtil.assetFromString('BNB.BUSD-BD1'),
1013
+ xchainUtil.assetFromString('ETH.USDC-0XA0B86991C6218B36C1D19D4A2E9EB0CE3606EB48'),
1014
+ xchainUtil.assetFromString('ETH.USDT-0XDAC17F958D2EE523A2206206994597C13D831EC7'),
1015
+ ],
1016
+ stagenet: [xchainUtil.assetFromString('ETH.USDT-0XDAC17F958D2EE523A2206206994597C13D831EC7')],
1017
+ testnet: [xchainUtil.assetFromString('BNB.BUSD-74E'), xchainUtil.assetFromString('ETH.USDT-0XA3910454BF2CB59B8B3A401589A3BACC5CA42306')],
1018
+ };
1019
+ /**
1020
+ * This class manages retrieving information from up to date Thorchain
1021
+ */
1022
+ class ThorchainCache {
1023
+ /**
1024
+ * Contrustor to create a ThorchainCache
1025
+ *
1026
+ * @param midgard - an instance of the midgard API (could be pointing to stagenet,testnet,mainnet)
1027
+ * @param expirePoolCacheMillis - how long should the pools be cached before expiry
1028
+ * @param expireAsgardCacheMillis - how long should the inboundAsgard Addresses be cached before expiry
1029
+ * @param expireInboundDetailsCacheMillis - how long should the InboundDetails be cached before expiry
1030
+ * @param expireNetworkValuesCacheMillis - how long should the Mimir/Constants be cached before expiry
1031
+ * @returns ThorchainCache
1032
+ */
1033
+ constructor(midgard, thornode, expirePoolCacheMillis = 6000, expireAsgardCacheMillis = TEN_MINUTES, expireInboundDetailsCacheMillis = 6000, expireNetworkValuesCacheMillis = TEN_MINUTES) {
1034
+ this.asgardAssetsCache = undefined;
1035
+ this.inboundDetailCache = undefined;
1036
+ this.networkValuesCache = undefined;
1037
+ this.midgard = midgard;
1038
+ this.thornode = thornode;
1039
+ this.expirePoolCacheMillis = expirePoolCacheMillis;
1040
+ this.expireAsgardCacheMillis = expireAsgardCacheMillis;
1041
+ this.expireInboundDetailsCacheMillis = expireInboundDetailsCacheMillis;
1042
+ this.expireNetworkValuesCacheMillis = expireNetworkValuesCacheMillis;
1043
+ //initialize the cache
1044
+ this.refereshPoolCache();
1045
+ }
1046
+ /**
1047
+ * Gets the exchange rate of the from asset in terms on the to asset
1048
+ *
1049
+ * @param asset - cannot be RUNE.
1050
+ * @returns Promise<BigNumber>
1051
+ */
1052
+ getExchangeRate(from, to) {
1053
+ return __awaiter(this, void 0, void 0, function* () {
1054
+ let exchangeRate;
1055
+ if (xchainUtil.eqAsset(from, to)) {
1056
+ exchangeRate = SAME_ASSET_EXCHANGE_RATE;
1057
+ }
1058
+ else if (xchainUtil.isAssetRuneNative(from)) {
1059
+ // Runes per Asset
1060
+ const lpTo = yield this.getPoolForAsset(to);
1061
+ exchangeRate = lpTo.assetToRuneRatio;
1062
+ }
1063
+ else if (xchainUtil.isAssetRuneNative(to)) {
1064
+ // Asset per rune
1065
+ const lpFrom = yield this.getPoolForAsset(from);
1066
+ exchangeRate = lpFrom.runeToAssetRatio;
1067
+ }
1068
+ else {
1069
+ // AssetA per AssetB
1070
+ const lpFrom = yield this.getPoolForAsset(from);
1071
+ const lpTo = yield this.getPoolForAsset(to);
1072
+ // from/R * R/to = from/to
1073
+ exchangeRate = lpFrom.runeToAssetRatio.times(lpTo.assetToRuneRatio);
1074
+ }
1075
+ // console.log(` 1 ${assetToString(from)} = ${exchangeRate} ${assetToString(to)}`)
1076
+ return exchangeRate;
1077
+ });
1078
+ }
1079
+ /**
1080
+ * Gets the Liquidity Pool for a given Asset
1081
+ *
1082
+ * @param asset - cannot be RUNE, since Rune is the other side of each pool.
1083
+ * @returns Promise<LiquidityPool>
1084
+ */
1085
+ getPoolForAsset(asset) {
1086
+ return __awaiter(this, void 0, void 0, function* () {
1087
+ if (xchainUtil.isAssetRuneNative(asset))
1088
+ throw Error(`AssetRuneNative doesn't have a pool`);
1089
+ const pools = yield this.getPools();
1090
+ // Not: we use ticker, not asset string to get the same pool for both assets and synths
1091
+ const pool = pools[asset.ticker];
1092
+ if (pool) {
1093
+ return pool;
1094
+ }
1095
+ throw Error(`Pool for ${xchainUtil.assetToString(asset)} not found`);
1096
+ });
1097
+ }
1098
+ /**
1099
+ * Get all the Liquidity Pools currently cached.
1100
+ * if the cache is expired, the pools wioll be re-fetched from midgard
1101
+ *
1102
+ * @returns Promise<Record<string, LiquidityPool>>
1103
+ */
1104
+ getPools() {
1105
+ var _a, _b;
1106
+ return __awaiter(this, void 0, void 0, function* () {
1107
+ const millisSinceLastRefeshed = Date.now() - (((_a = this.poolCache) === null || _a === void 0 ? void 0 : _a.lastRefreshed) || 0);
1108
+ if (millisSinceLastRefeshed > this.expirePoolCacheMillis) {
1109
+ try {
1110
+ yield this.refereshPoolCache();
1111
+ }
1112
+ catch (e) {
1113
+ console.error(e);
1114
+ }
1115
+ }
1116
+ if (this.poolCache) {
1117
+ return (_b = this.poolCache) === null || _b === void 0 ? void 0 : _b.pools;
1118
+ }
1119
+ else {
1120
+ throw Error(`Could not refresh Pools `);
1121
+ }
1122
+ });
1123
+ }
1124
+ /**
1125
+ * Refreshes the Pool Cache
1126
+ *
1127
+ * NOTE: do not call refereshPoolCache() directly, call getPools() instead
1128
+ * which will refresh the cache if it's expired
1129
+ */
1130
+ refereshPoolCache() {
1131
+ var _a;
1132
+ return __awaiter(this, void 0, void 0, function* () {
1133
+ const [thornodePools, midgardPools] = yield Promise.all([this.thornode.getPools(), this.midgard.getPools()]);
1134
+ const poolMap = {};
1135
+ if (midgardPools) {
1136
+ for (const pool of midgardPools) {
1137
+ const thornodePool = thornodePools.find((p) => p.asset === pool.asset);
1138
+ const decimals = (_a = thornodePool === null || thornodePool === void 0 ? void 0 : thornodePool.decimals) !== null && _a !== void 0 ? _a : 8;
1139
+ const lp = new LiquidityPool(pool, decimals);
1140
+ poolMap[lp.asset.ticker] = lp;
1141
+ }
1142
+ this.poolCache = {
1143
+ lastRefreshed: Date.now(),
1144
+ pools: poolMap,
1145
+ };
1146
+ }
1147
+ });
1148
+ }
1149
+ /**
1150
+ * Refreshes the asgardAssetsCache Cache
1151
+ *
1152
+ * NOTE: do not call refereshAsgardCache() directly, call getAsgardAssets() instead
1153
+ * which will refresh the cache if it's expired
1154
+ */
1155
+ refereshAsgardCache() {
1156
+ return __awaiter(this, void 0, void 0, function* () {
1157
+ const inboundAddressesItems = yield this.midgard.getAllInboundAddresses();
1158
+ const map = {};
1159
+ if (inboundAddressesItems) {
1160
+ for (const inboundAddress of inboundAddressesItems) {
1161
+ map[inboundAddress.chain] = inboundAddress;
1162
+ }
1163
+ this.asgardAssetsCache = {
1164
+ lastRefreshed: Date.now(),
1165
+ inboundAddressesItems: map,
1166
+ };
1167
+ }
1168
+ });
1169
+ }
1170
+ /**
1171
+ * Refreshes the InboundDetailCache Cache
1172
+ *
1173
+ * NOTE: do not call refereshInboundDetailCache() directly, call getInboundDetails() instead
1174
+ * which will refresh the cache if it's expired
1175
+ */
1176
+ refereshInboundDetailCache() {
1177
+ return __awaiter(this, void 0, void 0, function* () {
1178
+ const inboundDetails = yield this.midgard.getInboundDetails();
1179
+ this.inboundDetailCache = {
1180
+ lastRefreshed: Date.now(),
1181
+ inboundDetails,
1182
+ };
1183
+ });
1184
+ }
1185
+ /**
1186
+ * Refreshes the NetworkValuesCache Cache
1187
+ *
1188
+ * NOTE: do not call refereshNetworkValuesCache() directly, call getNetworkValuess() instead
1189
+ * which will refresh the cache if it's expired
1190
+ */
1191
+ refereshNetworkValuesCache() {
1192
+ return __awaiter(this, void 0, void 0, function* () {
1193
+ const networkValues = yield this.midgard.getNetworkValues();
1194
+ this.networkValuesCache = {
1195
+ lastRefreshed: Date.now(),
1196
+ networkValues,
1197
+ };
1198
+ });
1199
+ }
1200
+ /**
1201
+ *
1202
+ * Calcuate the expected slip, output & swapFee given the current pool depths
1203
+ *
1204
+ * swapFee - the amount of asset lost according to slip calculations
1205
+ * slip - the percent (0-1) of original amount lost to slipfees
1206
+ * output - the amount of asset expected from the swap *
1207
+ *
1208
+ * @param inputAmount - CryptoAmount amount to swap from
1209
+ * @param destinationAsset - destimation Asset to swap to
1210
+ * @returns SwapOutput - swap output object - output - fee - slip
1211
+ */
1212
+ getExpectedSwapOutput(inputAmount, destinationAsset) {
1213
+ return __awaiter(this, void 0, void 0, function* () {
1214
+ if (xchainUtil.isAssetRuneNative(inputAmount.asset)) {
1215
+ //singleswap from rune -> asset
1216
+ const pool = yield this.getPoolForAsset(destinationAsset);
1217
+ return getSingleSwap(inputAmount, pool, false);
1218
+ }
1219
+ else if (xchainUtil.isAssetRuneNative(destinationAsset)) {
1220
+ //singleswap from asset -> rune
1221
+ const pool = yield this.getPoolForAsset(inputAmount.asset);
1222
+ return getSingleSwap(inputAmount, pool, true);
1223
+ }
1224
+ else {
1225
+ //doubleswap asset-> asset
1226
+ const inPool = yield this.getPoolForAsset(inputAmount.asset);
1227
+ const destPool = yield this.getPoolForAsset(destinationAsset);
1228
+ return yield getDoubleSwap(inputAmount, inPool, destPool, this);
1229
+ }
1230
+ });
1231
+ }
1232
+ /**
1233
+ * Returns the exchange of a CryptoAmount to a different Asset
1234
+ *
1235
+ * Ex. convert(input:100 BUSD, outAsset: BTC) -> 0.0001234 BTC
1236
+ *
1237
+ * @param input - amount/asset to convert to outAsset
1238
+ * @param ouAsset - the Asset you want to convert to
1239
+ * @returns CryptoAmount of input
1240
+ */
1241
+ convert(input, outAsset) {
1242
+ var _a;
1243
+ return __awaiter(this, void 0, void 0, function* () {
1244
+ const exchangeRate = yield this.getExchangeRate(input.asset, outAsset);
1245
+ let decimals = DEFAULT_THORCHAIN_DECIMALS;
1246
+ if (!xchainUtil.isAssetRuneNative(outAsset)) {
1247
+ const pool = yield this.getPoolForAsset(outAsset);
1248
+ decimals = (_a = pool.decimals) !== null && _a !== void 0 ? _a : DEFAULT_THORCHAIN_DECIMALS;
1249
+ }
1250
+ const amt = xchainUtil.assetAmount(input.assetAmount.times(exchangeRate).amount().toFixed(), decimals);
1251
+ const result = new CryptoAmount(xchainUtil.assetToBase(amt), outAsset);
1252
+ return result;
1253
+ });
1254
+ }
1255
+ getRouterAddressForChain(chain) {
1256
+ return __awaiter(this, void 0, void 0, function* () {
1257
+ const inboundAsgard = (yield this.getInboundAddressesItems())[chain];
1258
+ if (!(inboundAsgard === null || inboundAsgard === void 0 ? void 0 : inboundAsgard.router)) {
1259
+ throw new Error('router address is not defined');
1260
+ }
1261
+ return inboundAsgard === null || inboundAsgard === void 0 ? void 0 : inboundAsgard.router;
1262
+ });
1263
+ }
1264
+ getInboundAddressesItems() {
1265
+ var _a;
1266
+ return __awaiter(this, void 0, void 0, function* () {
1267
+ const millisSinceLastRefeshed = Date.now() - (((_a = this.asgardAssetsCache) === null || _a === void 0 ? void 0 : _a.lastRefreshed) || 0);
1268
+ if (millisSinceLastRefeshed > this.expireAsgardCacheMillis) {
1269
+ try {
1270
+ yield this.refereshAsgardCache();
1271
+ }
1272
+ catch (e) {
1273
+ console.error(e);
1274
+ }
1275
+ }
1276
+ if (this.asgardAssetsCache) {
1277
+ return this.asgardAssetsCache.inboundAddressesItems;
1278
+ }
1279
+ else {
1280
+ throw Error(`Could not refresh refereshAsgardCache `);
1281
+ }
1282
+ });
1283
+ }
1284
+ getInboundDetails() {
1285
+ var _a;
1286
+ return __awaiter(this, void 0, void 0, function* () {
1287
+ const millisSinceLastRefeshed = Date.now() - (((_a = this.inboundDetailCache) === null || _a === void 0 ? void 0 : _a.lastRefreshed) || 0);
1288
+ if (millisSinceLastRefeshed > this.expireInboundDetailsCacheMillis) {
1289
+ try {
1290
+ yield this.refereshInboundDetailCache();
1291
+ }
1292
+ catch (e) {
1293
+ console.error(e);
1294
+ }
1295
+ }
1296
+ if (this.inboundDetailCache) {
1297
+ return this.inboundDetailCache.inboundDetails;
1298
+ }
1299
+ else {
1300
+ throw Error(`Could not refereshInboundDetailCache `);
1301
+ }
1302
+ });
1303
+ }
1304
+ getNetworkValues() {
1305
+ var _a;
1306
+ return __awaiter(this, void 0, void 0, function* () {
1307
+ const millisSinceLastRefeshed = Date.now() - (((_a = this.networkValuesCache) === null || _a === void 0 ? void 0 : _a.lastRefreshed) || 0);
1308
+ if (millisSinceLastRefeshed > this.expireNetworkValuesCacheMillis) {
1309
+ try {
1310
+ yield this.refereshNetworkValuesCache();
1311
+ }
1312
+ catch (e) {
1313
+ console.error(e);
1314
+ }
1315
+ }
1316
+ if (this.networkValuesCache) {
1317
+ return this.networkValuesCache.networkValues;
1318
+ }
1319
+ else {
1320
+ throw Error(`Could not refereshInboundDetailCache `);
1321
+ }
1322
+ });
1323
+ }
1324
+ getDeepestUSDPool() {
1325
+ return __awaiter(this, void 0, void 0, function* () {
1326
+ const usdAssets = USD_ASSETS[this.midgard.network];
1327
+ let deepestRuneDepth = new BigNumber.BigNumber(0);
1328
+ let deepestPool = null;
1329
+ for (const usdAsset of usdAssets) {
1330
+ const usdPool = yield this.getPoolForAsset(usdAsset);
1331
+ if (usdPool.runeBalance.amount() > deepestRuneDepth) {
1332
+ deepestRuneDepth = usdPool.runeBalance.amount();
1333
+ deepestPool = usdPool;
1334
+ }
1335
+ }
1336
+ if (!deepestPool)
1337
+ throw Error('now USD Pool found');
1338
+ return deepestPool;
1339
+ });
1340
+ }
1341
+ }
1342
+
1343
+ /**
1344
+ *
1345
+ * @param liquidity - asset amount added
1346
+ * @param pool - pool depths
1347
+ * @returns liquidity units
1348
+ */
1349
+ const getLiquidityUnits = (liquidity, pool) => {
1350
+ // formula: ((R + T) (r T + R t))/(4 R T)
1351
+ // part1 * (part2 + part3) / denominator
1352
+ const r = liquidity.rune;
1353
+ const t = liquidity.asset;
1354
+ const R = pool.runeBalance.amount().plus(r); // Must add r first
1355
+ const T = pool.assetBalance.amount().plus(t); // Must add t first
1356
+ const part1 = R.plus(T);
1357
+ const part2 = r.times(T);
1358
+ const part3 = R.times(t);
1359
+ const numerator = part1.times(part2.plus(part3));
1360
+ const denominator = R.times(T).times(4);
1361
+ const result = numerator.div(denominator);
1362
+ return result;
1363
+ };
1364
+ /**
1365
+ *
1366
+ * @param unitData
1367
+ * @param pool
1368
+ * @returns
1369
+ */
1370
+ const getPoolShare = (unitData, pool) => {
1371
+ // formula: (rune * part) / total; (asset * part) / total
1372
+ const units = unitData.liquidityUnits;
1373
+ const total = unitData.totalUnits;
1374
+ const R = pool.runeBalance.amount();
1375
+ const T = pool.assetBalance.amount();
1376
+ const asset = T.times(units).div(total);
1377
+ const rune = R.times(units).div(total);
1378
+ const liquidityData = {
1379
+ asset: asset,
1380
+ rune: rune,
1381
+ };
1382
+ return liquidityData;
1383
+ };
1384
+ /**
1385
+ *
1386
+ * @param liquidity
1387
+ * @param pool
1388
+ * @returns
1389
+ */
1390
+ const getSlipOnLiquidity = (liquidity, pool) => {
1391
+ // formula: (t * R - T * r)/ (T*r + R*T)
1392
+ const r = liquidity.rune;
1393
+ const t = liquidity.asset;
1394
+ const R = pool.runeBalance.amount();
1395
+ const T = pool.assetBalance.amount();
1396
+ const numerator = t.times(R).minus(T.times(r));
1397
+ const denominator = T.times(r).plus(R.times(T));
1398
+ const result = numerator.div(denominator).abs();
1399
+ return result;
1400
+ };
1401
+ /**
1402
+ *
1403
+ * @param liquidity
1404
+ * @param pool
1405
+ * @param block
1406
+ * @returns
1407
+ */
1408
+ // Blocks for full protection 144000 // 100 days
1409
+ const getLiquidityProtectionData = (liquidity, pool, block) => {
1410
+ //formula: protectionProgress (currentHeight-heightLastAdded)/blocksforfullprotection
1411
+ const R0 = liquidity.rune; // symetrical value of rune deposit
1412
+ const A0 = liquidity.asset; // symetrical value of asset deposit
1413
+ const R1 = pool.runeBalance.amount(); // rune to redeem
1414
+ const A1 = pool.assetBalance.amount(); // asset to redeem
1415
+ const P1 = R1.div(A1); // Pool ratio at withdrawal
1416
+ const coverage = A0.times(P1).plus(R0).minus(A1.times(P1).plus(R1));
1417
+ const currentHeight = block.current;
1418
+ const heightLastAdded = block.lastAdded;
1419
+ const blocksforfullprotection = block.fullProtection;
1420
+ const protectionProgress = (currentHeight - heightLastAdded) / blocksforfullprotection;
1421
+ const result = protectionProgress * coverage.toNumber(); // impermanent loss protection result
1422
+ return result;
1423
+ };
1424
+
1425
+ const defaultMidgardConfig = {
1426
+ mainnet: {
1427
+ apiRetries: 3,
1428
+ midgardBaseUrls: [
1429
+ 'https://midgard.thorchain.info',
1430
+ 'https://midgard.thorswap.net',
1431
+ 'https://midgard.ninerealms.com',
1432
+ ],
1433
+ },
1434
+ stagenet: {
1435
+ apiRetries: 3,
1436
+ midgardBaseUrls: ['https://stagenet-midgard.ninerealms.com'],
1437
+ },
1438
+ testnet: {
1439
+ apiRetries: 3,
1440
+ midgardBaseUrls: ['https://testnet.midgard.thorchain.info'],
1441
+ },
1442
+ };
1443
+ class Midgard {
1444
+ constructor(network = xchainClient.Network.Mainnet, config) {
1445
+ this.network = network;
1446
+ this.config = config !== null && config !== void 0 ? config : defaultMidgardConfig[this.network];
1447
+ axiosRetry__default['default'](axios__default['default'], { retries: this.config.apiRetries, retryDelay: axiosRetry__default['default'].exponentialDelay });
1448
+ this.midgardApis = this.config.midgardBaseUrls.map((url) => new xchainMidgard.MidgardApi(new xchainMidgard.Configuration({ basePath: url })));
1449
+ }
1450
+ getMimirDetails() {
1451
+ return __awaiter(this, void 0, void 0, function* () {
1452
+ const path = '/v2/thorchain/mimir';
1453
+ for (const baseUrl of this.config.midgardBaseUrls) {
1454
+ try {
1455
+ const { data } = yield axios__default['default'].get(`${baseUrl}${path}`);
1456
+ return data;
1457
+ }
1458
+ catch (e) {
1459
+ console.error(e);
1460
+ }
1461
+ }
1462
+ throw new Error('Midgard not responding');
1463
+ });
1464
+ }
1465
+ /**
1466
+ *
1467
+ * @returns an array of Pools
1468
+ */
1469
+ getPools() {
1470
+ return __awaiter(this, void 0, void 0, function* () {
1471
+ for (const api of this.midgardApis) {
1472
+ try {
1473
+ return (yield api.getPools()).data;
1474
+ }
1475
+ catch (e) {
1476
+ console.error(e);
1477
+ }
1478
+ }
1479
+ throw Error(`Midgard not responding`);
1480
+ });
1481
+ }
1482
+ getAllInboundAddresses() {
1483
+ return __awaiter(this, void 0, void 0, function* () {
1484
+ for (const api of this.midgardApis) {
1485
+ try {
1486
+ return (yield api.getProxiedInboundAddresses()).data;
1487
+ }
1488
+ catch (e) {
1489
+ console.error(e);
1490
+ }
1491
+ }
1492
+ throw Error(`Midgard not responding`);
1493
+ });
1494
+ }
1495
+ /**
1496
+ * Gets the Inbound Details
1497
+ * @returns inbound details
1498
+ */
1499
+ getInboundDetails() {
1500
+ return __awaiter(this, void 0, void 0, function* () {
1501
+ const [mimirDetails, allInboundDetails] = yield Promise.all([this.getMimirDetails(), this.getAllInboundAddresses()]);
1502
+ const inboundDetails = {};
1503
+ for (const inboundDetail of allInboundDetails) {
1504
+ const chain = inboundDetail.chain;
1505
+ if (!inboundDetail.gas_rate)
1506
+ throw new Error(`Could not get gas_rate for ${chain}`);
1507
+ const details = {
1508
+ vault: inboundDetail.address,
1509
+ gas_rate: new BigNumber__default['default'](inboundDetail.gas_rate),
1510
+ haltedChain: (inboundDetail === null || inboundDetail === void 0 ? void 0 : inboundDetail.halted) || !!mimirDetails[`HALT${chain}CHAIN`] || !!mimirDetails['HALTCHAINGLOBAL'],
1511
+ haltedTrading: !!mimirDetails['HALTTRADING'] || !!mimirDetails[`HALT${chain}TRADING`],
1512
+ haltedLP: !!mimirDetails['PAUSELP'] || !!mimirDetails[`PAUSELP${chain}`],
1513
+ };
1514
+ inboundDetails[chain] = details;
1515
+ }
1516
+ // add mock THORCHAIN inbound details
1517
+ const details = {
1518
+ vault: '',
1519
+ gas_rate: new BigNumber__default['default'](0),
1520
+ haltedChain: false,
1521
+ haltedTrading: !!mimirDetails['HALTTRADING'],
1522
+ haltedLP: false, //
1523
+ };
1524
+ inboundDetails[xchainUtil.Chain.THORChain] = details;
1525
+ return inboundDetails;
1526
+ });
1527
+ }
1528
+ getConstantsDetails() {
1529
+ return __awaiter(this, void 0, void 0, function* () {
1530
+ const path = '/v2/thorchain/constants';
1531
+ for (const baseUrl of this.config.midgardBaseUrls) {
1532
+ try {
1533
+ const { data } = yield axios__default['default'].get(`${baseUrl}${path}`);
1534
+ return data.int_64_values;
1535
+ }
1536
+ catch (e) {
1537
+ console.error(e);
1538
+ }
1539
+ }
1540
+ throw new Error('Midgard not responding');
1541
+ });
1542
+ }
1543
+ /**
1544
+ *
1545
+ * @returns the outbound Tx Value in RUNE (Basemount)
1546
+ */
1547
+ getScheduledOutboundValue() {
1548
+ return __awaiter(this, void 0, void 0, function* () {
1549
+ const path = '/v2/thorchain/queue';
1550
+ for (const baseUrl of this.config.midgardBaseUrls) {
1551
+ try {
1552
+ const { data } = yield axios__default['default'].get(`${baseUrl}${path}`);
1553
+ const value = new CryptoAmount(xchainUtil.baseAmount(data['scheduled_outbound_value']), xchainUtil.AssetRuneNative);
1554
+ return value;
1555
+ }
1556
+ catch (e) {
1557
+ console.error(e);
1558
+ }
1559
+ }
1560
+ throw new Error('Midgard not responding');
1561
+ });
1562
+ }
1563
+ /**
1564
+ * Function that wraps Mimir and Constants to return the value from a given constant name. Searchs Mimir first.
1565
+ *
1566
+ * @param networkValueName the network value to be used to search the contsants
1567
+ * @returns the mimir or constants value
1568
+ */
1569
+ getNetworkValues() {
1570
+ return __awaiter(this, void 0, void 0, function* () {
1571
+ const [mimirDetails, constantDetails] = yield Promise.all([this.getMimirDetails(), this.getConstantsDetails()]);
1572
+ const retVal = {};
1573
+ // insert constants first
1574
+ for (const constantKey of Object.keys(constantDetails)) {
1575
+ retVal[constantKey.toUpperCase()] = constantDetails[constantKey];
1576
+ }
1577
+ // mimir will overwrite any dupe constants
1578
+ for (const mimirKey of Object.keys(mimirDetails)) {
1579
+ const mimirValue = mimirDetails[mimirKey];
1580
+ retVal[mimirKey.toUpperCase()] = mimirValue;
1581
+ }
1582
+ return retVal;
1583
+ });
1584
+ }
1585
+ /**
1586
+ * Gets the latest block using the Health endpoint within Midgard
1587
+ *
1588
+ * @returns
1589
+ */
1590
+ getLatestBlockHeight() {
1591
+ return __awaiter(this, void 0, void 0, function* () {
1592
+ for (const api of this.midgardApis) {
1593
+ try {
1594
+ const data = (yield api.getHealth()).data;
1595
+ return +data.scannerHeight;
1596
+ }
1597
+ catch (e) {
1598
+ console.error(e);
1599
+ }
1600
+ }
1601
+ throw Error(`Midgard not responding`);
1602
+ });
1603
+ }
1604
+ /**
1605
+ * Gets actions related to a txID
1606
+ * @param txHash transaction id
1607
+ * @returns Type Action array of objects
1608
+ */
1609
+ getActions(txHash) {
1610
+ return __awaiter(this, void 0, void 0, function* () {
1611
+ for (const api of this.midgardApis) {
1612
+ try {
1613
+ const actions = (yield api.getActions('', txHash)).data.actions;
1614
+ return actions;
1615
+ }
1616
+ catch (e) {
1617
+ console.error(e);
1618
+ }
1619
+ }
1620
+ throw Error(`Midgard not responding`);
1621
+ });
1622
+ }
1623
+ }
1624
+
1625
+ const defaultThornodeConfig = {
1626
+ mainnet: {
1627
+ apiRetries: 3,
1628
+ thornodeBaseUrls: [
1629
+ `https://thornode.ninerealms.com`,
1630
+ `https://thornode.thorswap.net`,
1631
+ `https://thornode.thorchain.info`,
1632
+ ],
1633
+ },
1634
+ stagenet: {
1635
+ apiRetries: 3,
1636
+ thornodeBaseUrls: ['https://stagenet-thornode.ninerealms.com'],
1637
+ },
1638
+ testnet: {
1639
+ apiRetries: 3,
1640
+ thornodeBaseUrls: ['https://testnet.thornode.thorchain.info'],
1641
+ },
1642
+ };
1643
+ class Thornode {
1644
+ constructor(network = xchainClient.Network.Mainnet, config) {
1645
+ this.network = network;
1646
+ this.config = config !== null && config !== void 0 ? config : defaultThornodeConfig[this.network];
1647
+ axiosRetry__default['default'](axios__default['default'], { retries: this.config.apiRetries, retryDelay: axiosRetry__default['default'].exponentialDelay });
1648
+ this.transactionsApi = this.config.thornodeBaseUrls.map((url) => new xchainThornode.TransactionsApi(new xchainThornode.Configuration({ basePath: url })));
1649
+ this.queueApi = this.config.thornodeBaseUrls.map((url) => new xchainThornode.QueueApi(new xchainThornode.Configuration({ basePath: url })));
1650
+ this.networkApi = this.config.thornodeBaseUrls.map((url) => new xchainThornode.NetworkApi(new xchainThornode.Configuration({ basePath: url })));
1651
+ this.poolsApi = this.config.thornodeBaseUrls.map((url) => new xchainThornode.PoolsApi(new xchainThornode.Configuration({ basePath: url })));
1652
+ }
1653
+ /**
1654
+ * Returns the oubound transactions held by THORChain due to outbound delay
1655
+ * May be empty if there are no transactions
1656
+ *
1657
+ * @returns {ScheduledQueueItem} Array
1658
+ *
1659
+ */
1660
+ getscheduledQueue() {
1661
+ return __awaiter(this, void 0, void 0, function* () {
1662
+ for (const api of this.queueApi) {
1663
+ try {
1664
+ const queueScheduled = yield api.queueScheduled();
1665
+ return queueScheduled.data;
1666
+ }
1667
+ catch (e) {
1668
+ console.error(e);
1669
+ throw new Error(`THORNode not responding`);
1670
+ }
1671
+ }
1672
+ throw Error(`THORNode not responding`);
1673
+ });
1674
+ }
1675
+ getTxData(txHash) {
1676
+ return __awaiter(this, void 0, void 0, function* () {
1677
+ for (const api of this.transactionsApi) {
1678
+ try {
1679
+ const txResponse = yield api.tx(txHash);
1680
+ return txResponse.data;
1681
+ }
1682
+ catch (e) {
1683
+ const txR = {
1684
+ observed_tx: undefined,
1685
+ keysign_metric: undefined,
1686
+ };
1687
+ return txR;
1688
+ }
1689
+ }
1690
+ throw new Error(`THORNode not responding`);
1691
+ });
1692
+ }
1693
+ getLastBlock(height) {
1694
+ return __awaiter(this, void 0, void 0, function* () {
1695
+ for (const api of this.networkApi) {
1696
+ try {
1697
+ const lastBlock = yield api.lastblock(height);
1698
+ return lastBlock.data;
1699
+ }
1700
+ catch (e) {
1701
+ console.error(e);
1702
+ }
1703
+ }
1704
+ throw new Error(`THORNode not responding`);
1705
+ });
1706
+ }
1707
+ getPools() {
1708
+ return __awaiter(this, void 0, void 0, function* () {
1709
+ for (const api of this.poolsApi) {
1710
+ try {
1711
+ const pools = yield api.pools();
1712
+ return pools.data;
1713
+ }
1714
+ catch (e) {
1715
+ console.error(e);
1716
+ }
1717
+ }
1718
+ throw new Error(`THORNode not responding`);
1719
+ });
1720
+ }
1721
+ }
1722
+
1723
+ exports.CryptoAmount = CryptoAmount;
1724
+ exports.LiquidityPool = LiquidityPool;
1725
+ exports.Midgard = Midgard;
1726
+ exports.ThorchainCache = ThorchainCache;
1727
+ exports.ThorchainQuery = ThorchainQuery;
1728
+ exports.Thornode = Thornode;
1729
+ exports.calcNetworkFee = calcNetworkFee;
1730
+ exports.getDoubleSwap = getDoubleSwap;
1731
+ exports.getLiquidityProtectionData = getLiquidityProtectionData;
1732
+ exports.getLiquidityUnits = getLiquidityUnits;
1733
+ exports.getPoolShare = getPoolShare;
1734
+ exports.getSingleSwap = getSingleSwap;
1735
+ exports.getSlipOnLiquidity = getSlipOnLiquidity;