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