@gearbox-protocol/sdk 3.0.0-vfour.120 → 3.0.0-vfour.122

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.
@@ -1,54 +1,12 @@
1
1
  'use strict';
2
2
 
3
- var sdk = require('../sdk');
3
+ var sdkGov = require('@gearbox-protocol/sdk-gov');
4
4
  var viem = require('viem');
5
+ var accounts = require('viem/accounts');
6
+ var sdk = require('../sdk');
7
+ var abi = require('../sdk/abi');
5
8
 
6
- // src/dev/calcLiquidatableLTs.ts
7
- async function calcLiquidatableLTs(sdk$1, ca, factor = 9990n, logger) {
8
- const cm = sdk$1.marketRegister.findCreditManager(ca.creditManager);
9
- const market = sdk$1.marketRegister.findByCreditManager(ca.creditManager);
10
- const weightedBalances = ca.tokens.map((t) => {
11
- const { token, balance } = t;
12
- const balanceU = market.priceOracle.convertToUnderlying(token, balance);
13
- const lt = BigInt(cm.collateralTokens[token]);
14
- return {
15
- token,
16
- weightedBalance: balanceU * lt / sdk.PERCENTAGE_FACTOR,
17
- lt
18
- };
19
- }).sort((a, b) => {
20
- if (a.token === ca.underlying) return 1;
21
- if (b.token === ca.underlying) return -1;
22
- return b.weightedBalance > a.weightedBalance ? 1 : -1;
23
- });
24
- let divisor = 0n;
25
- let dividend = factor * (ca.debt + ca.accruedInterest + ca.accruedFees) / sdk.PERCENTAGE_FACTOR;
26
- for (const { token, weightedBalance } of weightedBalances) {
27
- if (token === ca.underlying) {
28
- dividend -= weightedBalance;
29
- } else {
30
- divisor += weightedBalance;
31
- }
32
- }
33
- if (divisor === 0n) {
34
- throw new Error("warning: assets have zero weighted value in underlying");
35
- }
36
- if (dividend <= 0n) {
37
- throw new Error(`warning: account balance in underlying covers debt`);
38
- }
39
- const k = sdk.WAD * dividend / divisor;
40
- const result = {};
41
- for (const { token, lt: oldLT } of weightedBalances) {
42
- if (token !== ca.underlying) {
43
- const newLT = oldLT * k / sdk.WAD;
44
- logger?.debug(
45
- `proposed ${sdk$1.tokensMeta.symbol(token)} LT change: ${oldLT} => ${newLT} `
46
- );
47
- result[token] = Number(newLT);
48
- }
49
- }
50
- return result;
51
- }
9
+ // src/dev/AccountOpener.ts
52
10
  function createAnvilClient({
53
11
  chain,
54
12
  transport
@@ -95,6 +53,309 @@ async function evmMineDetailed(client, timestamp) {
95
53
  }
96
54
  }
97
55
 
56
+ // src/dev/AccountOpener.ts
57
+ var AccountOpener = class {
58
+ #service;
59
+ #anvil;
60
+ #logger;
61
+ #borrower;
62
+ #faucet;
63
+ constructor(service, options = {}) {
64
+ this.#service = service;
65
+ this.#logger = sdk.childLogger("AccountOpener", service.sdk.logger);
66
+ this.#anvil = createAnvilClient({
67
+ chain: service.sdk.provider.chain,
68
+ transport: service.sdk.provider.transport
69
+ });
70
+ this.#faucet = options.faucet ?? service.sdk.addressProvider.getAddress("FAUCET");
71
+ }
72
+ /**
73
+ * Tries to open account with underlying only in each CM
74
+ */
75
+ async openCreditAccounts(targets) {
76
+ const borrower = await this.#prepareBorrower(targets);
77
+ for (const target of targets) {
78
+ try {
79
+ await this.#openAccount(target);
80
+ } catch (e) {
81
+ this.#logger?.error(e);
82
+ }
83
+ }
84
+ const accounts = await this.#service.getCreditAccounts({
85
+ owner: borrower.address
86
+ });
87
+ this.#logger?.info(`opened ${accounts.length} accounts`);
88
+ }
89
+ async #openAccount({
90
+ creditManager,
91
+ collateral,
92
+ leverage = 4,
93
+ slippage = 50
94
+ }) {
95
+ const borrower = await this.#getBorrower();
96
+ const cm = this.sdk.marketRegister.findCreditManager(creditManager);
97
+ const symbol = this.sdk.tokensMeta.symbol(collateral);
98
+ const logger = this.#logger?.child?.({
99
+ creditManager: cm.name,
100
+ collateral: symbol
101
+ });
102
+ const { minDebt, underlying } = cm.creditFacade;
103
+ const expectedBalances = [];
104
+ const leftoverBalances = [];
105
+ for (const t of Object.keys(cm.collateralTokens)) {
106
+ const token = t;
107
+ expectedBalances.push({
108
+ token,
109
+ balance: token === underlying ? BigInt(leverage) * minDebt : 1n
110
+ });
111
+ leftoverBalances.push({
112
+ token,
113
+ balance: 1n
114
+ });
115
+ }
116
+ logger?.debug("looking for open strategy");
117
+ const strategy = await this.sdk.router.findOpenStrategyPath({
118
+ creditManager: cm.creditManager,
119
+ expectedBalances,
120
+ leftoverBalances,
121
+ slippage,
122
+ target: collateral
123
+ });
124
+ logger?.debug("found open strategy");
125
+ const { tx, calls } = await this.#service.openCA({
126
+ creditManager: cm.creditManager.address,
127
+ averageQuota: [],
128
+ minQuota: [],
129
+ collateral: [{ token: underlying, balance: minDebt }],
130
+ debt: minDebt * BigInt(leverage - 1),
131
+ calls: strategy.calls,
132
+ ethAmount: 0n,
133
+ permits: {},
134
+ to: borrower.address,
135
+ referralCode: 0n
136
+ });
137
+ for (let i = 0; i < calls.length; i++) {
138
+ const call = calls[i];
139
+ logger?.debug(
140
+ `call #${i + 1}: ${this.sdk.parseFunctionData(call.target, call.callData)}`
141
+ );
142
+ }
143
+ logger?.debug("prepared open account transaction");
144
+ const hash = await sdk.sendRawTx(this.#anvil, {
145
+ tx,
146
+ account: borrower
147
+ });
148
+ logger?.debug(`send transaction ${hash}`);
149
+ const receipt = await this.#anvil.waitForTransactionReceipt({ hash });
150
+ if (receipt.status === "reverted") {
151
+ throw new Error(`open credit account tx ${hash} reverted`);
152
+ }
153
+ logger?.debug(
154
+ `opened credit account in ${cm.name} with collateral ${symbol}`
155
+ );
156
+ }
157
+ /**
158
+ * Creates borrower wallet,
159
+ * Sets ETH balance,
160
+ * Gets tokens from faucet,
161
+ * Approves collateral tokens to credit manager,
162
+ * Gets DEGEN_NFT,
163
+ */
164
+ async #prepareBorrower(targets) {
165
+ const borrower = await this.#getBorrower();
166
+ let claimUSD = 0n;
167
+ let degenNFTS = {};
168
+ for (const target of targets) {
169
+ const cm = this.sdk.marketRegister.findCreditManager(
170
+ target.creditManager
171
+ );
172
+ const market = this.sdk.marketRegister.findByCreditManager(
173
+ target.creditManager
174
+ );
175
+ const { minDebt, degenNFT } = cm.creditFacade;
176
+ claimUSD += market.priceOracle.convertToUSD(cm.underlying, minDebt);
177
+ if (viem.isAddress(degenNFT) && degenNFT !== sdkGov.ADDRESS_0X0) {
178
+ degenNFTS[degenNFT] = (degenNFTS[degenNFT] ?? 0) + 1;
179
+ }
180
+ for (const t of Object.keys(cm.collateralTokens)) {
181
+ await this.#approve(t, cm);
182
+ }
183
+ }
184
+ claimUSD = claimUSD * 11n / 10n;
185
+ this.#logger?.debug(`claiming ${sdkGov.formatBN(claimUSD, 8)} USD from faucet`);
186
+ let hash = await this.#anvil.writeContract({
187
+ account: borrower,
188
+ address: this.#faucet,
189
+ abi: [
190
+ {
191
+ type: "function",
192
+ inputs: [
193
+ { name: "amountUSD", internalType: "uint256", type: "uint256" }
194
+ ],
195
+ name: "claim",
196
+ outputs: [],
197
+ stateMutability: "nonpayable"
198
+ }
199
+ ],
200
+ functionName: "claim",
201
+ args: [claimUSD],
202
+ chain: this.#anvil.chain
203
+ });
204
+ let receipt = await this.#anvil.waitForTransactionReceipt({
205
+ hash
206
+ });
207
+ if (receipt.status === "reverted") {
208
+ throw new Error(
209
+ `borrower ${borrower.address} failed to claimed equivalent of ${sdkGov.formatBN(claimUSD, 8)} USD from faucet, tx: ${hash}`
210
+ );
211
+ }
212
+ this.#logger?.debug(
213
+ `borrower ${borrower.address} claimed equivalent of ${sdkGov.formatBN(claimUSD, 8)} USD from faucet, tx: ${hash}`
214
+ );
215
+ for (const [degenNFT, amount] of Object.entries(degenNFTS)) {
216
+ await this.#mintDegenNft(degenNFT, borrower.address, amount);
217
+ }
218
+ this.#logger?.debug("prepared borrower");
219
+ return borrower;
220
+ }
221
+ async #approve(token, cm) {
222
+ const borrower = await this.#getBorrower();
223
+ try {
224
+ const hash = await this.#anvil.writeContract({
225
+ account: borrower,
226
+ address: token,
227
+ abi: abi.ierc20Abi,
228
+ functionName: "approve",
229
+ args: [cm.creditManager.address, sdk.MAX_UINT256],
230
+ chain: this.#anvil.chain
231
+ });
232
+ const receipt = await this.#anvil.waitForTransactionReceipt({
233
+ hash
234
+ });
235
+ if (receipt.status === "reverted") {
236
+ this.#logger?.error(
237
+ `failed to allowed credit manager ${cm.creditManager.name} to spend ${token}, tx reverted: ${hash}`
238
+ );
239
+ } else {
240
+ this.#logger?.debug(
241
+ `allowed credit manager ${cm.creditManager.name} to spend ${token}, tx: ${hash}`
242
+ );
243
+ }
244
+ } catch (e) {
245
+ this.#logger?.error(
246
+ `failed to allowed credit manager ${cm.creditManager.name} to spend ${token}: ${e}`
247
+ );
248
+ }
249
+ }
250
+ async #mintDegenNft(degenNFT, to, amount) {
251
+ if (amount <= 0) {
252
+ return;
253
+ }
254
+ let minter;
255
+ try {
256
+ minter = await this.#anvil.readContract({
257
+ address: degenNFT,
258
+ abi: abi.iDegenNftv2Abi,
259
+ functionName: "minter"
260
+ });
261
+ } catch (e) {
262
+ this.#logger?.error(`failed to get minter of degenNFT ${degenNFT}: ${e}`);
263
+ return;
264
+ }
265
+ try {
266
+ await this.#anvil.impersonateAccount({ address: minter });
267
+ await this.#anvil.setBalance({
268
+ address: minter,
269
+ value: viem.parseEther("100")
270
+ });
271
+ const hash = await this.#anvil.writeContract({
272
+ account: minter,
273
+ address: degenNFT,
274
+ abi: abi.iDegenNftv2Abi,
275
+ functionName: "mint",
276
+ args: [to, BigInt(amount)],
277
+ chain: this.#anvil.chain
278
+ });
279
+ const receipt = await this.#anvil.waitForTransactionReceipt({
280
+ hash
281
+ });
282
+ if (receipt.status === "reverted") {
283
+ this.#logger?.error(
284
+ `failed to mint ${amount} degenNFT ${degenNFT} to borrower ${to}, tx reverted: ${hash}`
285
+ );
286
+ }
287
+ this.#logger?.debug(
288
+ `minted ${amount} degenNFT ${degenNFT} to borrower ${to}, tx: ${hash}`
289
+ );
290
+ } catch (e) {
291
+ this.#logger?.error(
292
+ `failed to mint ${amount} degenNFT ${degenNFT} to borrower ${to}: ${e}`
293
+ );
294
+ } finally {
295
+ await this.#anvil.stopImpersonatingAccount({ address: minter });
296
+ }
297
+ }
298
+ async #getBorrower() {
299
+ if (!this.#borrower) {
300
+ this.#borrower = accounts.privateKeyToAccount(accounts.generatePrivateKey());
301
+ await this.#anvil.setBalance({
302
+ address: this.#borrower.address,
303
+ value: viem.parseEther("100")
304
+ });
305
+ this.#logger?.info(`created borrower ${this.#borrower.address}`);
306
+ }
307
+ return this.#borrower;
308
+ }
309
+ get sdk() {
310
+ return this.#service.sdk;
311
+ }
312
+ };
313
+ async function calcLiquidatableLTs(sdk$1, ca, factor = 9990n, logger) {
314
+ const cm = sdk$1.marketRegister.findCreditManager(ca.creditManager);
315
+ const market = sdk$1.marketRegister.findByCreditManager(ca.creditManager);
316
+ const weightedBalances = ca.tokens.map((t) => {
317
+ const { token, balance } = t;
318
+ const balanceU = market.priceOracle.convertToUnderlying(token, balance);
319
+ const lt = BigInt(cm.collateralTokens[token]);
320
+ return {
321
+ token,
322
+ weightedBalance: balanceU * lt / sdk.PERCENTAGE_FACTOR,
323
+ lt
324
+ };
325
+ }).sort((a, b) => {
326
+ if (a.token === ca.underlying) return 1;
327
+ if (b.token === ca.underlying) return -1;
328
+ return b.weightedBalance > a.weightedBalance ? 1 : -1;
329
+ });
330
+ let divisor = 0n;
331
+ let dividend = factor * (ca.debt + ca.accruedInterest + ca.accruedFees) / sdk.PERCENTAGE_FACTOR;
332
+ for (const { token, weightedBalance } of weightedBalances) {
333
+ if (token === ca.underlying) {
334
+ dividend -= weightedBalance;
335
+ } else {
336
+ divisor += weightedBalance;
337
+ }
338
+ }
339
+ if (divisor === 0n) {
340
+ throw new Error("warning: assets have zero weighted value in underlying");
341
+ }
342
+ if (dividend <= 0n) {
343
+ throw new Error(`warning: account balance in underlying covers debt`);
344
+ }
345
+ const k = sdk.WAD * dividend / divisor;
346
+ const result = {};
347
+ for (const { token, lt: oldLT } of weightedBalances) {
348
+ if (token !== ca.underlying) {
349
+ const newLT = oldLT * k / sdk.WAD;
350
+ logger?.debug(
351
+ `proposed ${sdk$1.tokensMeta.symbol(token)} LT change: ${oldLT} => ${newLT} `
352
+ );
353
+ result[token] = Number(newLT);
354
+ }
355
+ }
356
+ return result;
357
+ }
358
+
98
359
  // src/dev/abi/v3.ts
