@curvefi/llamalend-api 1.0.2
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/.eslintrc.json +40 -0
- package/.github/workflows/lint.yml +15 -0
- package/.github/workflows/publish.yml +55 -0
- package/LICENSE +21 -0
- package/README.md +1976 -0
- package/lib/cache/index.d.ts +14 -0
- package/lib/cache/index.js +31 -0
- package/lib/constants/L2Networks.d.ts +1 -0
- package/lib/constants/L2Networks.js +1 -0
- package/lib/constants/abis/Controller.json +1027 -0
- package/lib/constants/abis/ERC20.json +222 -0
- package/lib/constants/abis/ERC4626.json +1674 -0
- package/lib/constants/abis/GaugeController.json +794 -0
- package/lib/constants/abis/GaugeFactoryMainnet.json +1 -0
- package/lib/constants/abis/GaugeFactorySidechain.json +475 -0
- package/lib/constants/abis/GaugeV5.json +958 -0
- package/lib/constants/abis/LeverageZap.json +35 -0
- package/lib/constants/abis/Llamma.json +984 -0
- package/lib/constants/abis/Minter.json +1 -0
- package/lib/constants/abis/MonetaryPolicy.json +221 -0
- package/lib/constants/abis/OneWayLendingFactoryABI.json +899 -0
- package/lib/constants/abis/SidechainGauge.json +939 -0
- package/lib/constants/abis/Vault.json +721 -0
- package/lib/constants/abis/crvUSD/DeleverageZap.json +248 -0
- package/lib/constants/abis/crvUSD/Factory.json +514 -0
- package/lib/constants/abis/crvUSD/HealthCalculatorZap.json +54 -0
- package/lib/constants/abis/crvUSD/LeverageZap.json +312 -0
- package/lib/constants/abis/crvUSD/MonetaryPolicy.json +294 -0
- package/lib/constants/abis/crvUSD/MonetaryPolicy2.json +299 -0
- package/lib/constants/abis/crvUSD/PegKeeper.json +411 -0
- package/lib/constants/abis/crvUSD/controller.json +991 -0
- package/lib/constants/abis/crvUSD/llamma.json +984 -0
- package/lib/constants/abis/gas_oracle_optimism.json +149 -0
- package/lib/constants/abis/gas_oracle_optimism_blob.json +203 -0
- package/lib/constants/aliases.d.ts +16 -0
- package/lib/constants/aliases.js +124 -0
- package/lib/constants/coins.d.ts +16 -0
- package/lib/constants/coins.js +24 -0
- package/lib/constants/llammas.d.ts +2 -0
- package/lib/constants/llammas.js +96 -0
- package/lib/constants/utils.d.ts +4 -0
- package/lib/constants/utils.js +27 -0
- package/lib/external-api.d.ts +13 -0
- package/lib/external-api.js +436 -0
- package/lib/index.d.ts +104 -0
- package/lib/index.js +123 -0
- package/lib/interfaces.d.ts +228 -0
- package/lib/interfaces.js +1 -0
- package/lib/lendMarkets/LendMarketTemplate.d.ts +510 -0
- package/lib/lendMarkets/LendMarketTemplate.js +4682 -0
- package/lib/lendMarkets/index.d.ts +3 -0
- package/lib/lendMarkets/index.js +3 -0
- package/lib/lendMarkets/lendMarketConstructor.d.ts +2 -0
- package/lib/lendMarkets/lendMarketConstructor.js +6 -0
- package/lib/llamalend.d.ts +80 -0
- package/lib/llamalend.js +878 -0
- package/lib/mintMarkets/MintMarketTemplate.d.ts +308 -0
- package/lib/mintMarkets/MintMarketTemplate.js +2998 -0
- package/lib/mintMarkets/index.d.ts +3 -0
- package/lib/mintMarkets/index.js +3 -0
- package/lib/mintMarkets/mintMarketConstructor.d.ts +2 -0
- package/lib/mintMarkets/mintMarketConstructor.js +4 -0
- package/lib/st-crvUSD.d.ts +35 -0
- package/lib/st-crvUSD.js +505 -0
- package/lib/utils.d.ts +58 -0
- package/lib/utils.js +661 -0
- package/package.json +42 -0
- package/src/cache/index.ts +41 -0
- package/src/constants/L2Networks.ts +1 -0
- package/src/constants/abis/Controller.json +1027 -0
- package/src/constants/abis/ERC20.json +222 -0
- package/src/constants/abis/ERC4626.json +1674 -0
- package/src/constants/abis/GaugeController.json +794 -0
- package/src/constants/abis/GaugeFactoryMainnet.json +1 -0
- package/src/constants/abis/GaugeFactorySidechain.json +475 -0
- package/src/constants/abis/GaugeV5.json +958 -0
- package/src/constants/abis/LeverageZap.json +35 -0
- package/src/constants/abis/Llamma.json +984 -0
- package/src/constants/abis/Minter.json +1 -0
- package/src/constants/abis/MonetaryPolicy.json +221 -0
- package/src/constants/abis/OneWayLendingFactoryABI.json +899 -0
- package/src/constants/abis/SidechainGauge.json +939 -0
- package/src/constants/abis/Vault.json +721 -0
- package/src/constants/abis/crvUSD/DeleverageZap.json +248 -0
- package/src/constants/abis/crvUSD/ERC20.json +222 -0
- package/src/constants/abis/crvUSD/Factory.json +514 -0
- package/src/constants/abis/crvUSD/HealthCalculatorZap.json +54 -0
- package/src/constants/abis/crvUSD/LeverageZap.json +312 -0
- package/src/constants/abis/crvUSD/MonetaryPolicy.json +294 -0
- package/src/constants/abis/crvUSD/MonetaryPolicy2.json +299 -0
- package/src/constants/abis/crvUSD/PegKeeper.json +411 -0
- package/src/constants/abis/crvUSD/controller.json +991 -0
- package/src/constants/abis/crvUSD/llamma.json +984 -0
- package/src/constants/abis/gas_oracle_optimism.json +149 -0
- package/src/constants/abis/gas_oracle_optimism_blob.json +203 -0
- package/src/constants/aliases.ts +141 -0
- package/src/constants/coins.ts +41 -0
- package/src/constants/llammas.ts +99 -0
- package/src/constants/utils.ts +33 -0
- package/src/external-api.ts +325 -0
- package/src/index.ts +128 -0
- package/src/interfaces.ts +237 -0
- package/src/lendMarkets/LendMarketTemplate.ts +3022 -0
- package/src/lendMarkets/index.ts +7 -0
- package/src/lendMarkets/lendMarketConstructor.ts +7 -0
- package/src/llamalend.ts +785 -0
- package/src/mintMarkets/MintMarketTemplate.ts +1781 -0
- package/src/mintMarkets/index.ts +7 -0
- package/src/mintMarkets/mintMarketConstructor.ts +5 -0
- package/src/st-crvUSD.ts +244 -0
- package/src/utils.ts +497 -0
- package/test/fetch.test.ts +152 -0
- package/test/general.test.ts +216 -0
- package/test/leverageBorrowMore.test.ts +245 -0
- package/test/leverageCreateLoan.test.ts +236 -0
- package/test/leverageRepay.test.ts +240 -0
- package/test/readme.test.ts +475 -0
- package/test/selfLiquidate.test.ts +57 -0
- package/test/selfLiquidateCrvUSD.test.ts +54 -0
- package/test/st_crvUSD.test.ts +68 -0
- package/test/swap.test.ts +62 -0
- package/test/swapCrvUSD.test.ts +56 -0
- package/test/vault.test.ts +112 -0
- package/tsconfig.build.json +10 -0
- package/tsconfig.json +72 -0
package/src/utils.ts
ADDED
|
@@ -0,0 +1,497 @@
|
|
|
1
|
+
import axios from "axios";
|
|
2
|
+
import { ethers, BigNumberish, Numeric } from "ethers";
|
|
3
|
+
import { Call } from "@curvefi/ethcall";
|
|
4
|
+
import BigNumber from 'bignumber.js';
|
|
5
|
+
import { ICurveContract, IDict, TGas } from "./interfaces.js";
|
|
6
|
+
import { _getUsdPricesFromApi } from "./external-api.js";
|
|
7
|
+
import { llamalend } from "./llamalend.js";
|
|
8
|
+
import { JsonFragment } from "ethers/lib.esm";
|
|
9
|
+
import { L2Networks } from "./constants/L2Networks.js";
|
|
10
|
+
import memoize from "memoizee";
|
|
11
|
+
|
|
12
|
+
export const MAX_ALLOWANCE = BigInt("115792089237316195423570985008687907853269984665640564039457584007913129639935"); // 2**256 - 1
|
|
13
|
+
export const MAX_ACTIVE_BAND = BigInt("57896044618658097711785492504343953926634992332820282019728792003956564819967"); // 2**255 - 1
|
|
14
|
+
|
|
15
|
+
// Common
|
|
16
|
+
|
|
17
|
+
export const createCall = (contract: ICurveContract, name: string, params: any[]): Call => {
|
|
18
|
+
const _abi = contract.abi;
|
|
19
|
+
const _name = name.split('-')[0];
|
|
20
|
+
const func = _abi.find((f: JsonFragment) => f.name === _name)
|
|
21
|
+
const inputs = func?.inputs || [];
|
|
22
|
+
const outputs = func?.outputs || [];
|
|
23
|
+
|
|
24
|
+
return {
|
|
25
|
+
contract: {
|
|
26
|
+
address: contract.address,
|
|
27
|
+
},
|
|
28
|
+
name: _name,
|
|
29
|
+
inputs,
|
|
30
|
+
outputs,
|
|
31
|
+
params,
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// Formatting numbers
|
|
36
|
+
|
|
37
|
+
export const _cutZeros = (strn: string): string => {
|
|
38
|
+
return strn.replace(/0+$/gi, '').replace(/\.$/gi, '');
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export const checkNumber = (n: number | string): number | string => {
|
|
42
|
+
if (Number(n) !== Number(n)) throw Error(`${n} is not a number`); // NaN
|
|
43
|
+
return n
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export const formatNumber = (n: number | string, decimals = 18): string => {
|
|
47
|
+
n = checkNumber(n);
|
|
48
|
+
const [integer, fractional] = String(n).split(".");
|
|
49
|
+
|
|
50
|
+
return !fractional ? integer : integer + "." + fractional.slice(0, decimals);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export const formatUnits = (value: BigNumberish, unit?: string | Numeric): string => {
|
|
54
|
+
return ethers.formatUnits(value, unit);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export const parseUnits = (n: number | string, decimals = 18): bigint => {
|
|
58
|
+
return ethers.parseUnits(formatNumber(n, decimals), decimals);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// bignumber.js
|
|
62
|
+
|
|
63
|
+
export const BN = (val: number | string): BigNumber => new BigNumber(checkNumber(val));
|
|
64
|
+
|
|
65
|
+
export const toBN = (n: bigint, decimals = 18): BigNumber => {
|
|
66
|
+
return BN(formatUnits(n, decimals));
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export const toStringFromBN = (bn: BigNumber, decimals = 18): string => {
|
|
70
|
+
return bn.toFixed(decimals);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export const fromBN = (bn: BigNumber, decimals = 18): bigint => {
|
|
74
|
+
return parseUnits(toStringFromBN(bn, decimals), decimals)
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// -----------------------------------------------------------------------------------------------
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
export const ETH_ADDRESS = "0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee";
|
|
81
|
+
export const isEth = (address: string): boolean => address.toLowerCase() === ETH_ADDRESS.toLowerCase();
|
|
82
|
+
export const getEthIndex = (addresses: string[]): number => addresses.map((address: string) => address.toLowerCase()).indexOf(ETH_ADDRESS.toLowerCase());
|
|
83
|
+
export const _mulBy1_3 = (n: bigint): bigint => n * parseUnits("130", 0) / parseUnits("100", 0);
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
export const smartNumber = (abstractNumber: bigint | bigint[]): number | number[] => {
|
|
87
|
+
if(Array.isArray(abstractNumber)) {
|
|
88
|
+
return [Number(abstractNumber[0]), Number(abstractNumber[1])];
|
|
89
|
+
} else {
|
|
90
|
+
return Number(abstractNumber);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
export const DIGas = (gas: bigint | Array<bigint>): bigint => {
|
|
95
|
+
if(Array.isArray(gas)) {
|
|
96
|
+
return gas[0];
|
|
97
|
+
} else {
|
|
98
|
+
return gas;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
export const getGasFromArray = (gas: number[]): number | number[] => {
|
|
103
|
+
if(gas[1] === 0) {
|
|
104
|
+
return gas[0];
|
|
105
|
+
} else {
|
|
106
|
+
return gas;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
export const gasSum = (gas: number[], currentGas: number | number[]): number[] => {
|
|
111
|
+
if(Array.isArray(currentGas)) {
|
|
112
|
+
gas[0] = gas[0] + currentGas[0];
|
|
113
|
+
gas[1] = gas[1] + currentGas[1];
|
|
114
|
+
} else {
|
|
115
|
+
gas[0] = gas[0] + currentGas;
|
|
116
|
+
}
|
|
117
|
+
return gas;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
export const _getAddress = (address: string): string => {
|
|
121
|
+
address = address || llamalend.signerAddress;
|
|
122
|
+
if (!address) throw Error("Need to connect wallet or pass address into args");
|
|
123
|
+
|
|
124
|
+
return address
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
export const handleMultiCallResponse = (callsMap: string[], response: any[]) => {
|
|
128
|
+
const result: Record<string, any> = {};
|
|
129
|
+
const responseLength = callsMap.length;
|
|
130
|
+
for(let i = 0; i < responseLength; i++) {
|
|
131
|
+
result[callsMap[i]] = response.filter((a, j) => j % responseLength === i) as string[];
|
|
132
|
+
}
|
|
133
|
+
return result;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// coins can be either addresses or symbols
|
|
137
|
+
export const _getCoinAddressesNoCheck = (...coins: string[] | string[][]): string[] => {
|
|
138
|
+
if (coins.length == 1 && Array.isArray(coins[0])) coins = coins[0];
|
|
139
|
+
coins = coins as string[];
|
|
140
|
+
return coins.map((c) => c.toLowerCase()).map((c) => llamalend.constants.COINS[c] || c);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
export const _getCoinAddresses = (coins: string[]): string[] => {
|
|
144
|
+
const coinAddresses = _getCoinAddressesNoCheck(coins);
|
|
145
|
+
const availableAddresses = Object.keys(llamalend.constants.DECIMALS);
|
|
146
|
+
for (const coinAddr of coinAddresses) {
|
|
147
|
+
if (!availableAddresses.includes(coinAddr)) throw Error(`Coin with address '${coinAddr}' is not available`);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
return coinAddresses
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
export const _getCoinDecimals = (coinAddresses: string[]): number[] => {
|
|
154
|
+
return coinAddresses.map((coinAddr) => llamalend.constants.DECIMALS[coinAddr.toLowerCase()] ?? 18);
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
// --- BALANCES ---
|
|
159
|
+
|
|
160
|
+
export const _getBalances = async (coinAddresses: string[], address = ""): Promise<bigint[]> => {
|
|
161
|
+
address = _getAddress(address);
|
|
162
|
+
const _coinAddresses = [...coinAddresses];
|
|
163
|
+
const ethIndex = getEthIndex(_coinAddresses);
|
|
164
|
+
if (ethIndex !== -1) {
|
|
165
|
+
_coinAddresses.splice(ethIndex, 1);
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
const contractCalls = [];
|
|
169
|
+
for (const coinAddr of _coinAddresses) {
|
|
170
|
+
contractCalls.push(llamalend.contracts[coinAddr].multicallContract.balanceOf(address));
|
|
171
|
+
}
|
|
172
|
+
const _balances: bigint[] = await llamalend.multicallProvider.all(contractCalls);
|
|
173
|
+
|
|
174
|
+
if (ethIndex !== -1) {
|
|
175
|
+
const ethBalance: bigint = await llamalend.provider.getBalance(address);
|
|
176
|
+
_balances.splice(ethIndex, 0, ethBalance);
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
return _balances
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
export const getBalances = async (coins: string[], address = ""): Promise<string[]> => {
|
|
183
|
+
const coinAddresses = _getCoinAddresses(coins);
|
|
184
|
+
const decimals = _getCoinDecimals(coinAddresses);
|
|
185
|
+
const _balances = await _getBalances(coinAddresses, address);
|
|
186
|
+
|
|
187
|
+
return _balances.map((_b, i: number ) => formatUnits(_b, decimals[i]));
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
export const _getAllowance = memoize(async (coins: string[], address: string, spender: string): Promise<bigint[]> => {
|
|
191
|
+
const _coins = [...coins]
|
|
192
|
+
const ethIndex = getEthIndex(_coins);
|
|
193
|
+
if (ethIndex !== -1) {
|
|
194
|
+
_coins.splice(ethIndex, 1);
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
let allowance: bigint[];
|
|
198
|
+
if (_coins.length === 1) {
|
|
199
|
+
allowance = [await llamalend.contracts[_coins[0]].contract.allowance(address, spender, llamalend.constantOptions)];
|
|
200
|
+
} else {
|
|
201
|
+
const contractCalls = _coins.map((coinAddr) => llamalend.contracts[coinAddr].multicallContract.allowance(address, spender));
|
|
202
|
+
allowance = await llamalend.multicallProvider.all(contractCalls);
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
if (ethIndex !== -1) {
|
|
206
|
+
allowance.splice(ethIndex, 0, MAX_ALLOWANCE);
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
return allowance;
|
|
210
|
+
},
|
|
211
|
+
{
|
|
212
|
+
promise: true,
|
|
213
|
+
maxAge: 5 * 1000, // 5s
|
|
214
|
+
primitive: true,
|
|
215
|
+
length: 3,
|
|
216
|
+
});
|
|
217
|
+
|
|
218
|
+
// coins can be either addresses or symbols
|
|
219
|
+
export const getAllowance = async (coins: string[], address: string, spender: string): Promise<string[]> => {
|
|
220
|
+
const coinAddresses = _getCoinAddresses(coins);
|
|
221
|
+
const decimals = _getCoinDecimals(coinAddresses);
|
|
222
|
+
const _allowance = await _getAllowance(coinAddresses, address, spender);
|
|
223
|
+
|
|
224
|
+
return _allowance.map((a, i) => llamalend.formatUnits(a, decimals[i]))
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
// coins can be either addresses or symbols
|
|
228
|
+
export const hasAllowance = async (coins: string[], amounts: (number | string)[], address: string, spender: string): Promise<boolean> => {
|
|
229
|
+
const coinAddresses = _getCoinAddresses(coins);
|
|
230
|
+
const decimals = _getCoinDecimals(coinAddresses);
|
|
231
|
+
const _allowance = await _getAllowance(coinAddresses, address, spender);
|
|
232
|
+
const _amounts = amounts.map((a, i) => parseUnits(a, decimals[i]));
|
|
233
|
+
|
|
234
|
+
return _allowance.map((a, i) => a >= _amounts[i]).reduce((a, b) => a && b);
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
export const _ensureAllowance = async (coins: string[], _amounts: bigint[], spender: string, isMax = true): Promise<string[]> => {
|
|
238
|
+
const address = llamalend.signerAddress;
|
|
239
|
+
const _allowance: bigint[] = await _getAllowance(coins, address, spender);
|
|
240
|
+
|
|
241
|
+
const txHashes: string[] = []
|
|
242
|
+
for (let i = 0; i < _allowance.length; i++) {
|
|
243
|
+
if (_allowance[i] < _amounts[i]) {
|
|
244
|
+
const contract = llamalend.contracts[coins[i]].contract;
|
|
245
|
+
const _approveAmount = isMax ? MAX_ALLOWANCE : _amounts[i];
|
|
246
|
+
await llamalend.updateFeeData();
|
|
247
|
+
const gasLimit = _mulBy1_3(DIGas(await contract.approve.estimateGas(spender, _approveAmount, llamalend.constantOptions)));
|
|
248
|
+
txHashes.push((await contract.approve(spender, _approveAmount, { ...llamalend.options, gasLimit })).hash);
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
return txHashes;
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
// coins can be either addresses or symbols
|
|
256
|
+
export const ensureAllowanceEstimateGas = async (coins: string[], amounts: (number | string)[], spender: string, isMax = true): Promise<TGas> => {
|
|
257
|
+
const coinAddresses = _getCoinAddresses(coins);
|
|
258
|
+
const decimals = _getCoinDecimals(coinAddresses);
|
|
259
|
+
const _amounts = amounts.map((a, i) => parseUnits(a, decimals[i]));
|
|
260
|
+
const _allowance: bigint[] = await _getAllowance(coinAddresses, llamalend.signerAddress, spender);
|
|
261
|
+
|
|
262
|
+
let gas = [0,0];
|
|
263
|
+
for (let i = 0; i < _allowance.length; i++) {
|
|
264
|
+
if (_allowance[i] < _amounts[i]) {
|
|
265
|
+
const contract = llamalend.contracts[coinAddresses[i]].contract;
|
|
266
|
+
const _approveAmount = isMax ? MAX_ALLOWANCE : _amounts[i];
|
|
267
|
+
const currentGas = smartNumber(await contract.approve.estimateGas(spender, _approveAmount, llamalend.constantOptions));
|
|
268
|
+
gas = gasSum(gas, currentGas);
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
return getGasFromArray(gas);
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
// coins can be either addresses or symbols
|
|
276
|
+
export const ensureAllowance = async (coins: string[], amounts: (number | string)[], spender: string, isMax = true): Promise<string[]> => {
|
|
277
|
+
const coinAddresses = _getCoinAddresses(coins);
|
|
278
|
+
const decimals = _getCoinDecimals(coinAddresses);
|
|
279
|
+
const _amounts = amounts.map((a, i) => parseUnits(a, decimals[i]));
|
|
280
|
+
|
|
281
|
+
return await _ensureAllowance(coinAddresses, _amounts, spender, isMax)
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
const _usdRatesCache: IDict<{ rate: number, time: number }> = {}
|
|
285
|
+
export const _getUsdRate = async (assetId: string): Promise<number> => {
|
|
286
|
+
if (llamalend.chainId === 1 && assetId.toLowerCase() === '0x8762db106b2c2a0bccb3a80d1ed41273552616e8') return 0; // RSR
|
|
287
|
+
const pricesFromApi = await _getUsdPricesFromApi();
|
|
288
|
+
if (assetId.toLowerCase() in pricesFromApi) return pricesFromApi[assetId.toLowerCase()];
|
|
289
|
+
|
|
290
|
+
if (assetId === 'USD' || (llamalend.chainId === 137 && (assetId.toLowerCase() === llamalend.constants.COINS.am3crv.toLowerCase()))) return 1
|
|
291
|
+
|
|
292
|
+
let chainName = {
|
|
293
|
+
1: 'ethereum',
|
|
294
|
+
10: 'optimistic-ethereum',
|
|
295
|
+
56: "binance-smart-chain",
|
|
296
|
+
100: 'xdai',
|
|
297
|
+
137: 'polygon-pos',
|
|
298
|
+
156: 'sonic',
|
|
299
|
+
196: 'x-layer',
|
|
300
|
+
250: 'fantom',
|
|
301
|
+
252: 'fraxtal',
|
|
302
|
+
324: 'zksync',
|
|
303
|
+
1284: 'moonbeam',
|
|
304
|
+
2222: 'kava',
|
|
305
|
+
5000: 'mantle',
|
|
306
|
+
8453: 'base',
|
|
307
|
+
42220: 'celo',
|
|
308
|
+
43114: 'avalanche',
|
|
309
|
+
42161: 'arbitrum-one',
|
|
310
|
+
1313161554: 'aurora',
|
|
311
|
+
}[llamalend.chainId];
|
|
312
|
+
|
|
313
|
+
const nativeTokenName = {
|
|
314
|
+
1: 'ethereum',
|
|
315
|
+
10: 'ethereum',
|
|
316
|
+
56: 'binancecoin',
|
|
317
|
+
100: 'xdai',
|
|
318
|
+
137: 'matic-network',
|
|
319
|
+
156: 'sonic-3',
|
|
320
|
+
196: 'okb',
|
|
321
|
+
250: 'fantom',
|
|
322
|
+
252: 'frax-ether',
|
|
323
|
+
324: 'ethereum',
|
|
324
|
+
1284: 'moonbeam',
|
|
325
|
+
2222: 'kava',
|
|
326
|
+
5000: 'mantle',
|
|
327
|
+
8453: 'ethereum',
|
|
328
|
+
42220: 'celo',
|
|
329
|
+
43114: 'avalanche-2',
|
|
330
|
+
42161: 'ethereum',
|
|
331
|
+
1313161554: 'ethereum',
|
|
332
|
+
}[llamalend.chainId] as string;
|
|
333
|
+
|
|
334
|
+
if (chainName === undefined) {
|
|
335
|
+
throw Error('curve object is not initialized')
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
assetId = {
|
|
339
|
+
'CRV': 'curve-dao-token',
|
|
340
|
+
'EUR': 'stasis-eurs',
|
|
341
|
+
'BTC': 'bitcoin',
|
|
342
|
+
'ETH': 'ethereum',
|
|
343
|
+
'LINK': 'link',
|
|
344
|
+
}[assetId.toUpperCase()] || assetId
|
|
345
|
+
assetId = isEth(assetId) ? nativeTokenName : assetId.toLowerCase();
|
|
346
|
+
|
|
347
|
+
// No EURT on Coingecko Polygon
|
|
348
|
+
if (llamalend.chainId === 137 && assetId.toLowerCase() === llamalend.constants.COINS.eurt) {
|
|
349
|
+
chainName = 'ethereum';
|
|
350
|
+
assetId = '0xC581b735A1688071A1746c968e0798D642EDE491'.toLowerCase(); // EURT Ethereum
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
// CRV
|
|
354
|
+
if (assetId.toLowerCase() === llamalend.constants.ALIASES.crv) {
|
|
355
|
+
assetId = 'curve-dao-token';
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
if ((_usdRatesCache[assetId]?.time || 0) + 600000 < Date.now()) {
|
|
359
|
+
const url = [nativeTokenName, 'ethereum', 'bitcoin', 'link', 'curve-dao-token', 'stasis-eurs'].includes(assetId.toLowerCase()) ?
|
|
360
|
+
`https://api.coingecko.com/api/v3/simple/price?ids=${assetId}&vs_currencies=usd` :
|
|
361
|
+
`https://api.coingecko.com/api/v3/simple/token_price/${chainName}?contract_addresses=${assetId}&vs_currencies=usd`
|
|
362
|
+
const response = await axios.get(url);
|
|
363
|
+
try {
|
|
364
|
+
_usdRatesCache[assetId] = {'rate': response.data[assetId]['usd'] ?? 0, 'time': Date.now()};
|
|
365
|
+
} catch (err) { // TODO pay attention!
|
|
366
|
+
_usdRatesCache[assetId] = {'rate': 0, 'time': Date.now()};
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
return _usdRatesCache[assetId]['rate']
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
export const getUsdRate = async (coin: string): Promise<number> => {
|
|
374
|
+
const [coinAddress] = _getCoinAddressesNoCheck(coin);
|
|
375
|
+
return await _getUsdRate(coinAddress);
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
export const getBaseFeeByLastBlock = async () => {
|
|
379
|
+
const provider = llamalend.provider;
|
|
380
|
+
|
|
381
|
+
try {
|
|
382
|
+
const block = await provider.getBlock('latest');
|
|
383
|
+
if(!block) {
|
|
384
|
+
return 0.01
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
return Number(block.baseFeePerGas) / (10**9);
|
|
388
|
+
} catch (error: any) {
|
|
389
|
+
throw new Error(error)
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
export const getGasPriceFromL1 = async (): Promise<number> => {
|
|
394
|
+
if(L2Networks.includes(llamalend.chainId) && llamalend.L1WeightedGasPrice) {
|
|
395
|
+
return llamalend.L1WeightedGasPrice + 1e9; // + 1 gwei
|
|
396
|
+
} else {
|
|
397
|
+
throw Error("This method exists only for L2 networks");
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
export const getGasPriceFromL2 = async (): Promise<number> => {
|
|
402
|
+
if(llamalend.chainId === 42161) {
|
|
403
|
+
try {
|
|
404
|
+
return await getBaseFeeByLastBlock()
|
|
405
|
+
} catch (e: any) {
|
|
406
|
+
throw Error(e)
|
|
407
|
+
}
|
|
408
|
+
} else {
|
|
409
|
+
throw Error("This method exists only for ARBITRUM network");
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
export const getGasInfoForL2 = async (): Promise<Record<string, number>> => {
|
|
414
|
+
if(llamalend.chainId === 42161) {
|
|
415
|
+
try {
|
|
416
|
+
const baseFee = await getBaseFeeByLastBlock()
|
|
417
|
+
|
|
418
|
+
return {
|
|
419
|
+
maxFeePerGas: Number(((baseFee * 1.1) + 0.01).toFixed(2)),
|
|
420
|
+
maxPriorityFeePerGas: 0.01,
|
|
421
|
+
}
|
|
422
|
+
} catch (e: any) {
|
|
423
|
+
throw Error(e)
|
|
424
|
+
}
|
|
425
|
+
} else {
|
|
426
|
+
throw Error("This method exists only for ARBITRUM network");
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
export const totalSupply = async (): Promise<{ total: string, minted: string, pegKeepersDebt: string }> => {
|
|
431
|
+
const calls = [];
|
|
432
|
+
for (const llammaId of llamalend.getMintMarketList()) {
|
|
433
|
+
const controllerAddress = llamalend.constants.LLAMMAS[llammaId].controller_address;
|
|
434
|
+
const controllerContract = llamalend.contracts[controllerAddress].multicallContract;
|
|
435
|
+
calls.push(controllerContract.minted(), controllerContract.redeemed());
|
|
436
|
+
}
|
|
437
|
+
for (const pegKeeper of llamalend.constants.PEG_KEEPERS) {
|
|
438
|
+
calls.push(llamalend.contracts[pegKeeper].multicallContract.debt());
|
|
439
|
+
}
|
|
440
|
+
const res: bigint[] = await llamalend.multicallProvider.all(calls);
|
|
441
|
+
|
|
442
|
+
let mintedBN = BN(0);
|
|
443
|
+
for (let i = 0; i < llamalend.getMintMarketList().length; i++) {
|
|
444
|
+
const [_minted, _redeemed] = res.splice(0, 2);
|
|
445
|
+
mintedBN = toBN(_minted).minus(toBN(_redeemed)).plus(mintedBN);
|
|
446
|
+
}
|
|
447
|
+
let pegKeepersBN = BN(0);
|
|
448
|
+
for (const _pegKeeperDebt of res) {
|
|
449
|
+
pegKeepersBN = pegKeepersBN.plus(toBN(_pegKeeperDebt));
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
return { total: mintedBN.plus(pegKeepersBN).toString(), minted: mintedBN.toString(), pegKeepersDebt: pegKeepersBN.toString() };
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
export const getLsdApy = memoize(async(name: 'wstETH' | 'sfrxETH'): Promise<{
|
|
456
|
+
apy: number,
|
|
457
|
+
baseApy: number,
|
|
458
|
+
apyMean30d: number,
|
|
459
|
+
}> => {
|
|
460
|
+
const response = await axios.get('https://yields.llama.fi/pools');
|
|
461
|
+
const {data} = response as { data: { chain: string, project: string, symbol: string, apy: number, apyBase: number, apyMean30d: number }[] };
|
|
462
|
+
|
|
463
|
+
const params = {
|
|
464
|
+
'wstETH': {
|
|
465
|
+
project: 'lido',
|
|
466
|
+
symbol: 'STETH',
|
|
467
|
+
},
|
|
468
|
+
'sfrxETH': {
|
|
469
|
+
project: 'frax-ether',
|
|
470
|
+
symbol: 'SFRXETH',
|
|
471
|
+
},
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
const result = data.find(({
|
|
475
|
+
chain,
|
|
476
|
+
project,
|
|
477
|
+
symbol,
|
|
478
|
+
}) => (
|
|
479
|
+
chain === 'Ethereum' &&
|
|
480
|
+
project === params[name].project &&
|
|
481
|
+
symbol === params[name].symbol
|
|
482
|
+
));
|
|
483
|
+
|
|
484
|
+
if(result) {
|
|
485
|
+
return {
|
|
486
|
+
apy: result.apy,
|
|
487
|
+
baseApy: result.apyBase,
|
|
488
|
+
apyMean30d: result.apyMean30d,
|
|
489
|
+
};
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
throw new Error('Pool not found')
|
|
493
|
+
},
|
|
494
|
+
{
|
|
495
|
+
promise: true,
|
|
496
|
+
maxAge: 60 * 1000, // 1m
|
|
497
|
+
});
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
import { assert } from "chai";
|
|
2
|
+
import llamalend from "../src/index.js";
|
|
3
|
+
import {OneWayMarketTemplate} from "../src/markets";
|
|
4
|
+
|
|
5
|
+
function cloneDeep<T>(obj: T): T {
|
|
6
|
+
if (obj === null || typeof obj !== 'object') {
|
|
7
|
+
return obj;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
if (typeof obj === 'function') {
|
|
11
|
+
return obj;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
if (Array.isArray(obj)) {
|
|
15
|
+
return (obj.map((item) => cloneDeep(item)) as unknown) as T;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
if (obj instanceof Date) {
|
|
19
|
+
return new Date(obj.getTime()) as unknown as T;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
if (obj instanceof Set) {
|
|
23
|
+
return new Set(Array.from(obj).map((item) => cloneDeep(item))) as unknown as T;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
if (obj instanceof Map) {
|
|
27
|
+
return new Map(Array.from(obj.entries()).map(([key, value]) => [cloneDeep(key), cloneDeep(value)])) as unknown as T;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const clonedObj: { [key: string]: any } = {};
|
|
31
|
+
Object.keys(obj).forEach((key) => {
|
|
32
|
+
clonedObj[key] = cloneDeep((obj as { [key: string]: any })[key]);
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
return clonedObj as T;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
type CompareObject = {
|
|
39
|
+
[key: string]: any;
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
const compareObjects = (
|
|
43
|
+
obj1: CompareObject,
|
|
44
|
+
obj2: CompareObject,
|
|
45
|
+
fieldsToExclude: string[] = []
|
|
46
|
+
): boolean => {
|
|
47
|
+
function deepEqual(a: any, b: any): boolean {
|
|
48
|
+
if (a === b) return true;
|
|
49
|
+
if (typeof a !== "object" || typeof b !== "object" || a === null || b === null) {
|
|
50
|
+
return false;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const keysA = Object.keys(a);
|
|
54
|
+
const keysB = Object.keys(b);
|
|
55
|
+
|
|
56
|
+
if (keysA.length !== keysB.length) {
|
|
57
|
+
return false;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
for (const key of keysA) {
|
|
61
|
+
if (!keysB.includes(key)) {
|
|
62
|
+
return false;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
if (typeof a[key] === "function" || typeof b[key] === "function") {
|
|
66
|
+
continue;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
if (!deepEqual(a[key], b[key])) {
|
|
70
|
+
return false;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
return true;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function filterFields(obj: CompareObject, fieldsToExclude: string[]): { [key: string]: any } {
|
|
77
|
+
const filteredObj: { [key: string]: any } = {};
|
|
78
|
+
for (const key in obj) {
|
|
79
|
+
if (!fieldsToExclude.includes(key) && typeof obj[key] !== "function") {
|
|
80
|
+
filteredObj[key] = obj[key];
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
return filteredObj;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
const filteredObj1 = filterFields(obj1, fieldsToExclude);
|
|
87
|
+
const filteredObj2 = filterFields(obj2, fieldsToExclude);
|
|
88
|
+
|
|
89
|
+
return deepEqual(filteredObj1, filteredObj2);
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
const matchTest = (marketAPI: OneWayMarketTemplate, marketContract: OneWayMarketTemplate, id: string) => {
|
|
96
|
+
describe(`${id} match test`, function () {
|
|
97
|
+
it("match", async function () {
|
|
98
|
+
const comparisonResult = compareObjects(marketAPI, marketContract, ['name', 'borrowed_token', 'collateral_token', 'estimateGas', 'createLoanApprove']);
|
|
99
|
+
assert.isTrue(comparisonResult, `Market data for item ${id} does not match.`);
|
|
100
|
+
});
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
describe('Fetch Markets Test', async function () {
|
|
106
|
+
this.timeout(1200000);
|
|
107
|
+
|
|
108
|
+
before(async function () {
|
|
109
|
+
await llamalend.init('JsonRpc', {}, { gasPrice: 0 });
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
it('should fetch and compare market lists with and without parameter', async function () {
|
|
113
|
+
await llamalend.oneWayfactory.fetchMarkets(true);
|
|
114
|
+
const marketsWithDefault = llamalend.oneWayfactory.getMarketList();
|
|
115
|
+
|
|
116
|
+
await llamalend.oneWayfactory.fetchMarkets(false);
|
|
117
|
+
const marketsWithFalse = llamalend.oneWayfactory.getMarketList();
|
|
118
|
+
|
|
119
|
+
assert.deepEqual(
|
|
120
|
+
marketsWithDefault,
|
|
121
|
+
marketsWithFalse,
|
|
122
|
+
'Market lists should match when fetched with and without the `false` parameter'
|
|
123
|
+
);
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
it('should compare market objects from API and Blockchain', async function () {
|
|
127
|
+
await llamalend.oneWayfactory.fetchMarkets(true);
|
|
128
|
+
const marketListAPI = await llamalend.oneWayfactory.getMarketList();
|
|
129
|
+
|
|
130
|
+
const marketsAPI: Record<string, any> = {};
|
|
131
|
+
marketListAPI.forEach((item: string) => {
|
|
132
|
+
marketsAPI[item] = cloneDeep(llamalend.getOneWayMarket(item));
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
await llamalend.oneWayfactory.fetchMarkets(false);
|
|
136
|
+
const marketListBlockchain = await llamalend.oneWayfactory.getMarketList();
|
|
137
|
+
|
|
138
|
+
const marketsBlockchain: Record<string, any> = {};
|
|
139
|
+
marketListBlockchain.forEach((item: string) => {
|
|
140
|
+
marketsBlockchain[item] = cloneDeep(llamalend.getOneWayMarket(item));
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
marketListAPI.forEach((item: string) => {
|
|
144
|
+
matchTest(marketsAPI[item], marketsBlockchain[item], item);
|
|
145
|
+
});
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
|