99
360
  var iaclAbi = [
100
361
  {
@@ -1602,6 +1863,7 @@ async function setLTZero(anvil, cm, logger) {
1602
1863
  await anvil.stopImpersonatingAccount({ address: configuratorAddr });
1603
1864
  }
1604
1865
 
1866
+ exports.AccountOpener = AccountOpener;
1605
1867
  exports.anvilNodeInfo = anvilNodeInfo;
1606
1868
  exports.calcLiquidatableLTs = calcLiquidatableLTs;
1607
1869
  exports.createAnvilClient = createAnvilClient;
@@ -1,5 +1,24 @@
1
1
  import { Address, TestRpcSchema, Hex, Block, Prettify, Client, Transport, Chain, TestActions, PublicClient, WalletClient } from 'viem';
2
- import { GearboxSDK, CreditAccountData, ILogger, CreditManagerState } from '../sdk';
2
+ import { CreditAccountsService, GearboxSDK, CreditAccountData, ILogger, CreditManagerState } from '../sdk';
3
+
4
+ interface AccountOpenerOptions {
5
+ faucet?: Address;
6
+ }
7
+ interface TargetAccount {
8
+ creditManager: Address;
9
+ collateral: Address;
10
+ leverage?: number;
11
+ slippage?: number;
12
+ }
13
+ declare class AccountOpener {
14
+ #private;
15
+ constructor(service: CreditAccountsService, options?: AccountOpenerOptions);
16
+ /**
17
+ * Tries to open account with underlying only in each CM
18
+ */
19
+ openCreditAccounts(targets: TargetAccount[]): Promise<void>;
20
+ private get sdk();
21
+ }
3
22
 
4
23
  /**
5
24
  * Given credit accounts, calculates new liquidation thresholds that needs to be set to drop account health factor a bit to make it eligible for partial liquidation
@@ -85,4 +104,4 @@ declare function setLTs(anvil: AnvilClient, cm: CreditManagerState, newLTs: Reco
85
104
 
86
105
  declare function setLTZero(anvil: AnvilClient, cm: CreditManagerState, logger?: ILogger): Promise<void>;
87
106
 
88
- export { type AnvilActions, type AnvilClient, type AnvilClientConfig, type AnvilNodeInfo, type AnvilRPCSchema, anvilNodeInfo, calcLiquidatableLTs, createAnvilClient, evmMineDetailed, isAnvil, setLTZero, setLTs };
107
+ export { AccountOpener, type AccountOpenerOptions, type AnvilActions, type AnvilClient, type AnvilClientConfig, type AnvilNodeInfo, type AnvilRPCSchema, type TargetAccount, anvilNodeInfo, calcLiquidatableLTs, createAnvilClient, evmMineDetailed, isAnvil, setLTZero, setLTs };
@@ -27052,16 +27052,37 @@ var MellowLRTPriceFeedContract = class extends AbstractLPPriceFeedContract {
27052
27052
  return stack.totalValue * BigInt(1e18) / stack.totalSupply;
27053
27053
  }
27054
27054
  };
27055
-
27056
- // src/sdk/market/pricefeeds/PendleTWAPPTPriceFeed.ts
27057
27055
  var abi28 = pendleTWAPPTPriceFeedAbi;
27058
27056
  var PendleTWAPPTPriceFeed = class extends AbstractPriceFeedContract {
27057
+ market;
27058
+ sy;
27059
+ yt;
27060
+ expiry;
27061
+ twapWindow;
27062
+ priceToSy;
27059
27063
  constructor(sdk, args) {
27060
27064
  super(sdk, {
27061
27065
  ...args,
27062
27066
  name: "PendleTWAPPTPriceFeed",
27063
27067
  abi: abi28
27064
27068
  });
27069
+ const decoded = viem.decodeAbiParameters(
27070
+ [
27071
+ { type: "address", name: "market" },
27072
+ { type: "address", name: "sy" },
27073
+ { type: "address", name: "yt" },
27074
+ { type: "uint256", name: "expiry" },
27075
+ { type: "uint32", name: "twapWindow" },
27076
+ { type: "bool", name: "priceToSy" }
27077
+ ],
27078
+ args.baseParams.serializedParams
27079
+ );
27080
+ this.market = decoded[0];
27081
+ this.sy = decoded[1];
27082
+ this.yt = decoded[2];
27083
+ this.expiry = decoded[3];
27084
+ this.twapWindow = decoded[4];
27085
+ this.priceToSy = decoded[5];
27065
27086
  }
27066
27087
  };
27067
27088
 
@@ -27704,6 +27725,18 @@ var PriceOracleBaseContract = class extends BaseContract {
27704
27725
  const toScale = 10n ** BigInt(this.sdk.tokensMeta.decimals(to));
27705
27726
  return amount * fromPrice * toScale / (toPrice * fromScale);
27706
27727
  }
27728
+ /**
27729
+ * Tries to convert amount of token to USD, using latest known prices
27730
+ * @param from
27731
+ * @param to
27732
+ * @param amount
27733
+ * @param reserve
27734
+ */
27735
+ convertToUSD(from, amount, reserve = false) {
27736
+ const price = reserve ? this.reservePrices.mustGet(from) : this.mainPrices.mustGet(from);
27737
+ const scale = 10n ** BigInt(this.sdk.tokensMeta.decimals(from));
27738
+ return amount * price / scale;
27739
+ }
27707
27740
  /**
27708
27741
  * Loads new prices for this oracle from PriceFeedCompressor
27709
27742
  * Does not update price feeds, only updates prices
@@ -25312,6 +25312,12 @@ declare const abi$j: readonly [{
25312
25312
  }];
25313
25313
  type abi$j = typeof abi$j;
25314
25314
  declare class PendleTWAPPTPriceFeed extends AbstractPriceFeedContract<abi$j> {
25315
+ readonly market: Address;
25316
+ readonly sy: Address;
25317
+ readonly yt: Address;
25318
+ readonly expiry: bigint;
25319
+ readonly twapWindow: number;
25320
+ readonly priceToSy: boolean;
25315
25321
  constructor(sdk: GearboxSDK, args: PartialPriceFeedTreeNode);
25316
25322
  }
25317
25323
 
@@ -25581,6 +25587,14 @@ declare class PriceOracleBaseContract<abi extends Abi | readonly unknown[]> exte
25581
25587
  * @param reserve
25582
25588
  */
25583
25589
  convert(from: Address, to: Address, amount: bigint, reserve?: boolean): bigint;
25590
+ /**
25591
+ * Tries to convert amount of token to USD, using latest known prices
25592
+ * @param from
25593
+ * @param to
25594
+ * @param amount
25595
+ * @param reserve
25596
+ */
25597
+ convertToUSD(from: Address, amount: bigint, reserve?: boolean): bigint;
25584
25598
  /**
25585
25599
  * Loads new prices for this oracle from PriceFeedCompressor
25586
25600
  * Does not update price feeds, only updates prices
@@ -1,5 +1,24 @@
1
1
  import { Address, TestRpcSchema, Hex, Block, Prettify, Client, Transport, Chain, TestActions, PublicClient, WalletClient } from 'viem';
2
- import { GearboxSDK, CreditAccountData, ILogger, CreditManagerState } from '../sdk';
2
+ import { CreditAccountsService, GearboxSDK, CreditAccountData, ILogger, CreditManagerState } from '../sdk';
3
+
4
+ interface AccountOpenerOptions {
5
+ faucet?: Address;
6
+ }
7
+ interface TargetAccount {
8
+ creditManager: Address;
9
+ collateral: Address;
10
+ leverage?: number;
11
+ slippage?: number;
12
+ }
13
+ declare class AccountOpener {
14
+ #private;
15
+ constructor(service: CreditAccountsService, options?: AccountOpenerOptions);
16
+ /**
17
+ * Tries to open account with underlying only in each CM
18
+ */
19
+ openCreditAccounts(targets: TargetAccount[]): Promise<void>;
20
+ private get sdk();
21
+ }
3
22
 
4
23
  /**
5
24
  * Given credit accounts, calculates new liquidation thresholds that needs to be set to drop account health factor a bit to make it eligible for partial liquidation
@@ -85,4 +104,4 @@ declare function setLTs(anvil: AnvilClient, cm: CreditManagerState, newLTs: Reco
85
104
 
86
105
  declare function setLTZero(anvil: AnvilClient, cm: CreditManagerState, logger?: ILogger): Promise<void>;
87
106
 
88
- export { type AnvilActions, type AnvilClient, type AnvilClientConfig, type AnvilNodeInfo, type AnvilRPCSchema, anvilNodeInfo, calcLiquidatableLTs, createAnvilClient, evmMineDetailed, isAnvil, setLTZero, setLTs };
107
+ export { AccountOpener, type AccountOpenerOptions, type AnvilActions, type AnvilClient, type AnvilClientConfig, type AnvilNodeInfo, type AnvilRPCSchema, type TargetAccount, anvilNodeInfo, calcLiquidatableLTs, createAnvilClient, evmMineDetailed, isAnvil, setLTZero, setLTs };
@@ -1,52 +1,10 @@
1
- import { PERCENTAGE_FACTOR, WAD } from '../sdk/index.mjs';
2
- import { createTestClient, publicActions, walletActions, toHex, parseEther } from 'viem';
1
+ import { ADDRESS_0X0, formatBN } from '@gearbox-protocol/sdk-gov';
2
+ import { createTestClient, publicActions, walletActions, toHex, isAddress, parseEther } from 'viem';
3
+ import { privateKeyToAccount, generatePrivateKey } from 'viem/accounts';
4
+ import { childLogger, sendRawTx, MAX_UINT256, PERCENTAGE_FACTOR, WAD } from '../sdk/index.mjs';
5
+ import { ierc20Abi, iDegenNftv2Abi } from '../sdk/abi';
3
6
 
4
- // src/dev/calcLiquidatableLTs.ts
5
- async function calcLiquidatableLTs(sdk, ca, factor = 9990n, logger) {
6
- const cm = sdk.marketRegister.findCreditManager(ca.creditManager);
7
- const market = sdk.marketRegister.findByCreditManager(ca.creditManager);
8
- const weightedBalances = ca.tokens.map((t) => {
9
- const { token, balance } = t;
10
- const balanceU = market.priceOracle.convertToUnderlying(token, balance);
11
- const lt = BigInt(cm.collateralTokens[token]);
12
- return {
13
- token,
14
- weightedBalance: balanceU * lt / PERCENTAGE_FACTOR,
15
- lt
16
- };
17
- }).sort((a, b) => {
18
- if (a.token === ca.underlying) return 1;
19
- if (b.token === ca.underlying) return -1;
20
- return b.weightedBalance > a.weightedBalance ? 1 : -1;
21
- });
22
- let divisor = 0n;
23
- let dividend = factor * (ca.debt + ca.accruedInterest + ca.accruedFees) / PERCENTAGE_FACTOR;
24
- for (const { token, weightedBalance } of weightedBalances) {
25
- if (token === ca.underlying) {
26
- dividend -= weightedBalance;
27
- } else {
28
- divisor += weightedBalance;
29
- }
30
- }
31
- if (divisor === 0n) {
32
- throw new Error("warning: assets have zero weighted value in underlying");
33
- }
34
- if (dividend <= 0n) {
35
- throw new Error(`warning: account balance in underlying covers debt`);
36
- }
37
- const k = WAD * dividend / divisor;
38
- const result = {};
39
- for (const { token, lt: oldLT } of weightedBalances) {
40
- if (token !== ca.underlying) {
41
- const newLT = oldLT * k / WAD;
42
- logger?.debug(
43
- `proposed ${sdk.tokensMeta.symbol(token)} LT change: ${oldLT} => ${newLT} `
44
- );
45
- result[token] = Number(newLT);
46
- }
47
- }
48
- return result;
49
- }
7
+ // src/dev/AccountOpener.ts
50
8
  function createAnvilClient({
51
9
  chain,
52
10
  transport
@@ -93,6 +51,309 @@ async function evmMineDetailed(client, timestamp) {
93
51
  }
94
52
  }
95
53
 
54
+ // src/dev/AccountOpener.ts
55
+ var AccountOpener = class {
56
+ #service;
57
+ #anvil;
58
+ #logger;
59
+ #borrower;
60
+ #faucet;
61
+ constructor(service, options = {}) {
62
+ this.#service = service;
63
+ this.#logger = childLogger("AccountOpener", service.sdk.logger);
64
+ this.#anvil = createAnvilClient({
65
+ chain: service.sdk.provider.chain,
66
+ transport: service.sdk.provider.transport
67
+ });
68
+ this.#faucet = options.faucet ?? service.sdk.addressProvider.getAddress("FAUCET");
69
+ }
70
+ /**
71
+ * Tries to open account with underlying only in each CM
72
+ */
73
+ async openCreditAccounts(targets) {
74
+ const borrower = await this.#prepareBorrower(targets);
75
+ for (const target of targets) {
76
+ try {
77
+ await this.#openAccount(target);
78
+ } catch (e) {
79
+ this.#logger?.error(e);
80
+ }
81
+ }
82
+ const accounts = await this.#service.getCreditAccounts({
83
+ owner: borrower.address
84
+ });
85
+ this.#logger?.info(`opened ${accounts.length} accounts`);
86
+ }
87
+ async #openAccount({
88
+ creditManager,
89
+ collateral,
90
+ leverage = 4,
91
+ slippage = 50
92
+ }) {
93
+ const borrower = await this.#getBorrower();
94
+ const cm = this.sdk.marketRegister.findCreditManager(creditManager);
95
+ const symbol = this.sdk.tokensMeta.symbol(collateral);
96
+ const logger = this.#logger?.child?.({
97
+ creditManager: cm.name,
98
+ collateral: symbol
99
+ });
100
+ const { minDebt, underlying } = cm.creditFacade;
101
+ const expectedBalances = [];
102
+ const leftoverBalances = [];
103
+ for (const t of Object.keys(cm.collateralTokens)) {
104
+ const token = t;
105
+ expectedBalances.push({
106
+ token,
107
+ balance: token === underlying ? BigInt(leverage) * minDebt : 1n
108
+ });
109
+ leftoverBalances.push({
110
+ token,
111
+ balance: 1n
112
+ });
113
+ }
114
+ logger?.debug("looking for open strategy");
115
+ const strategy = await this.sdk.router.findOpenStrategyPath({
116
+ creditManager: cm.creditManager,
117
+ expectedBalances,
118
+ leftoverBalances,
119
+ slippage,
120
+ target: collateral
121
+ });
122
+ logger?.debug("found open strategy");
123
+ const { tx, calls } = await this.#service.openCA({
124
+ creditManager: cm.creditManager.address,
125
+ averageQuota: [],
126
+ minQuota: [],
127
+ collateral: [{ token: underlying, balance: minDebt }],
128
+ debt: minDebt * BigInt(leverage - 1),
129
+ calls: strategy.calls,
130
+ ethAmount: 0n,
131
+ permits: {},
132
+ to: borrower.address,
133
+ referralCode: 0n
134
+ });
135
+ for (let i = 0; i < calls.length; i++) {
136
+ const call = calls[i];
137
+ logger?.debug(
138
+ `call #${i + 1}: ${this.sdk.parseFunctionData(call.target, call.callData)}`
139
+ );
140
+ }
141
+ logger?.debug("prepared open account transaction");
142
+ const hash = await sendRawTx(this.#anvil, {
143
+ tx,
144
+ account: borrower
145
+ });
146
+ logger?.debug(`send transaction ${hash}`);
147
+ const receipt = await this.#anvil.waitForTransactionReceipt({ hash });
148
+ if (receipt.status === "reverted") {
149
+ throw new Error(`open credit account tx ${hash} reverted`);
150
+ }
151
+ logger?.debug(
152
+ `opened credit account in ${cm.name} with collateral ${symbol}`
153
+ );
154
+ }
155
+ /**
156
+ * Creates borrower wallet,
157
+ * Sets ETH balance,
158
+ * Gets tokens from faucet,
159
+ * Approves collateral tokens to credit manager,
160
+ * Gets DEGEN_NFT,
161
+ */
162
+ async #prepareBorrower(targets) {
163
+ const borrower = await this.#getBorrower();
164
+ let claimUSD = 0n;
165
+ let degenNFTS = {};
166
+ for (const target of targets) {
167
+ const cm = this.sdk.marketRegister.findCreditManager(
168
+ target.creditManager
169
+ );
170
+ const market = this.sdk.marketRegister.findByCreditManager(
171
+ target.creditManager
172
+ );
173
+ const { minDebt, degenNFT } = cm.creditFacade;
174
+ claimUSD += market.priceOracle.convertToUSD(cm.underlying, minDebt);
175
+ if (isAddress(degenNFT) && degenNFT !== ADDRESS_0X0) {
176
+ degenNFTS[degenNFT] = (degenNFTS[degenNFT] ?? 0) + 1;
177
+ }
178
+ for (const t of Object.keys(cm.collateralTokens)) {
179
+ await this.#approve(t, cm);
180
+ }
181
+ }
182
+ claimUSD = claimUSD * 11n / 10n;
183
+ this.#logger?.debug(`claiming ${formatBN(claimUSD, 8)} USD from faucet`);
184
+ let hash = await this.#anvil.writeContract({
185
+ account: borrower,
186
+ address: this.#faucet,
187
+ abi: [
188
+ {
189
+ type: "function",
190
+ inputs: [
191
+ { name: "amountUSD", internalType: "uint256", type: "uint256" }
192
+ ],
193
+ name: "claim",
194
+ outputs: [],
195
+ stateMutability: "nonpayable"
196
+ }
197
+ ],
198
+ functionName: "claim",
199
+ args: [claimUSD],
200
+ chain: this.#anvil.chain
201
+ });
202
+ let receipt = await this.#anvil.waitForTransactionReceipt({
203
+ hash
204
+ });
205
+ if (receipt.status === "reverted") {
206
+ throw new Error(
207
+ `borrower ${borrower.address} failed to claimed equivalent of ${formatBN(claimUSD, 8)} USD from faucet, tx: ${hash}`
208
+ );
209
+ }
210
+ this.#logger?.debug(
211
+ `borrower ${borrower.address} claimed equivalent of ${formatBN(claimUSD, 8)} USD from faucet, tx: ${hash}`
212
+ );
213
+ for (const [degenNFT, amount] of Object.entries(degenNFTS)) {
214
+ await this.#mintDegenNft(degenNFT, borrower.address, amount);
215
+ }
216
+ this.#logger?.debug("prepared borrower");
217
+ return borrower;
218
+ }
219
+ async #approve(token, cm) {
220
+ const borrower = await this.#getBorrower();
221
+ try {
222
+ const hash = await this.#anvil.writeContract({
223
+ account: borrower,
224
+ address: token,
225
+ abi: ierc20Abi,
226
+ functionName: "approve",
227
+ args: [cm.creditManager.address, MAX_UINT256],
228
+ chain: this.#anvil.chain
229
+ });
230
+ const receipt = await this.#anvil.waitForTransactionReceipt({
231
+ hash
232
+ });
233
+ if (receipt.status === "reverted") {
234
+ this.#logger?.error(
235
+ `failed to allowed credit manager ${cm.creditManager.name} to spend ${token}, tx reverted: ${hash}`
236
+ );
237
+ } else {
238
+ this.#logger?.debug(
239
+ `allowed credit manager ${cm.creditManager.name} to spend ${token}, tx: ${hash}`
240
+ );
241
+ }
242
+ } catch (e) {
243
+ this.#logger?.error(
244
+ `failed to allowed credit manager ${cm.creditManager.name} to spend ${token}: ${e}`
245
+ );
246
+ }
247
+ }
248
+ async #mintDegenNft(degenNFT, to, amount) {
249
+ if (amount <= 0) {
250
+ return;
251
+ }
252
+ let minter;
253
+ try {
254
+ minter = await this.#anvil.readContract({
255
+ address: degenNFT,
256
+ abi: iDegenNftv2Abi,
257
+ functionName: "minter"
258
+ });
259
+ } catch (e) {
260
+ this.#logger?.error(`failed to get minter of degenNFT ${degenNFT}: ${e}`);
261
+ return;
262
+ }
263
+ try {
264
+ await this.#anvil.impersonateAccount({ address: minter });
265
+ await this.#anvil.setBalance({
266
+ address: minter,
267
+ value: parseEther("100")
268
+ });
269
+ const hash = await this.#anvil.writeContract({
270
+ account: minter,
271
+ address: degenNFT,
272
+ abi: iDegenNftv2Abi,
273
+ functionName: "mint",
274
+ args: [to, BigInt(amount)],
275
+ chain: this.#anvil.chain
276
+ });
277
+ const receipt = await this.#anvil.waitForTransactionReceipt({
278
+ hash
279
+ });
280
+ if (receipt.status === "reverted") {
281
+ this.#logger?.error(
282
+ `failed to mint ${amount} degenNFT ${degenNFT} to borrower ${to}, tx reverted: ${hash}`
283
+ );
284
+ }
285
+ this.#logger?.debug(
286
+ `minted ${amount} degenNFT ${degenNFT} to borrower ${to}, tx: ${hash}`
287
+ );
288
+ } catch (e) {
289
+ this.#logger?.error(
290
+ `failed to mint ${amount} degenNFT ${degenNFT} to borrower ${to}: ${e}`
291
+ );
292
+ } finally {
293
+ await this.#anvil.stopImpersonatingAccount({ address: minter });
294
+ }
295
+ }
296
+ async #getBorrower() {
297
+ if (!this.#borrower) {
298
+ this.#borrower = privateKeyToAccount(generatePrivateKey());
299
+ await this.#anvil.setBalance({
300
+ address: this.#borrower.address,
301
+ value: parseEther("100")
302
+ });
303
+ this.#logger?.info(`created borrower ${this.#borrower.address}`);
304
+ }
305
+ return this.#borrower;
306
+ }
307
+ get sdk() {
308
+ return this.#service.sdk;
309
+ }
310
+ };
311
+ async function calcLiquidatableLTs(sdk, ca, factor = 9990n, logger) {
312
+ const cm = sdk.marketRegister.findCreditManager(ca.creditManager);
313
+ const market = sdk.marketRegister.findByCreditManager(ca.creditManager);
314
+ const weightedBalances = ca.tokens.map((t) => {
315
+ const { token, balance } = t;
316
+ const balanceU = market.priceOracle.convertToUnderlying(token, balance);
317
+ const lt = BigInt(cm.collateralTokens[token]);
318
+ return {
319
+ token,
320
+ weightedBalance: balanceU * lt / PERCENTAGE_FACTOR,
321
+ lt
322
+ };
323
+ }).sort((a, b) => {
324
+ if (a.token === ca.underlying) return 1;
325
+ if (b.token === ca.underlying) return -1;
326
+ return b.weightedBalance > a.weightedBalance ? 1 : -1;
327
+ });
328
+ let divisor = 0n;
329
+ let dividend = factor * (ca.debt + ca.accruedInterest + ca.accruedFees) / PERCENTAGE_FACTOR;
330
+ for (const { token, weightedBalance } of weightedBalances) {
331
+ if (token === ca.underlying) {
332
+ dividend -= weightedBalance;
333
+ } else {
334
+ divisor += weightedBalance;
335
+ }
336
+ }
337
+ if (divisor === 0n) {
338
+ throw new Error("warning: assets have zero weighted value in underlying");
339
+ }
340
+ if (dividend <= 0n) {
341
+ throw new Error(`warning: account balance in underlying covers debt`);
342
+ }
343
+ const k = WAD * dividend / divisor;
344
+ const result = {};
345
+ for (const { token, lt: oldLT } of weightedBalances) {
346
+ if (token !== ca.underlying) {
347
+ const newLT = oldLT * k / WAD;
348
+ logger?.debug(
349
+ `proposed ${sdk.tokensMeta.symbol(token)} LT change: ${oldLT} => ${newLT} `
350
+ );
351
+ result[token] = Number(newLT);
352
+ }
353
+ }
354
+ return result;
355
+ }
356
+
96
357
  // src/dev/abi/v3.ts
97
358
  var iaclAbi = [
98
359
  {
@@ -1600,4 +1861,4 @@ async function setLTZero(anvil, cm, logger) {
1600
1861
  await anvil.stopImpersonatingAccount({ address: configuratorAddr });
1601
1862
  }
1602
1863
 
1603
- export { anvilNodeInfo, calcLiquidatableLTs, createAnvilClient, evmMineDetailed, isAnvil, setLTZero, setLTs };
1864
+ export { AccountOpener, anvilNodeInfo, calcLiquidatableLTs, createAnvilClient, evmMineDetailed, isAnvil, setLTZero, setLTs };
@@ -25312,6 +25312,12 @@ declare const abi$j: readonly [{
25312
25312
  }];
25313
25313
  type abi$j = typeof abi$j;
25314
25314
  declare class PendleTWAPPTPriceFeed extends AbstractPriceFeedContract<abi$j> {
25315
+ readonly market: Address;
25316
+ readonly sy: Address;
25317
+ readonly yt: Address;
25318
+ readonly expiry: bigint;
25319
+ readonly twapWindow: number;
25320
+ readonly priceToSy: boolean;
25315
25321
  constructor(sdk: GearboxSDK, args: PartialPriceFeedTreeNode);
25316
25322
  }
25317
25323
 
@@ -25581,6 +25587,14 @@ declare class PriceOracleBaseContract<abi extends Abi | readonly unknown[]> exte
25581
25587
  * @param reserve
25582
25588
  */
25583
25589
  convert(from: Address, to: Address, amount: bigint, reserve?: boolean): bigint;
25590
+ /**
25591
+ * Tries to convert amount of token to USD, using latest known prices
25592
+ * @param from
25593
+ * @param to
25594
+ * @param amount
25595
+ * @param reserve
25596
+ */
25597
+ convertToUSD(from: Address, amount: bigint, reserve?: boolean): bigint;
25584
25598
  /**
25585
25599
  * Loads new prices for this oracle from PriceFeedCompressor
25586
25600
  * Does not update price feeds, only updates prices
@@ -27050,16 +27050,37 @@ var MellowLRTPriceFeedContract = class extends AbstractLPPriceFeedContract {
27050
27050
  return stack.totalValue * BigInt(1e18) / stack.totalSupply;
27051
27051
  }
27052
27052
  };
27053
-
27054
- // src/sdk/market/pricefeeds/PendleTWAPPTPriceFeed.ts
27055
27053
  var abi28 = pendleTWAPPTPriceFeedAbi;
27056
27054
  var PendleTWAPPTPriceFeed = class extends AbstractPriceFeedContract {
27055
+ market;
27056
+ sy;
27057
+ yt;
27058
+ expiry;
27059
+ twapWindow;
27060
+ priceToSy;
27057
27061
  constructor(sdk, args) {
27058
27062
  super(sdk, {
27059
27063
  ...args,
27060
27064
  name: "PendleTWAPPTPriceFeed",
27061
27065
  abi: abi28
27062
27066
  });
27067
+ const decoded = decodeAbiParameters(
27068
+ [
27069
+ { type: "address", name: "market" },
27070
+ { type: "address", name: "sy" },
27071
+ { type: "address", name: "yt" },
27072
+ { type: "uint256", name: "expiry" },
27073
+ { type: "uint32", name: "twapWindow" },
27074
+ { type: "bool", name: "priceToSy" }
27075
+ ],
27076
+ args.baseParams.serializedParams
27077
+ );
27078
+ this.market = decoded[0];
27079
+ this.sy = decoded[1];
27080
+ this.yt = decoded[2];
27081
+ this.expiry = decoded[3];
27082
+ this.twapWindow = decoded[4];
27083
+ this.priceToSy = decoded[5];
27063
27084
  }
27064
27085
  };
27065
27086
 
@@ -27702,6 +27723,18 @@ var PriceOracleBaseContract = class extends BaseContract {
27702
27723
  const toScale = 10n ** BigInt(this.sdk.tokensMeta.decimals(to));
27703
27724
  return amount * fromPrice * toScale / (toPrice * fromScale);
27704
27725
  }
27726
+ /**
27727
+ * Tries to convert amount of token to USD, using latest known prices
27728
+ * @param from
27729
+ * @param to
27730
+ * @param amount
27731
+ * @param reserve
27732
+ */
27733
+ convertToUSD(from, amount, reserve = false) {
27734
+ const price = reserve ? this.reservePrices.mustGet(from) : this.mainPrices.mustGet(from);
27735
+ const scale = 10n ** BigInt(this.sdk.tokensMeta.decimals(from));
27736
+ return amount * price / scale;
27737
+ }
27705
27738
  /**
27706
27739
  * Loads new prices for this oracle from PriceFeedCompressor
27707
27740
  * Does not update price feeds, only updates prices
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gearbox-protocol/sdk",
3
- "version": "3.0.0-vfour.120",
3
+ "version": "3.0.0-vfour.122",
4
4
  "description": "Gearbox SDK",
5
5
  "license": "MIT",
6
6
  "main": "./dist/cjs/sdk/index.cjs",
@@ -38,6 +38,7 @@
38
38
  "build": "tsup",
39
39
  "dev": "tsup --watch",
40
40
  "example": "tsx --env-file .env scripts/example.ts | pino-pretty",
41
+ "accounts": "tsx --env-file .env scripts/accounts.ts | pino-pretty",
41
42
  "test": "yarn -v",
42
43
  "prepare": "husky",
43
44
  "prettier": "prettier --write .",