@gearbox-protocol/sdk 3.0.0-vfour.31 → 3.0.0-vfour.33

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.
@@ -2067,6 +2067,239 @@ var iMarketCompressorAbi = [
2067
2067
  stateMutability: "view"
2068
2068
  }
2069
2069
  ];
2070
+ var iPriceFeedCompressorAbi = [
2071
+ {
2072
+ type: "function",
2073
+ inputs: [],
2074
+ name: "contractType",
2075
+ outputs: [{ name: "", internalType: "bytes32", type: "bytes32" }],
2076
+ stateMutability: "view"
2077
+ },
2078
+ {
2079
+ type: "function",
2080
+ inputs: [
2081
+ { name: "priceOracle", internalType: "address", type: "address" },
2082
+ { name: "tokens", internalType: "address[]", type: "address[]" }
2083
+ ],
2084
+ name: "getPriceFeeds",
2085
+ outputs: [
2086
+ {
2087
+ name: "priceFeedMap",
2088
+ internalType: "struct PriceFeedMapEntry[]",
2089
+ type: "tuple[]",
2090
+ components: [
2091
+ { name: "token", internalType: "address", type: "address" },
2092
+ { name: "reserve", internalType: "bool", type: "bool" },
2093
+ { name: "priceFeed", internalType: "address", type: "address" },
2094
+ { name: "stalenessPeriod", internalType: "uint32", type: "uint32" }
2095
+ ]
2096
+ },
2097
+ {
2098
+ name: "priceFeedTree",
2099
+ internalType: "struct PriceFeedTreeNode[]",
2100
+ type: "tuple[]",
2101
+ components: [
2102
+ {
2103
+ name: "baseParams",
2104
+ internalType: "struct BaseParams",
2105
+ type: "tuple",
2106
+ components: [
2107
+ { name: "addr", internalType: "address", type: "address" },
2108
+ { name: "version", internalType: "uint256", type: "uint256" },
2109
+ {
2110
+ name: "contractType",
2111
+ internalType: "bytes32",
2112
+ type: "bytes32"
2113
+ },
2114
+ {
2115
+ name: "serializedParams",
2116
+ internalType: "bytes",
2117
+ type: "bytes"
2118
+ }
2119
+ ]
2120
+ },
2121
+ { name: "decimals", internalType: "uint8", type: "uint8" },
2122
+ { name: "skipCheck", internalType: "bool", type: "bool" },
2123
+ { name: "updatable", internalType: "bool", type: "bool" },
2124
+ {
2125
+ name: "underlyingFeeds",
2126
+ internalType: "address[]",
2127
+ type: "address[]"
2128
+ },
2129
+ {
2130
+ name: "underlyingStalenessPeriods",
2131
+ internalType: "uint32[]",
2132
+ type: "uint32[]"
2133
+ },
2134
+ {
2135
+ name: "answer",
2136
+ internalType: "struct PriceFeedAnswer",
2137
+ type: "tuple",
2138
+ components: [
2139
+ { name: "price", internalType: "int256", type: "int256" },
2140
+ { name: "updatedAt", internalType: "uint256", type: "uint256" },
2141
+ { name: "success", internalType: "bool", type: "bool" }
2142
+ ]
2143
+ }
2144
+ ]
2145
+ }
2146
+ ],
2147
+ stateMutability: "view"
2148
+ },
2149
+ {
2150
+ type: "function",
2151
+ inputs: [{ name: "priceOracle", internalType: "address", type: "address" }],
2152
+ name: "getPriceFeeds",
2153
+ outputs: [
2154
+ {
2155
+ name: "priceFeedMap",
2156
+ internalType: "struct PriceFeedMapEntry[]",
2157
+ type: "tuple[]",
2158
+ components: [
2159
+ { name: "token", internalType: "address", type: "address" },
2160
+ { name: "reserve", internalType: "bool", type: "bool" },
2161
+ { name: "priceFeed", internalType: "address", type: "address" },
2162
+ { name: "stalenessPeriod", internalType: "uint32", type: "uint32" }
2163
+ ]
2164
+ },
2165
+ {
2166
+ name: "priceFeedTree",
2167
+ internalType: "struct PriceFeedTreeNode[]",
2168
+ type: "tuple[]",
2169
+ components: [
2170
+ {
2171
+ name: "baseParams",
2172
+ internalType: "struct BaseParams",
2173
+ type: "tuple",
2174
+ components: [
2175
+ { name: "addr", internalType: "address", type: "address" },
2176
+ { name: "version", internalType: "uint256", type: "uint256" },
2177
+ {
2178
+ name: "contractType",
2179
+ internalType: "bytes32",
2180
+ type: "bytes32"
2181
+ },
2182
+ {
2183
+ name: "serializedParams",
2184
+ internalType: "bytes",
2185
+ type: "bytes"
2186
+ }
2187
+ ]
2188
+ },
2189
+ { name: "decimals", internalType: "uint8", type: "uint8" },
2190
+ { name: "skipCheck", internalType: "bool", type: "bool" },
2191
+ { name: "updatable", internalType: "bool", type: "bool" },
2192
+ {
2193
+ name: "underlyingFeeds",
2194
+ internalType: "address[]",
2195
+ type: "address[]"
2196
+ },
2197
+ {
2198
+ name: "underlyingStalenessPeriods",
2199
+ internalType: "uint32[]",
2200
+ type: "uint32[]"
2201
+ },
2202
+ {
2203
+ name: "answer",
2204
+ internalType: "struct PriceFeedAnswer",
2205
+ type: "tuple",
2206
+ components: [
2207
+ { name: "price", internalType: "int256", type: "int256" },
2208
+ { name: "updatedAt", internalType: "uint256", type: "uint256" },
2209
+ { name: "success", internalType: "bool", type: "bool" }
2210
+ ]
2211
+ }
2212
+ ]
2213
+ }
2214
+ ],
2215
+ stateMutability: "view"
2216
+ },
2217
+ {
2218
+ type: "function",
2219
+ inputs: [
2220
+ { name: "priceFeeds", internalType: "address[]", type: "address[]" }
2221
+ ],
2222
+ name: "loadPriceFeedTree",
2223
+ outputs: [
2224
+ {
2225
+ name: "priceFeedTree",
2226
+ internalType: "struct PriceFeedTreeNode[]",
2227
+ type: "tuple[]",
2228
+ components: [
2229
+ {
2230
+ name: "baseParams",
2231
+ internalType: "struct BaseParams",
2232
+ type: "tuple",
2233
+ components: [
2234
+ { name: "addr", internalType: "address", type: "address" },
2235
+ { name: "version", internalType: "uint256", type: "uint256" },
2236
+ {
2237
+ name: "contractType",
2238
+ internalType: "bytes32",
2239
+ type: "bytes32"
2240
+ },
2241
+ {
2242
+ name: "serializedParams",
2243
+ internalType: "bytes",
2244
+ type: "bytes"
2245
+ }
2246
+ ]
2247
+ },
2248
+ { name: "decimals", internalType: "uint8", type: "uint8" },
2249
+ { name: "skipCheck", internalType: "bool", type: "bool" },
2250
+ { name: "updatable", internalType: "bool", type: "bool" },
2251
+ {
2252
+ name: "underlyingFeeds",
2253
+ internalType: "address[]",
2254
+ type: "address[]"
2255
+ },
2256
+ {
2257
+ name: "underlyingStalenessPeriods",
2258
+ internalType: "uint32[]",
2259
+ type: "uint32[]"
2260
+ },
2261
+ {
2262
+ name: "answer",
2263
+ internalType: "struct PriceFeedAnswer",
2264
+ type: "tuple",
2265
+ components: [
2266
+ { name: "price", internalType: "int256", type: "int256" },
2267
+ { name: "updatedAt", internalType: "uint256", type: "uint256" },
2268
+ { name: "success", internalType: "bool", type: "bool" }
2269
+ ]
2270
+ }
2271
+ ]
2272
+ }
2273
+ ],
2274
+ stateMutability: "view"
2275
+ },
2276
+ {
2277
+ type: "function",
2278
+ inputs: [],
2279
+ name: "version",
2280
+ outputs: [{ name: "", internalType: "uint256", type: "uint256" }],
2281
+ stateMutability: "view"
2282
+ },
2283
+ {
2284
+ type: "event",
2285
+ anonymous: false,
2286
+ inputs: [
2287
+ {
2288
+ name: "contractType",
2289
+ internalType: "bytes32",
2290
+ type: "bytes32",
2291
+ indexed: true
2292
+ },
2293
+ {
2294
+ name: "serializer",
2295
+ internalType: "address",
2296
+ type: "address",
2297
+ indexed: true
2298
+ }
2299
+ ],
2300
+ name: "SetSerializer"
2301
+ }
2302
+ ];
2070
2303
 
2071
2304
  // src/sdk/abi/oracles.ts
2072
2305
  var bptStablePriceFeedAbi = [
@@ -16745,6 +16978,7 @@ var AP_PRICE_ORACLE = "PRICE_ORACLE";
16745
16978
  var AP_ACCOUNT_FACTORY = "ACCOUNT_FACTORY";
16746
16979
  var AP_DATA_COMPRESSOR = "DATA_COMPRESSOR";
16747
16980
  var AP_MARKET_COMPRESSOR = "MARKET_COMPRESSOR";
16981
+ var AP_PRICE_FEED_COMPRESSOR = "PRICE_FEED_COMPRESSOR";
16748
16982
  var AP_CREDIT_ACCOUNT_COMPRESSOR = "CREDIT_ACCOUNT_COMPRESSOR";
16749
16983
  var AP_TREASURY = "TREASURY";
16750
16984
  var AP_GEAR_TOKEN = "GEAR_TOKEN";
@@ -17273,748 +17507,24 @@ var VotingContractStatus = /* @__PURE__ */ ((VotingContractStatus2) => {
17273
17507
  VotingContractStatus2[VotingContractStatus2["UNVOTE_ONLY"] = 2] = "UNVOTE_ONLY";
17274
17508
  return VotingContractStatus2;
17275
17509
  })(VotingContractStatus || {});
17276
- var SUPPORTED_CHAINS = [
17277
- "Mainnet",
17278
- "Arbitrum",
17279
- "Optimism",
17280
- "Base"
17281
- ];
17282
- var chains = {
17283
- Mainnet: chains$1.mainnet,
17284
- Arbitrum: chains$1.arbitrum,
17285
- Optimism: chains$1.optimism,
17286
- Base: chains$1.base
17287
- };
17288
- var Provider = class {
17289
- account;
17290
- chainId;
17291
- chain;
17292
- networkType;
17293
- publicClient;
17294
- addressLabels;
17295
- transport;
17296
- constructor(opts) {
17297
- const {
17298
- account,
17299
- chainId,
17300
- networkType,
17301
- rpcURLs,
17302
- timeout = 12e4,
17303
- retryCount
17304
- } = opts;
17305
- this.account = account;
17306
- this.chainId = chainId;
17307
- this.networkType = networkType;
17308
- this.chain = viem.defineChain({
17309
- ...chains[networkType],
17310
- id: chainId
17311
- });
17312
- const rpcs = rpcURLs.map((url) => viem.http(url, { timeout, retryCount }));
17313
- this.transport = rpcs.length ? viem.fallback(rpcs) : rpcs[0];
17314
- this.publicClient = viem.createPublicClient({
17315
- chain: this.chain,
17316
- transport: this.transport
17510
+
17511
+ // src/sdk/market/CreditConfiguratorContract.ts
17512
+ var CreditConfiguratorContract = class extends BaseContract {
17513
+ adapters = [];
17514
+ emergencyLiquidators = [];
17515
+ constructor(sdk, { creditConfigurator, creditManager }, emergencyLiquidators) {
17516
+ super(sdk, {
17517
+ ...creditConfigurator.baseParams,
17518
+ name: `CreditConfigurator(${creditManager.name})`,
17519
+ abi: creditConfiguratorV3Abi
17317
17520
  });
17318
- this.addressLabels = new AddressLabeller();
17521
+ this.emergencyLiquidators = [...emergencyLiquidators];
17319
17522
  }
17320
- };
17321
-
17322
- // src/sdk/utils/viem/detectNetwork.ts
17323
- async function detectNetwork(client) {
17324
- for (const chain of SUPPORTED_CHAINS) {
17325
- try {
17326
- await client.readContract({
17327
- abi: ierc20MetadataAbi,
17328
- address: USDC[chain],
17329
- functionName: "symbol"
17330
- });
17331
- return chain;
17332
- } catch {
17333
- }
17334
- }
17335
- throw new Error("Unsupported network");
17336
- }
17337
- async function simulateMulticall(client, parameters) {
17338
- const {
17339
- account,
17340
- allowFailure = true,
17341
- batchSize: batchSize_,
17342
- blockNumber,
17343
- blockTag,
17344
- gas,
17345
- multicallAddress: multicallAddress_,
17346
- stateOverride
17347
- } = parameters;
17348
- const contracts = parameters.contracts;
17349
- const batchSize = batchSize_ ?? (typeof client.batch?.multicall === "object" && client.batch.multicall.batchSize || 1024);
17350
- let multicallAddress = multicallAddress_;
17351
- if (!multicallAddress) {
17352
- if (!client.chain)
17353
- throw new Error(
17354
- "client chain not configured. multicallAddress is required."
17355
- );
17356
- multicallAddress = viem.getChainContractAddress({
17357
- blockNumber,
17358
- chain: client.chain,
17359
- contract: "multicall3"
17360
- });
17361
- }
17362
- const chunkedCalls = [[]];
17363
- let currentChunk = 0;
17364
- let currentChunkSize = 0;
17365
- for (const contract of contracts) {
17366
- const { abi: abi17, address, args, functionName } = contract;
17367
- try {
17368
- const callData = viem.encodeFunctionData({ abi: abi17, args, functionName });
17369
- currentChunkSize += (callData.length - 2) / 2;
17370
- if (
17371
- // Check if batching is enabled.
17372
- batchSize > 0 && // Check if the current size of the batch exceeds the size limit.
17373
- currentChunkSize > batchSize && // Check if the current chunk is not already empty.
17374
- chunkedCalls[currentChunk].length > 0
17375
- ) {
17376
- currentChunk++;
17377
- currentChunkSize = (callData.length - 2) / 2;
17378
- chunkedCalls[currentChunk] = [];
17379
- }
17380
- chunkedCalls[currentChunk] = [
17381
- ...chunkedCalls[currentChunk],
17382
- {
17383
- allowFailure: true,
17384
- callData,
17385
- target: address
17386
- }
17387
- ];
17388
- } catch (err) {
17389
- const error = viem.getContractError(err, {
17390
- abi: abi17,
17391
- address,
17392
- args,
17393
- docsPath: "/docs/contract/multicall",
17394
- functionName
17395
- });
17396
- if (!allowFailure) throw error;
17397
- chunkedCalls[currentChunk] = [
17398
- ...chunkedCalls[currentChunk],
17399
- {
17400
- allowFailure: true,
17401
- callData: "0x",
17402
- target: address
17403
- }
17404
- ];
17405
- }
17406
- }
17407
- const aggregate3Results = await Promise.allSettled(
17408
- chunkedCalls.map(
17409
- (calls) => utils.getAction(
17410
- client,
17411
- actions.simulateContract,
17412
- "simulateContract"
17413
- )({
17414
- account,
17415
- abi: viem.multicall3Abi,
17416
- address: multicallAddress,
17417
- args: [calls],
17418
- blockNumber,
17419
- blockTag,
17420
- // does not infer well that either blockNumber or blockTag must be present
17421
- functionName: "aggregate3",
17422
- stateOverride,
17423
- gas
17424
- })
17425
- )
17426
- );
17427
- const results = [];
17428
- for (let i = 0; i < aggregate3Results.length; i++) {
17429
- const result = aggregate3Results[i];
17430
- if (result.status === "rejected") {
17431
- if (!allowFailure) {
17432
- throw result.reason;
17433
- }
17434
- for (const _i of chunkedCalls[i]) {
17435
- results.push({
17436
- status: "failure",
17437
- error: result.reason,
17438
- result: void 0
17439
- });
17440
- }
17441
- continue;
17442
- }
17443
- const aggregate3Result = result.value.result;
17444
- for (let j = 0; j < aggregate3Result.length; j++) {
17445
- const { returnData, success } = aggregate3Result[j];
17446
- const { callData } = chunkedCalls[i][j];
17447
- const { abi: abi17, address, functionName, args } = contracts[results.length];
17448
- try {
17449
- if (callData === "0x") throw new viem.AbiDecodingZeroDataError();
17450
- if (!success) throw new viem.RawContractError({ data: returnData });
17451
- const result2 = viem.decodeFunctionResult({
17452
- abi: abi17,
17453
- args,
17454
- data: returnData,
17455
- functionName
17456
- });
17457
- results.push(allowFailure ? { result: result2, status: "success" } : result2);
17458
- } catch (err) {
17459
- const error = viem.getContractError(err, {
17460
- abi: abi17,
17461
- address,
17462
- args,
17463
- docsPath: "/docs/contract/multicall",
17464
- functionName
17465
- });
17466
- if (!allowFailure) throw error;
17467
- results.push({ error, result: void 0, status: "failure" });
17468
- }
17469
- }
17470
- }
17471
- if (results.length !== contracts.length)
17472
- throw new viem.BaseError("multicall results mismatch");
17473
- return results;
17474
- }
17475
-
17476
- // src/sdk/accounts/CreditAccountsService.ts
17477
- var CreditAccountsService = class extends SDKConstruct {
17478
- #compressor;
17479
- constructor(sdk) {
17480
- super(sdk);
17481
- this.#compressor = sdk.addressProvider.getLatestVersion(
17482
- AP_CREDIT_ACCOUNT_COMPRESSOR
17483
- );
17484
- }
17485
- /**
17486
- * Returns single credit account data, or undefined if it's not found
17487
- * Performs all necessary price feed updates under the hood
17488
- * @param account
17489
- * @returns
17490
- */
17491
- async getCreditAccountData(account) {
17492
- let raw;
17493
- try {
17494
- raw = await this.provider.publicClient.readContract({
17495
- abi: iCreditAccountCompressorAbi,
17496
- address: this.#compressor,
17497
- functionName: "getCreditAccountData",
17498
- args: [account]
17499
- });
17500
- } catch (e) {
17501
- return void 0;
17502
- }
17503
- if (raw.success) {
17504
- return raw;
17505
- }
17506
- const { txs: priceUpdateTxs, timestamp: _ } = await this.sdk.priceFeeds.generatePriceFeedsUpdateTxs();
17507
- const resp = await simulateMulticall(this.provider.publicClient, {
17508
- account: this.provider.account,
17509
- contracts: [
17510
- ...priceUpdateTxs.map(rawTxToMulticallPriceUpdate),
17511
- {
17512
- abi: iCreditAccountCompressorAbi,
17513
- address: this.#compressor,
17514
- functionName: "getCreditAccountData",
17515
- args: [account]
17516
- }
17517
- ],
17518
- allowFailure: false,
17519
- gas: 550000000n,
17520
- batchSize: 0
17521
- // we cannot have price updates and compressor request in different batches
17522
- });
17523
- const cad = resp.pop();
17524
- return cad;
17525
- }
17526
- /**
17527
- * Methods to get all credit accounts with some optional filtering
17528
- * Performs all necessary price feed updates under the hood
17529
- *
17530
- * TODO: do we want to expose pagination?
17531
- * TODO: do we want to expose "reverting"?
17532
- * TODO: do we want to expose MarketFilter in any way? If so, we need to check that the MarketFilter is compatibled with attached markets?
17533
- * @param args
17534
- * @returns returned credit accounts are sorted by health factor in ascending order
17535
- */
17536
- async getCreditAccounts(args) {
17537
- const {
17538
- creditManager,
17539
- includeZeroDebt = false,
17540
- maxHealthFactor = 65535,
17541
- // TODO: this will change to bigint
17542
- minHealthFactor = 0,
17543
- owner = ADDRESS_0X0
17544
- } = args ?? {};
17545
- const arg0 = creditManager ?? {
17546
- curators: [],
17547
- pools: this.pools,
17548
- underlying: ADDRESS_0X0
17549
- };
17550
- const caFilter = {
17551
- owner,
17552
- includeZeroDebt,
17553
- minHealthFactor,
17554
- maxHealthFactor
17555
- };
17556
- const { txs: priceUpdateTxs, timestamp: _ } = await this.sdk.priceFeeds.generatePriceFeedsUpdateTxs();
17557
- const allCAs = [];
17558
- for (const reverting of [false, true]) {
17559
- let offset = 0n;
17560
- do {
17561
- const [accounts, newOffset] = await this.#getCreditAccounts(
17562
- [arg0, { ...caFilter, reverting }, offset],
17563
- priceUpdateTxs
17564
- );
17565
- allCAs.push(...accounts);
17566
- offset = newOffset;
17567
- } while (offset !== 0n);
17568
- }
17569
- return allCAs.sort((a, b) => Number(a.healthFactor - b.healthFactor));
17570
- }
17571
- /**
17572
- * Generates transaction to liquidate credit account
17573
- * @param account
17574
- * @param to Address to transfer underlying left after liquidation
17575
- * @param slippage
17576
- * @returns
17577
- */
17578
- async fullyLiquidate(account, to, slippage = 50n) {
17579
- const cm = this.sdk.marketRegister.findCreditManager(account.creditManager);
17580
- const preview = await this.sdk.router.findBestClosePath(
17581
- account,
17582
- cm.creditManager,
17583
- slippage
17584
- );
17585
- const priceUpdates = await this.getPriceUpdatesForFacade(account);
17586
- const recipient = to ?? this.sdk.provider.account;
17587
- if (!recipient) {
17588
- throw new Error("liquidate account: assets recipient not specied");
17589
- }
17590
- return cm.creditFacade.createRawTx({
17591
- functionName: "liquidateCreditAccount",
17592
- args: [
17593
- account.creditAccount,
17594
- recipient,
17595
- [...priceUpdates, ...preview.calls]
17596
- ],
17597
- description: `fully liquidate ${account.creditAccount}`
17598
- });
17599
- }
17600
- /**
17601
- * Closes credit account or sets debt to zero (but keep account)
17602
- * @param operation
17603
- * @param ca
17604
- * @param assetsToKeep Tokens to withdraw from credit account
17605
- * @param to Address to withdraw underlying to
17606
- * @param slippage
17607
- * @returns
17608
- */
17609
- async closeCreditAccount(operation, ca, assetsToKeep, to, slippage = 50n) {
17610
- const cm = this.sdk.marketRegister.findCreditManager(ca.creditManager);
17611
- const recipient = to ?? this.sdk.provider.account;
17612
- if (!recipient) {
17613
- throw new Error("close account: assets recipient not specied");
17614
- }
17615
- const calls = await this.#prepareCloseCreditAccount(
17616
- ca,
17617
- cm,
17618
- assetsToKeep,
17619
- recipient,
17620
- slippage
17621
- );
17622
- return cm.creditFacade.createRawTx({
17623
- functionName: operation === "close" ? "closeCreditAccount" : "multicall",
17624
- args: [ca.creditAccount, calls],
17625
- description: `${operation} account ${ca.creditAccount}`
17626
- });
17627
- }
17628
- /**
17629
- * Internal wrapper for CreditAccountCompressor.getCreditAccounts + price updates wrapped into multicall
17630
- * @param args
17631
- * @param priceUpdateTxs
17632
- * @returns
17633
- */
17634
- async #getCreditAccounts(args, priceUpdateTxs) {
17635
- if (priceUpdateTxs?.length) {
17636
- const resp = await simulateMulticall(this.provider.publicClient, {
17637
- account: this.provider.account,
17638
- contracts: [
17639
- ...priceUpdateTxs.map(rawTxToMulticallPriceUpdate),
17640
- {
17641
- abi: iCreditAccountCompressorAbi,
17642
- address: this.#compressor,
17643
- functionName: "getCreditAccounts",
17644
- args
17645
- }
17646
- ],
17647
- allowFailure: false,
17648
- gas: 550000000n,
17649
- batchSize: 0
17650
- // we cannot have price updates and compressor request in different batches
17651
- });
17652
- const getCreditAccountsResp = resp.pop();
17653
- return getCreditAccountsResp;
17654
- }
17655
- return this.provider.publicClient.readContract({
17656
- abi: iCreditAccountCompressorAbi,
17657
- address: this.#compressor,
17658
- functionName: "getCreditAccounts",
17659
- args
17660
- });
17661
- }
17662
- /**
17663
- * Returns raw txs that are needed to update all price feeds so that all credit accounts (possibly from different markets) compute
17664
- * @param accounts
17665
- * @returns
17666
- */
17667
- async getUpdateForAccounts(accounts) {
17668
- const tokensByPool = /* @__PURE__ */ new Map();
17669
- const oracleByPool = /* @__PURE__ */ new Map();
17670
- for (const acc of accounts) {
17671
- const market = this.sdk.marketRegister.findByCreditManager(
17672
- acc.creditManager
17673
- );
17674
- const pool = market.state.pool.pool.address;
17675
- oracleByPool.set(pool, market.priceOracle);
17676
- for (const t of acc.tokens) {
17677
- if (t.balance > 10n) {
17678
- const tokens = tokensByPool.get(pool) ?? /* @__PURE__ */ new Set();
17679
- tokens.add(t.token);
17680
- tokensByPool.set(pool, tokens);
17681
- }
17682
- }
17683
- }
17684
- const priceFeeds = [];
17685
- for (const [pool, priceFeedFactory] of oracleByPool.entries()) {
17686
- const tokens = Array.from(tokensByPool.get(pool) ?? []);
17687
- priceFeeds.push(...priceFeedFactory.priceFeedsForTokens(tokens));
17688
- }
17689
- return this.sdk.priceFeeds.generatePriceFeedsUpdateTxs(priceFeeds);
17690
- }
17691
- /**
17692
- * Returns account price updates in a non-encoded format
17693
- * @param acc
17694
- * @returns
17695
- */
17696
- async getOnDemandPriceUpdates(acc) {
17697
- const market = this.sdk.marketRegister.findByCreditManager(
17698
- acc.creditManager
17699
- );
17700
- const update = await this.getUpdateForAccounts([acc]);
17701
- return market.priceOracle.onDemandPriceUpdates(update);
17702
- }
17703
- /**
17704
- * Returns price updates in format that is accepted by various credit facade methods (multicall, close/liquidate, etc...)
17705
- * @param acc
17706
- * @returns
17707
- */
17708
- async getPriceUpdatesForFacade(acc) {
17709
- const cm = this.sdk.marketRegister.findCreditManager(acc.creditManager);
17710
- const updates = await this.getOnDemandPriceUpdates(acc);
17711
- return updates.map(({ token, reserve, data }) => ({
17712
- target: cm.creditFacade.address,
17713
- callData: viem.encodeFunctionData({
17714
- abi: iCreditFacadeV3MulticallAbi,
17715
- functionName: "onDemandPriceUpdate",
17716
- args: [token, reserve, data]
17717
- })
17718
- }));
17719
- }
17720
- async #prepareCloseCreditAccount(ca, cm, assetsToKeep, to, slippage = 50n) {
17721
- const closePath = await this.sdk.router.findBestClosePath(
17722
- ca,
17723
- cm.creditManager,
17724
- slippage
17725
- );
17726
- const priceUpdates = await this.getPriceUpdatesForFacade(ca);
17727
- return [
17728
- ...priceUpdates,
17729
- ...closePath.calls,
17730
- ...this.#prepareDisableQuotas(ca),
17731
- ...this.#prepareDecreaseDebt(ca),
17732
- ...this.#prepareDisableTokens(ca),
17733
- ...assetsToKeep.map(
17734
- (t) => this.#prepareWithdrawToken(ca, t, MAX_UINT256, to)
17735
- )
17736
- ];
17737
- }
17738
- #prepareDisableQuotas(ca) {
17739
- const calls = [];
17740
- for (const { token, quota } of ca.tokens) {
17741
- if (quota > 0n) {
17742
- calls.push({
17743
- target: ca.creditFacade,
17744
- callData: viem.encodeFunctionData({
17745
- abi: iCreditFacadeV3MulticallAbi,
17746
- functionName: "updateQuota",
17747
- args: [token, MIN_INT96, 0n]
17748
- })
17749
- });
17750
- }
17751
- }
17752
- return calls;
17753
- }
17754
- #prepareDecreaseDebt(ca) {
17755
- if (ca.totalDebtUSD > 0n) {
17756
- return [
17757
- {
17758
- target: ca.creditFacade,
17759
- callData: viem.encodeFunctionData({
17760
- abi: iCreditFacadeV3MulticallAbi,
17761
- functionName: "decreaseDebt",
17762
- args: [MAX_UINT256]
17763
- })
17764
- }
17765
- ];
17766
- }
17767
- return [];
17768
- }
17769
- #prepareDisableTokens(ca) {
17770
- const calls = [];
17771
- for (const t of ca.tokens) {
17772
- if (t.token !== ca.underlying && (t.mask & ca.enabledTokensMask) !== 0n && t.quota === 0n) {
17773
- calls.push({
17774
- target: ca.creditFacade,
17775
- callData: viem.encodeFunctionData({
17776
- abi: iCreditFacadeV3MulticallAbi,
17777
- functionName: "disableToken",
17778
- args: [t.token]
17779
- })
17780
- });
17781
- }
17782
- }
17783
- return calls;
17784
- }
17785
- #prepareWithdrawToken(ca, token, amount, to) {
17786
- return {
17787
- target: ca.creditFacade,
17788
- callData: viem.encodeFunctionData({
17789
- abi: iCreditFacadeV3MulticallAbi,
17790
- functionName: "withdrawCollateral",
17791
- args: [token, amount, to]
17792
- })
17793
- };
17794
- }
17795
- /**
17796
- * Returns addresses of pools of attached markets
17797
- */
17798
- get pools() {
17799
- return this.sdk.marketRegister.poolState.map((p) => p.pool.address);
17800
- }
17801
- };
17802
- function rawTxToMulticallPriceUpdate(tx) {
17803
- const { to, callData } = tx;
17804
- const { args, functionName } = viem.decodeFunctionData({
17805
- abi: iUpdatablePriceFeedAbi,
17806
- data: callData
17807
- });
17808
- return {
17809
- abi: iUpdatablePriceFeedAbi,
17810
- address: to,
17811
- functionName,
17812
- args
17813
- };
17814
- }
17815
- var AddressProviderContractV3_1 = class extends BaseContract {
17816
- #addresses = {};
17817
- versions = {};
17818
- latest = {};
17819
- constructor(sdk, address) {
17820
- super(sdk, {
17821
- addr: address,
17822
- name: "AddressProviderV3",
17823
- abi: iAddressProviderV3_1Abi
17824
- });
17825
- }
17826
- parseFunctionParams(params) {
17827
- switch (params.functionName) {
17828
- case "setAddress": {
17829
- if (params.args.length !== 3) {
17830
- const [key2, saveVersion2] = params.args;
17831
- return [key2, `${saveVersion2}`];
17832
- }
17833
- const [key, value, saveVersion] = params.args;
17834
- return [viem.bytesToString(viem.toBytes(key)), value, `${saveVersion}`];
17835
- }
17836
- default:
17837
- return void 0;
17838
- }
17839
- }
17840
- setInternalAddress(key, address, version) {
17841
- if (!this.#addresses[key]) {
17842
- this.#addresses[key] = {};
17843
- }
17844
- this.#addresses[key][version] = address;
17845
- if (!this.latest[key] || version > this.latest[key]) {
17846
- this.latest[key] = version;
17847
- }
17848
- if (!this.versions[key]) {
17849
- this.versions[key] = /* @__PURE__ */ new Set();
17850
- }
17851
- this.versions[key].add(version);
17852
- }
17853
- getAddress(contract, version = NO_VERSION) {
17854
- if (!this.#addresses[contract]) {
17855
- throw new Error(`Address ${contract}, version: ${version} not found`);
17856
- }
17857
- const result = this.#addresses[contract][version];
17858
- if (!result) {
17859
- throw new Error(`Address ${contract}, version: ${version} not found`);
17860
- }
17861
- return result;
17862
- }
17863
- getLatestVersion(contract) {
17864
- if (!this.latest[contract]) {
17865
- throw new Error(`Latest version for ${contract} not found`);
17866
- }
17867
- this.logger?.debug(
17868
- `Latest version found for ${contract} : ${this.latest[contract]}`
17869
- );
17870
- return this.getAddress(contract, this.latest[contract]);
17871
- }
17872
- async fetchState(toBlock) {
17873
- const entries = await this.contract.read.getAllSavedContracts({
17874
- blockNumber: toBlock
17875
- });
17876
- entries.forEach((log) => {
17877
- this.setInternalAddress(log.key, log.value, Number(log.version));
17878
- });
17879
- this.version = 310;
17880
- }
17881
- get state() {
17882
- return {
17883
- ...this.contractData,
17884
- addresses: this.#addresses
17885
- };
17886
- }
17887
- parseLog(log) {
17888
- const parsedLog = viem.parseEventLogs({
17889
- abi: this.abi,
17890
- logs: [log]
17891
- })[0];
17892
- switch (parsedLog.eventName) {
17893
- case "SetAddress": {
17894
- const parsedLog2 = viem.parseEventLogs({
17895
- abi: this.abi,
17896
- eventName: "SetAddress",
17897
- logs: [log]
17898
- })[0];
17899
- const key = parsedLog2.args.key;
17900
- this.setInternalAddress(
17901
- key,
17902
- parsedLog.args.value,
17903
- Number(parsedLog2.args.version)
17904
- );
17905
- break;
17906
- }
17907
- default:
17908
- this.logger?.warn(`Unknown event: ${parsedLog.eventName}`);
17909
- break;
17910
- }
17911
- }
17912
- };
17913
- var BotListContract = class extends BaseContract {
17914
- approvedCreditManagers = /* @__PURE__ */ new Set();
17915
- constructor(sdk, address) {
17916
- super(sdk, { addr: address, name: "BotListV3", abi: botListV3Abi });
17917
- }
17918
- parseFunctionParams(params) {
17919
- switch (params.functionName) {
17920
- case "setCreditManagerApprovedStatus": {
17921
- const [creditManager, status] = params.args;
17922
- return [this.addressLabels.get(creditManager), `${status}`];
17923
- }
17924
- case "setBotSpecialPermissions": {
17925
- const [bot, creditManager, permissions] = params.args;
17926
- return [
17927
- this.addressLabels.get(bot),
17928
- this.addressLabels.get(creditManager),
17929
- botPermissionsToString(permissions)
17930
- ];
17931
- }
17932
- default:
17933
- return void 0;
17934
- }
17935
- }
17936
- async fetchState(toBlock) {
17937
- const logs = await this.provider.publicClient.getContractEvents({
17938
- address: this.address,
17939
- abi: this.abi,
17940
- fromBlock: 0n,
17941
- toBlock
17942
- });
17943
- logs.forEach((e) => this.parseLog(e));
17944
- }
17945
- parseLog(log) {
17946
- const parsedLog = viem.parseEventLogs({
17947
- abi: this.abi,
17948
- logs: [log]
17949
- })[0];
17950
- switch (parsedLog.eventName) {
17951
- case "SetCreditManagerApprovedStatus":
17952
- if (parsedLog.args.approved) {
17953
- this.approvedCreditManagers.add(parsedLog.args.creditManager);
17954
- } else {
17955
- this.approvedCreditManagers.delete(parsedLog.args.creditManager);
17956
- }
17957
- break;
17958
- case "SetBotSpecialPermissions":
17959
- this.logger?.debug(
17960
- `Bot ${parsedLog.args.bot} has been given permissions ${botPermissionsToString(
17961
- parsedLog.args.permissions
17962
- )} for credit manager ${parsedLog.args.creditManager}`
17963
- );
17964
- break;
17965
- default:
17966
- this.logger?.warn(`Unknown event: ${parsedLog.eventName}`);
17967
- break;
17968
- }
17969
- }
17970
- get state() {
17971
- return {
17972
- ...this.contractData
17973
- };
17974
- }
17975
- };
17976
-
17977
- // src/sdk/core/GearStakingV3Contract.ts
17978
- var GearStakingContract = class extends BaseContract {
17979
- constructor(sdk, address) {
17980
- super(sdk, { addr: address, name: "GearStakingV3", abi: gearStakingV3Abi });
17981
- }
17982
- parseFunctionParams(params) {
17983
- switch (params.functionName) {
17984
- case "setVotingContractStatus": {
17985
- const [address, status] = params.args;
17986
- return [this.addressLabels.get(address), VotingContractStatus[status]];
17987
- }
17988
- default:
17989
- return void 0;
17990
- }
17991
- }
17992
- get state() {
17993
- return {
17994
- ...this.contractData,
17995
- successor: ADDRESS_0X0,
17996
- migrator: ADDRESS_0X0
17997
- };
17998
- }
17999
- };
18000
-
18001
- // src/sdk/market/CreditConfiguratorContract.ts
18002
- var CreditConfiguratorContract = class extends BaseContract {
18003
- adapters = [];
18004
- emergencyLiquidators = [];
18005
- constructor(sdk, { creditConfigurator, creditManager }, emergencyLiquidators) {
18006
- super(sdk, {
18007
- ...creditConfigurator.baseParams,
18008
- name: `CreditConfigurator(${creditManager.name})`,
18009
- abi: creditConfiguratorV3Abi
18010
- });
18011
- this.emergencyLiquidators = [...emergencyLiquidators];
18012
- }
18013
- get state() {
18014
- return {
18015
- ...this.contractData,
18016
- emergencyLiquidators: this.emergencyLiquidators
18017
- };
17523
+ get state() {
17524
+ return {
17525
+ ...this.contractData,
17526
+ emergencyLiquidators: this.emergencyLiquidators
17527
+ };
18018
17528
  }
18019
17529
  parseFunctionParams(params) {
18020
17530
  switch (params.functionName) {
@@ -18666,7 +18176,206 @@ var PoolFactory = class {
18666
18176
  gauge: this.gaugeContract.state
18667
18177
  };
18668
18178
  }
18669
- };
18179
+ };
18180
+ var SUPPORTED_CHAINS = [
18181
+ "Mainnet",
18182
+ "Arbitrum",
18183
+ "Optimism",
18184
+ "Base"
18185
+ ];
18186
+ var chains = {
18187
+ Mainnet: chains$1.mainnet,
18188
+ Arbitrum: chains$1.arbitrum,
18189
+ Optimism: chains$1.optimism,
18190
+ Base: chains$1.base
18191
+ };
18192
+ var Provider = class {
18193
+ account;
18194
+ chainId;
18195
+ chain;
18196
+ networkType;
18197
+ publicClient;
18198
+ addressLabels;
18199
+ transport;
18200
+ constructor(opts) {
18201
+ const {
18202
+ account,
18203
+ chainId,
18204
+ networkType,
18205
+ rpcURLs,
18206
+ timeout = 12e4,
18207
+ retryCount
18208
+ } = opts;
18209
+ this.account = account;
18210
+ this.chainId = chainId;
18211
+ this.networkType = networkType;
18212
+ this.chain = viem.defineChain({
18213
+ ...chains[networkType],
18214
+ id: chainId
18215
+ });
18216
+ const rpcs = rpcURLs.map((url) => viem.http(url, { timeout, retryCount }));
18217
+ this.transport = rpcs.length ? viem.fallback(rpcs) : rpcs[0];
18218
+ this.publicClient = viem.createPublicClient({
18219
+ chain: this.chain,
18220
+ transport: this.transport
18221
+ });
18222
+ this.addressLabels = new AddressLabeller();
18223
+ }
18224
+ };
18225
+
18226
+ // src/sdk/utils/viem/detectNetwork.ts
18227
+ async function detectNetwork(client) {
18228
+ for (const chain of SUPPORTED_CHAINS) {
18229
+ try {
18230
+ await client.readContract({
18231
+ abi: ierc20MetadataAbi,
18232
+ address: USDC[chain],
18233
+ functionName: "symbol"
18234
+ });
18235
+ return chain;
18236
+ } catch {
18237
+ }
18238
+ }
18239
+ throw new Error("Unsupported network");
18240
+ }
18241
+ async function simulateMulticall(client, parameters) {
18242
+ const {
18243
+ account,
18244
+ allowFailure = true,
18245
+ batchSize: batchSize_,
18246
+ blockNumber,
18247
+ blockTag,
18248
+ gas,
18249
+ multicallAddress: multicallAddress_,
18250
+ stateOverride
18251
+ } = parameters;
18252
+ const contracts = parameters.contracts;
18253
+ const batchSize = batchSize_ ?? (typeof client.batch?.multicall === "object" && client.batch.multicall.batchSize || 1024);
18254
+ let multicallAddress = multicallAddress_;
18255
+ if (!multicallAddress) {
18256
+ if (!client.chain)
18257
+ throw new Error(
18258
+ "client chain not configured. multicallAddress is required."
18259
+ );
18260
+ multicallAddress = viem.getChainContractAddress({
18261
+ blockNumber,
18262
+ chain: client.chain,
18263
+ contract: "multicall3"
18264
+ });
18265
+ }
18266
+ const chunkedCalls = [[]];
18267
+ let currentChunk = 0;
18268
+ let currentChunkSize = 0;
18269
+ for (const contract of contracts) {
18270
+ const { abi: abi17, address, args, functionName } = contract;
18271
+ try {
18272
+ const callData = viem.encodeFunctionData({ abi: abi17, args, functionName });
18273
+ currentChunkSize += (callData.length - 2) / 2;
18274
+ if (
18275
+ // Check if batching is enabled.
18276
+ batchSize > 0 && // Check if the current size of the batch exceeds the size limit.
18277
+ currentChunkSize > batchSize && // Check if the current chunk is not already empty.
18278
+ chunkedCalls[currentChunk].length > 0
18279
+ ) {
18280
+ currentChunk++;
18281
+ currentChunkSize = (callData.length - 2) / 2;
18282
+ chunkedCalls[currentChunk] = [];
18283
+ }
18284
+ chunkedCalls[currentChunk] = [
18285
+ ...chunkedCalls[currentChunk],
18286
+ {
18287
+ allowFailure: true,
18288
+ callData,
18289
+ target: address
18290
+ }
18291
+ ];
18292
+ } catch (err) {
18293
+ const error = viem.getContractError(err, {
18294
+ abi: abi17,
18295
+ address,
18296
+ args,
18297
+ docsPath: "/docs/contract/multicall",
18298
+ functionName
18299
+ });
18300
+ if (!allowFailure) throw error;
18301
+ chunkedCalls[currentChunk] = [
18302
+ ...chunkedCalls[currentChunk],
18303
+ {
18304
+ allowFailure: true,
18305
+ callData: "0x",
18306
+ target: address
18307
+ }
18308
+ ];
18309
+ }
18310
+ }
18311
+ const aggregate3Results = await Promise.allSettled(
18312
+ chunkedCalls.map(
18313
+ (calls) => utils.getAction(
18314
+ client,
18315
+ actions.simulateContract,
18316
+ "simulateContract"
18317
+ )({
18318
+ account,
18319
+ abi: viem.multicall3Abi,
18320
+ address: multicallAddress,
18321
+ args: [calls],
18322
+ blockNumber,
18323
+ blockTag,
18324
+ // does not infer well that either blockNumber or blockTag must be present
18325
+ functionName: "aggregate3",
18326
+ stateOverride,
18327
+ gas
18328
+ })
18329
+ )
18330
+ );
18331
+ const results = [];
18332
+ for (let i = 0; i < aggregate3Results.length; i++) {
18333
+ const result = aggregate3Results[i];
18334
+ if (result.status === "rejected") {
18335
+ if (!allowFailure) {
18336
+ throw result.reason;
18337
+ }
18338
+ for (const _i of chunkedCalls[i]) {
18339
+ results.push({
18340
+ status: "failure",
18341
+ error: result.reason,
18342
+ result: void 0
18343
+ });
18344
+ }
18345
+ continue;
18346
+ }
18347
+ const aggregate3Result = result.value.result;
18348
+ for (let j = 0; j < aggregate3Result.length; j++) {
18349
+ const { returnData, success } = aggregate3Result[j];
18350
+ const { callData } = chunkedCalls[i][j];
18351
+ const { abi: abi17, address, functionName, args } = contracts[results.length];
18352
+ try {
18353
+ if (callData === "0x") throw new viem.AbiDecodingZeroDataError();
18354
+ if (!success) throw new viem.RawContractError({ data: returnData });
18355
+ const result2 = viem.decodeFunctionResult({
18356
+ abi: abi17,
18357
+ args,
18358
+ data: returnData,
18359
+ functionName
18360
+ });
18361
+ results.push(allowFailure ? { result: result2, status: "success" } : result2);
18362
+ } catch (err) {
18363
+ const error = viem.getContractError(err, {
18364
+ abi: abi17,
18365
+ address,
18366
+ args,
18367
+ docsPath: "/docs/contract/multicall",
18368
+ functionName
18369
+ });
18370
+ if (!allowFailure) throw error;
18371
+ results.push({ error, result: void 0, status: "failure" });
18372
+ }
18373
+ }
18374
+ }
18375
+ if (results.length !== contracts.length)
18376
+ throw new viem.BaseError("multicall results mismatch");
18377
+ return results;
18378
+ }
18670
18379
 
18671
18380
  // src/sdk/market/pricefeeds/PriceFeedRef.ts
18672
18381
  var PriceFeedRef = class {
@@ -18865,101 +18574,460 @@ var ChainlinkPriceFeedContract = class extends AbstractPriceFeedContract {
18865
18574
  get state() {
18866
18575
  return {
18867
18576
  ...this.contractData,
18868
- contractType: this.priceFeedType,
18869
- skipCheck: false,
18870
- pricefeeds: []
18577
+ contractType: this.priceFeedType,
18578
+ skipCheck: false,
18579
+ pricefeeds: []
18580
+ };
18581
+ }
18582
+ };
18583
+
18584
+ // src/sdk/market/pricefeeds/CompositePriceFeed.ts
18585
+ var CompositePriceFeedContract = class extends AbstractPriceFeedContract {
18586
+ constructor(sdk, args) {
18587
+ super(sdk, {
18588
+ ...args,
18589
+ name: "CompositePriceFeed",
18590
+ abi: compositePriceFeedAbi,
18591
+ decimals: 8
18592
+ });
18593
+ }
18594
+ get targetToBasePriceFeed() {
18595
+ return this.underlyingPriceFeeds[0];
18596
+ }
18597
+ get baseToUsdPriceFeed() {
18598
+ return this.underlyingPriceFeeds[1];
18599
+ }
18600
+ get state() {
18601
+ return {
18602
+ ...this.contractData,
18603
+ contractType: this.priceFeedType,
18604
+ pricefeeds: [
18605
+ this.targetToBasePriceFeed.state,
18606
+ this.baseToUsdPriceFeed.state
18607
+ ],
18608
+ skipCheck: true
18609
+ };
18610
+ }
18611
+ };
18612
+
18613
+ // src/sdk/market/pricefeeds/CurveCryptoPriceFeed.ts
18614
+ var CurveCryptoPriceFeedContract = class extends AbstractLPPriceFeedContract {
18615
+ constructor(sdk, args) {
18616
+ super(sdk, {
18617
+ ...args,
18618
+ name: "CurveCryptoPriceFeed",
18619
+ abi: curveCryptoLpPriceFeedAbi
18620
+ });
18621
+ }
18622
+ get state() {
18623
+ return {
18624
+ ...this.contractData,
18625
+ contractType: this.priceFeedType,
18626
+ skipCheck: true,
18627
+ pricefeeds: this.underlyingPriceFeeds.map((pf) => pf.state)
18628
+ };
18629
+ }
18630
+ async getValue() {
18631
+ return await this.sdk.provider.publicClient.readContract({
18632
+ abi: iCurvePoolAbi,
18633
+ address: this.lpContract,
18634
+ functionName: "get_virtual_price"
18635
+ });
18636
+ }
18637
+ };
18638
+
18639
+ // src/sdk/market/pricefeeds/CurveStablePriceFeed.ts
18640
+ var CurveStablePriceFeedContract = class extends AbstractLPPriceFeedContract {
18641
+ constructor(sdk, args) {
18642
+ super(sdk, {
18643
+ ...args,
18644
+ name: "CurveStablePriceFeed",
18645
+ abi: curveStableLpPriceFeedAbi
18646
+ });
18647
+ }
18648
+ get state() {
18649
+ return {
18650
+ ...this.contractData,
18651
+ contractType: this.priceFeedType,
18652
+ skipCheck: true,
18653
+ pricefeeds: this.underlyingPriceFeeds.map((pf) => pf.state)
18654
+ };
18655
+ }
18656
+ async getValue() {
18657
+ return await this.sdk.provider.publicClient.readContract({
18658
+ abi: iCurvePoolAbi,
18659
+ address: this.lpContract,
18660
+ functionName: "get_virtual_price"
18661
+ });
18662
+ }
18663
+ };
18664
+
18665
+ // src/sdk/market/pricefeeds/CurveUSDPriceFeed.ts
18666
+ var CurveUSDPriceFeedContract = class extends AbstractLPPriceFeedContract {
18667
+ constructor(sdk, args) {
18668
+ super(sdk, {
18669
+ ...args,
18670
+ name: "CurveUSDPriceFeed",
18671
+ abi: curveUsdPriceFeedAbi
18672
+ });
18673
+ }
18674
+ get state() {
18675
+ return {
18676
+ ...this.contractData,
18677
+ contractType: this.priceFeedType,
18678
+ skipCheck: true,
18679
+ pricefeeds: [this.underlyingPriceFeeds[0].state]
18680
+ };
18681
+ }
18682
+ async getValue() {
18683
+ return await this.sdk.provider.publicClient.readContract({
18684
+ abi: iCurvePoolAbi,
18685
+ address: this.lpContract,
18686
+ functionName: "get_virtual_price"
18687
+ });
18688
+ }
18689
+ };
18690
+ var Erc4626PriceFeedContract = class extends AbstractLPPriceFeedContract {
18691
+ constructor(sdk, args) {
18692
+ super(sdk, {
18693
+ ...args,
18694
+ name: "ERC4626PriceFeed",
18695
+ abi: erc4626PriceFeedAbi
18696
+ });
18697
+ }
18698
+ get state() {
18699
+ return {
18700
+ ...this.contractData,
18701
+ contractType: this.priceFeedType,
18702
+ skipCheck: true,
18703
+ pricefeeds: [this.underlyingPriceFeeds[0].state]
18704
+ };
18705
+ }
18706
+ async getValue() {
18707
+ const decimals2 = await this.sdk.provider.publicClient.readContract({
18708
+ abi: erc20Abi,
18709
+ address: this.lpContract,
18710
+ functionName: "decimals"
18711
+ });
18712
+ const price = await this.sdk.provider.publicClient.readContract({
18713
+ abi: viem.erc4626Abi,
18714
+ address: this.lpContract,
18715
+ functionName: "convertToAssets",
18716
+ args: [10n ** BigInt(decimals2)]
18717
+ });
18718
+ return price;
18719
+ }
18720
+ };
18721
+
18722
+ // src/sdk/market/pricefeeds/MellowLRTPriceFeed.ts
18723
+ var MellowLRTPriceFeedContract = class extends AbstractLPPriceFeedContract {
18724
+ constructor(sdk, args) {
18725
+ super(sdk, {
18726
+ ...args,
18727
+ name: "MellowLRTPriceFeed",
18728
+ abi: mellowLrtPriceFeedAbi
18729
+ });
18730
+ }
18731
+ get state() {
18732
+ return {
18733
+ ...this.contractData,
18734
+ contractType: this.priceFeedType,
18735
+ skipCheck: true,
18736
+ pricefeeds: [this.underlyingPriceFeeds[0].state]
18737
+ };
18738
+ }
18739
+ async getValue() {
18740
+ const stack = await this.sdk.provider.publicClient.readContract({
18741
+ abi: iMellowVaultAbi,
18742
+ address: this.lpContract,
18743
+ functionName: "calculateStack"
18744
+ });
18745
+ return stack.totalValue * BigInt(1e18) / stack.totalSupply;
18746
+ }
18747
+ };
18748
+
18749
+ // src/sdk/utils/internal/Hooks.ts
18750
+ var Hooks = class {
18751
+ #reg = {};
18752
+ addHook(hookName, fn) {
18753
+ if (!this.#reg[hookName]) {
18754
+ this.#reg[hookName] = [];
18755
+ }
18756
+ this.#reg[hookName].push(fn);
18757
+ }
18758
+ removeHook(hookName, fn) {
18759
+ if (!this.#reg[hookName]) {
18760
+ return;
18761
+ }
18762
+ this.#reg[hookName] = this.#reg[hookName]?.filter((hookFn) => hookFn !== fn) ?? [];
18763
+ }
18764
+ async triggerHooks(hookName, ...args) {
18765
+ if (!this.#reg[hookName]) {
18766
+ return;
18767
+ }
18768
+ for (const fn of this.#reg[hookName]) {
18769
+ await fn(...args);
18770
+ }
18771
+ }
18772
+ };
18773
+ var RedstonePriceFeedContract = class extends AbstractPriceFeedContract {
18774
+ decimals = 8;
18775
+ dataServiceId;
18776
+ dataId;
18777
+ signers;
18778
+ signersThreshold;
18779
+ constructor(sdk, args) {
18780
+ super(sdk, {
18781
+ ...args,
18782
+ name: `RedstonePriceFeed`,
18783
+ abi: redstonePriceFeedAbi
18784
+ });
18785
+ const decoder = viem.decodeAbiParameters(
18786
+ [
18787
+ { type: "address" },
18788
+ // [0]: pf.token(),
18789
+ { type: "bytes32" },
18790
+ // [1]: pf.dataFeedId(),
18791
+ { type: "address" },
18792
+ // [2]: pf.signerAddress0(),
18793
+ { type: "address" },
18794
+ // [3]: pf.signerAddress1(),
18795
+ { type: "address" },
18796
+ // [4]: pf.signerAddress2(),
18797
+ { type: "address" },
18798
+ // [5]: pf.signerAddress3(),
18799
+ { type: "address" },
18800
+ // [6]: pf.signerAddress4(),
18801
+ { type: "address" },
18802
+ // [7]: pf.signerAddress5()
18803
+ { type: "address" },
18804
+ // [8]: pf.signerAddress6(),
18805
+ { type: "address" },
18806
+ // [9]: pf.signerAddress7(),
18807
+ { type: "address" },
18808
+ // [10]: pf.signerAddress8(),
18809
+ { type: "address" },
18810
+ // [11]: pf.signerAddress9(),
18811
+ { type: "uint8" },
18812
+ // [12]: pf.getUniqueSignersThreshold()
18813
+ { type: "uint128" },
18814
+ // [13]: pf.lastPrice(),
18815
+ { type: "uint40" }
18816
+ // [14]: pf.lastPayloadTimestamp()
18817
+ ],
18818
+ args.baseParams.serializedParams
18819
+ );
18820
+ this.dataId = viem.bytesToString(viem.toBytes(decoder[1])).replaceAll("\0", "");
18821
+ this.signers = decoder.slice(2, 11);
18822
+ this.signersThreshold = Number(decoder[12]);
18823
+ this.dataServiceId = ["GMX", "BAL"].includes(this.dataId) ? "redstone-arbitrum-prod" : "redstone-primary-prod";
18824
+ }
18825
+ get state() {
18826
+ return {
18827
+ ...this.contractData,
18828
+ contractType: "PF_REDSTONE_ORACLE",
18829
+ dataId: this.dataId,
18830
+ signers: this.signers,
18831
+ signersThreshold: this.signersThreshold,
18832
+ skipCheck: true
18871
18833
  };
18872
18834
  }
18873
18835
  };
18874
-
18875
- // src/sdk/market/pricefeeds/CompositePriceFeed.ts
18876
- var CompositePriceFeedContract = class extends AbstractPriceFeedContract {
18877
- constructor(sdk, args) {
18878
- super(sdk, {
18879
- ...args,
18880
- name: "CompositePriceFeed",
18881
- abi: compositePriceFeedAbi,
18882
- decimals: 8
18883
- });
18836
+ var RedstoneUpdater = class extends SDKConstruct {
18837
+ #logger;
18838
+ #cache = /* @__PURE__ */ new Map();
18839
+ #historicalTimestamp;
18840
+ constructor(sdk) {
18841
+ super(sdk);
18842
+ this.#logger = childLogger("RedstoneUpdater", sdk.logger);
18884
18843
  }
18885
- get targetToBasePriceFeed() {
18886
- return this.underlyingPriceFeeds[0];
18844
+ setHistoricalTimestamp(timestamp) {
18845
+ this.#historicalTimestamp = timestamp;
18887
18846
  }
18888
- get baseToUsdPriceFeed() {
18889
- return this.underlyingPriceFeeds[1];
18847
+ async getUpdateTxs(feeds) {
18848
+ this.#logger?.debug(
18849
+ `generating update transactions for ${feeds.length} redstone price feeds`
18850
+ );
18851
+ const groupedFeeds = {};
18852
+ const priceFeeds = /* @__PURE__ */ new Map();
18853
+ for (const feed of feeds) {
18854
+ const key = `${feed.dataServiceId}:${feed.signersThreshold}`;
18855
+ if (!groupedFeeds[key]) {
18856
+ groupedFeeds[key] = /* @__PURE__ */ new Set();
18857
+ }
18858
+ groupedFeeds[key].add(feed.dataId);
18859
+ priceFeeds.set(feed.dataId, feed);
18860
+ }
18861
+ const results = [];
18862
+ for (const [key, group] of Object.entries(groupedFeeds)) {
18863
+ const [dataServiceId, signersStr] = key.split(":");
18864
+ const uniqueSignersCount = parseInt(signersStr, 10);
18865
+ this.#logger?.debug(
18866
+ `fetching redstone payloads for ${group.size} data feeds in ${dataServiceId} with ${uniqueSignersCount} signers: ${Array.from(group).join(", ")}`
18867
+ );
18868
+ const payloads = await this.#getPayloads(
18869
+ dataServiceId,
18870
+ group,
18871
+ uniqueSignersCount
18872
+ );
18873
+ for (const { dataFeedId, data, timestamp } of payloads) {
18874
+ const priceFeed = priceFeeds.get(dataFeedId);
18875
+ if (!priceFeed) {
18876
+ throw new Error(`cannot get price feed address for ${dataFeedId}`);
18877
+ }
18878
+ results.push({
18879
+ priceFeed: priceFeed.address.toLowerCase(),
18880
+ tx: priceFeed.createRawTx({
18881
+ functionName: "updatePrice",
18882
+ args: [data],
18883
+ description: `updating price for ${dataFeedId} [${priceFeed.address}]`
18884
+ }),
18885
+ timestamp
18886
+ });
18887
+ }
18888
+ }
18889
+ this.#logger?.debug(
18890
+ `generated ${results.length} update transactions for redstone price feeds`
18891
+ );
18892
+ return results;
18890
18893
  }
18891
- get state() {
18892
- return {
18893
- ...this.contractData,
18894
- contractType: this.priceFeedType,
18895
- pricefeeds: [
18896
- this.targetToBasePriceFeed.state,
18897
- this.baseToUsdPriceFeed.state
18898
- ],
18899
- skipCheck: true
18900
- };
18894
+ /**
18895
+ * Gets redstone payloads in one request for multiple feeds with the same dataServiceId and uniqueSignersCount
18896
+ * If historicalTimestamp is set, responses will be cached
18897
+ * @param dataServiceId
18898
+ * @param dataFeedsIds
18899
+ * @param uniqueSignersCount
18900
+ * @returns
18901
+ */
18902
+ async #getPayloads(dataServiceId, dataFeedsIds, uniqueSignersCount) {
18903
+ const fromCache = [];
18904
+ const uncached = [];
18905
+ for (const dataFeedId of dataFeedsIds) {
18906
+ const key = cacheKey(
18907
+ dataServiceId,
18908
+ dataFeedId,
18909
+ uniqueSignersCount,
18910
+ this.#historicalTimestamp
18911
+ );
18912
+ const cached = this.#cache.get(key);
18913
+ if (this.#historicalTimestamp && !!cached) {
18914
+ fromCache.push(cached);
18915
+ } else {
18916
+ uncached.push(dataFeedId);
18917
+ }
18918
+ }
18919
+ const fromRedstone = await this.#fetchPayloads(
18920
+ dataServiceId,
18921
+ new Set(uncached),
18922
+ uniqueSignersCount
18923
+ );
18924
+ if (this.#historicalTimestamp) {
18925
+ for (const resp of fromRedstone) {
18926
+ const key = cacheKey(
18927
+ dataServiceId,
18928
+ resp.dataFeedId,
18929
+ uniqueSignersCount,
18930
+ this.#historicalTimestamp
18931
+ );
18932
+ this.#cache.set(key, resp);
18933
+ }
18934
+ }
18935
+ return [...fromCache, ...fromRedstone];
18901
18936
  }
18902
- };
18903
-
18904
- // src/sdk/market/pricefeeds/CurveCryptoPriceFeed.ts
18905
- var CurveCryptoPriceFeedContract = class extends AbstractLPPriceFeedContract {
18906
- constructor(sdk, args) {
18907
- super(sdk, {
18908
- ...args,
18909
- name: "CurveCryptoPriceFeed",
18910
- abi: curveCryptoLpPriceFeedAbi
18937
+ /**
18938
+ * Fetches redstone payloads in one request for multiple feeds with the same dataServiceId and uniqueSignersCount
18939
+ * Payloads are loaded in one request to avoid redstone rate limit
18940
+ * @param dataServiceId
18941
+ * @param dataFeedsIds
18942
+ * @param uniqueSignersCount
18943
+ * @returns
18944
+ */
18945
+ async #fetchPayloads(dataServiceId, dataFeedsIds, uniqueSignersCount) {
18946
+ if (dataFeedsIds.size === 0) {
18947
+ return [];
18948
+ }
18949
+ const dataPackagesIds = Array.from(dataFeedsIds);
18950
+ const wrapper = new evmConnector.DataServiceWrapper({
18951
+ dataServiceId,
18952
+ dataPackagesIds,
18953
+ uniqueSignersCount,
18954
+ historicalTimestamp: this.#historicalTimestamp
18911
18955
  });
18912
- }
18913
- get state() {
18914
- return {
18915
- ...this.contractData,
18916
- contractType: this.priceFeedType,
18917
- skipCheck: true,
18918
- pricefeeds: this.underlyingPriceFeeds.map((pf) => pf.state)
18919
- };
18920
- }
18921
- async getValue() {
18922
- return await this.sdk.provider.publicClient.readContract({
18923
- abi: iCurvePoolAbi,
18924
- address: this.lpContract,
18925
- functionName: "get_virtual_price"
18956
+ const dataPayload = await wrapper.prepareRedstonePayload(true);
18957
+ const parsed = redstoneProtocol.RedstonePayload.parse(viem.toBytes(`0x${dataPayload}`));
18958
+ const packagesByDataFeedId = groupDataPackages(parsed.signedDataPackages);
18959
+ return dataPackagesIds.map((dataFeedId) => {
18960
+ const signedDataPackages = packagesByDataFeedId[dataFeedId];
18961
+ if (!signedDataPackages) {
18962
+ throw new Error(`cannot find data packages for ${dataFeedId}`);
18963
+ }
18964
+ if (signedDataPackages.length !== uniqueSignersCount) {
18965
+ throw new Error(
18966
+ `got ${signedDataPackages.length} data packages for ${dataFeedId}, but expected ${uniqueSignersCount}`
18967
+ );
18968
+ }
18969
+ return getCalldataWithTimestamp(
18970
+ dataFeedId,
18971
+ signedDataPackages,
18972
+ wrapper.getUnsignedMetadata()
18973
+ );
18926
18974
  });
18927
18975
  }
18928
18976
  };
18929
-
18930
- // src/sdk/market/pricefeeds/CurveStablePriceFeed.ts
18931
- var CurveStablePriceFeedContract = class extends AbstractLPPriceFeedContract {
18932
- constructor(sdk, args) {
18933
- super(sdk, {
18934
- ...args,
18935
- name: "CurveStablePriceFeed",
18936
- abi: curveStableLpPriceFeedAbi
18937
- });
18938
- }
18939
- get state() {
18940
- return {
18941
- ...this.contractData,
18942
- contractType: this.priceFeedType,
18943
- skipCheck: true,
18944
- pricefeeds: this.underlyingPriceFeeds.map((pf) => pf.state)
18945
- };
18977
+ function cacheKey(dataServiceId, dataFeedId, uniqueSignersCount, historicalTimestamp = 0) {
18978
+ return `${dataServiceId}:${dataFeedId}:${uniqueSignersCount}:${historicalTimestamp}`;
18979
+ }
18980
+ function groupDataPackages(signedDataPackages) {
18981
+ const packagesByDataFeedId = {};
18982
+ for (const p of signedDataPackages) {
18983
+ const { dataPoints } = p.dataPackage;
18984
+ const dataFeedId0 = dataPoints[0].dataFeedId;
18985
+ for (const dp of dataPoints) {
18986
+ if (dp.dataFeedId !== dataFeedId0) {
18987
+ throw new Error(
18988
+ `data package contains data points with different dataFeedIds: ${dp.dataFeedId} and ${dataFeedId0}`
18989
+ );
18990
+ }
18991
+ }
18992
+ if (!packagesByDataFeedId[dataFeedId0]) {
18993
+ packagesByDataFeedId[dataFeedId0] = [];
18994
+ }
18995
+ packagesByDataFeedId[dataFeedId0].push(p);
18946
18996
  }
18947
- async getValue() {
18948
- return await this.sdk.provider.publicClient.readContract({
18949
- abi: iCurvePoolAbi,
18950
- address: this.lpContract,
18951
- functionName: "get_virtual_price"
18952
- });
18997
+ return packagesByDataFeedId;
18998
+ }
18999
+ function getCalldataWithTimestamp(dataFeedId, packages, unsignedMetadata) {
19000
+ const originalPayload = redstoneProtocol.RedstonePayload.prepare(packages, unsignedMetadata);
19001
+ const originalPayloadLength = originalPayload.length / 2;
19002
+ const bytesToAdd = 32 - originalPayloadLength % 32;
19003
+ const newUnsignedMetadata = unsignedMetadata + "_".repeat(bytesToAdd);
19004
+ const payload = redstoneProtocol.RedstonePayload.prepare(packages, newUnsignedMetadata);
19005
+ let timestamp = 0;
19006
+ for (const p of packages) {
19007
+ const newTimestamp = p.dataPackage.timestampMilliseconds / 1e3;
19008
+ if (timestamp === 0) {
19009
+ timestamp = newTimestamp;
19010
+ } else if (timestamp !== newTimestamp) {
19011
+ throw new Error("Timestamps are not equal");
19012
+ }
18953
19013
  }
18954
- };
19014
+ return {
19015
+ dataFeedId,
19016
+ data: viem.encodeAbiParameters(
19017
+ [{ type: "uint256" }, { type: "bytes" }],
19018
+ [BigInt(timestamp), `0x${payload}`]
19019
+ ),
19020
+ timestamp
19021
+ };
19022
+ }
18955
19023
 
18956
- // src/sdk/market/pricefeeds/CurveUSDPriceFeed.ts
18957
- var CurveUSDPriceFeedContract = class extends AbstractLPPriceFeedContract {
19024
+ // src/sdk/market/pricefeeds/WstETHPriceFeed.ts
19025
+ var WstETHPriceFeedContract = class extends AbstractLPPriceFeedContract {
18958
19026
  constructor(sdk, args) {
18959
19027
  super(sdk, {
18960
19028
  ...args,
18961
- name: "CurveUSDPriceFeed",
18962
- abi: curveUsdPriceFeedAbi
19029
+ name: "WstETHPriceFeed",
19030
+ abi: wstEthPriceFeedAbi
18963
19031
  });
18964
19032
  }
18965
19033
  get state() {
@@ -18972,18 +19040,20 @@ var CurveUSDPriceFeedContract = class extends AbstractLPPriceFeedContract {
18972
19040
  }
18973
19041
  async getValue() {
18974
19042
  return await this.sdk.provider.publicClient.readContract({
18975
- abi: iCurvePoolAbi,
19043
+ abi: iwstEthAbi,
18976
19044
  address: this.lpContract,
18977
- functionName: "get_virtual_price"
19045
+ functionName: "stEthPerToken"
18978
19046
  });
18979
19047
  }
18980
19048
  };
18981
- var Erc4626PriceFeedContract = class extends AbstractLPPriceFeedContract {
19049
+
19050
+ // src/sdk/market/pricefeeds/YearnPriceFeed.ts
19051
+ var YearnPriceFeedContract = class extends AbstractLPPriceFeedContract {
18982
19052
  constructor(sdk, args) {
18983
19053
  super(sdk, {
18984
19054
  ...args,
18985
- name: "ERC4626PriceFeed",
18986
- abi: erc4626PriceFeedAbi
19055
+ name: "YearnPriceFeed",
19056
+ abi: yearnPriceFeedAbi
18987
19057
  });
18988
19058
  }
18989
19059
  get state() {
@@ -18995,878 +19065,1028 @@ var Erc4626PriceFeedContract = class extends AbstractLPPriceFeedContract {
18995
19065
  };
18996
19066
  }
18997
19067
  async getValue() {
18998
- const decimals2 = await this.sdk.provider.publicClient.readContract({
18999
- abi: erc20Abi,
19000
- address: this.lpContract,
19001
- functionName: "decimals"
19002
- });
19003
- const price = await this.sdk.provider.publicClient.readContract({
19004
- abi: viem.erc4626Abi,
19068
+ return await this.sdk.provider.publicClient.readContract({
19069
+ abi: iyVaultAbi,
19005
19070
  address: this.lpContract,
19006
- functionName: "convertToAssets",
19007
- args: [10n ** BigInt(decimals2)]
19071
+ functionName: "pricePerShare"
19008
19072
  });
19009
- return price;
19010
19073
  }
19011
19074
  };
19012
19075
 
19013
- // src/sdk/market/pricefeeds/MellowLRTPriceFeed.ts
19014
- var MellowLRTPriceFeedContract = class extends AbstractLPPriceFeedContract {
19076
+ // src/sdk/market/pricefeeds/ZeroPriceFeed.ts
19077
+ var ZeroPriceFeedContract = class extends AbstractPriceFeedContract {
19015
19078
  constructor(sdk, args) {
19016
19079
  super(sdk, {
19017
19080
  ...args,
19018
- name: "MellowLRTPriceFeed",
19019
- abi: mellowLrtPriceFeedAbi
19081
+ name: "ZeroPriceFeed",
19082
+ abi: zeroPriceFeedAbi,
19083
+ decimals: 8
19020
19084
  });
19021
19085
  }
19022
19086
  get state() {
19023
19087
  return {
19024
19088
  ...this.contractData,
19025
- contractType: this.priceFeedType,
19089
+ contractType: "PF_ZERO_ORACLE",
19026
19090
  skipCheck: true,
19027
- pricefeeds: [this.underlyingPriceFeeds[0].state]
19091
+ stalenessPeriod: 0,
19092
+ pricefeeds: []
19028
19093
  };
19029
19094
  }
19030
- async getValue() {
19031
- const stack = await this.sdk.provider.publicClient.readContract({
19032
- abi: iMellowVaultAbi,
19033
- address: this.lpContract,
19034
- functionName: "calculateStack"
19035
- });
19036
- return stack.totalValue * BigInt(1e18) / stack.totalSupply;
19037
- }
19038
19095
  };
19039
19096
 
19040
- // src/sdk/utils/internal/Hooks.ts
19041
- var Hooks = class {
19042
- #reg = {};
19043
- addHook(hookName, fn) {
19044
- if (!this.#reg[hookName]) {
19045
- this.#reg[hookName] = [];
19046
- }
19047
- this.#reg[hookName].push(fn);
19048
- }
19049
- removeHook(hookName, fn) {
19050
- if (!this.#reg[hookName]) {
19051
- return;
19052
- }
19053
- this.#reg[hookName] = this.#reg[hookName]?.filter((hookFn) => hookFn !== fn) ?? [];
19097
+ // src/sdk/market/pricefeeds/PriceFeedsRegister.ts
19098
+ var PriceFeedRegister = class extends SDKConstruct {
19099
+ logger;
19100
+ #hooks = new Hooks();
19101
+ #feeds = new AddressMap();
19102
+ #redstoneUpdater;
19103
+ // public readonly zeroPriceFeed: ZeroPriceFeedContract;
19104
+ constructor(sdk) {
19105
+ super(sdk);
19106
+ this.logger = childLogger("PriceFeedRegister", sdk.logger);
19107
+ this.#redstoneUpdater = new RedstoneUpdater(sdk);
19054
19108
  }
19055
- async triggerHooks(hookName, ...args) {
19056
- if (!this.#reg[hookName]) {
19057
- return;
19109
+ addHook = this.#hooks.addHook.bind(this.#hooks);
19110
+ removeHook = this.#hooks.removeHook.bind(this.#hooks);
19111
+ /**
19112
+ * Returns RawTxs to update price feeds
19113
+ * @param priceFeeds top-level price feeds, actual updatable price feeds will be derived. If not provided will use all price feeds that are attached
19114
+ * @returns
19115
+ */
19116
+ async generatePriceFeedsUpdateTxs(priceFeeds) {
19117
+ const priceFeedz = priceFeeds ?? Object.values(this.#feeds);
19118
+ const updateables = priceFeedz.flatMap((pf) => pf.updatableDependencies());
19119
+ const txs = [];
19120
+ const redstonePFs = [];
19121
+ for (const pf of updateables) {
19122
+ if (pf instanceof RedstonePriceFeedContract) {
19123
+ redstonePFs.push(pf);
19124
+ }
19058
19125
  }
19059
- for (const fn of this.#reg[hookName]) {
19060
- await fn(...args);
19126
+ let maxTimestamp = 0;
19127
+ if (redstonePFs.length > 0) {
19128
+ const redstoneUpdates = await this.#redstoneUpdater.getUpdateTxs(redstonePFs);
19129
+ for (const { tx, timestamp } of redstoneUpdates) {
19130
+ if (timestamp > maxTimestamp) {
19131
+ maxTimestamp = timestamp;
19132
+ }
19133
+ txs.push(tx);
19134
+ }
19061
19135
  }
19136
+ const result = { txs, timestamp: maxTimestamp };
19137
+ await this.#hooks.triggerHooks("updatesGenerated", result);
19138
+ return result;
19062
19139
  }
19063
- };
19064
- var RedstonePriceFeedContract = class extends AbstractPriceFeedContract {
19065
- decimals = 8;
19066
- dataServiceId;
19067
- dataId;
19068
- signers;
19069
- signersThreshold;
19070
- constructor(sdk, args) {
19071
- super(sdk, {
19072
- ...args,
19073
- name: `RedstonePriceFeed`,
19074
- abi: redstonePriceFeedAbi
19075
- });
19076
- const decoder = viem.decodeAbiParameters(
19077
- [
19078
- { type: "address" },
19079
- // [0]: pf.token(),
19080
- { type: "bytes32" },
19081
- // [1]: pf.dataFeedId(),
19082
- { type: "address" },
19083
- // [2]: pf.signerAddress0(),
19084
- { type: "address" },
19085
- // [3]: pf.signerAddress1(),
19086
- { type: "address" },
19087
- // [4]: pf.signerAddress2(),
19088
- { type: "address" },
19089
- // [5]: pf.signerAddress3(),
19090
- { type: "address" },
19091
- // [6]: pf.signerAddress4(),
19092
- { type: "address" },
19093
- // [7]: pf.signerAddress5()
19094
- { type: "address" },
19095
- // [8]: pf.signerAddress6(),
19096
- { type: "address" },
19097
- // [9]: pf.signerAddress7(),
19098
- { type: "address" },
19099
- // [10]: pf.signerAddress8(),
19100
- { type: "address" },
19101
- // [11]: pf.signerAddress9(),
19102
- { type: "uint8" },
19103
- // [12]: pf.getUniqueSignersThreshold()
19104
- { type: "uint128" },
19105
- // [13]: pf.lastPrice(),
19106
- { type: "uint40" }
19107
- // [14]: pf.lastPayloadTimestamp()
19108
- ],
19109
- args.baseParams.serializedParams
19110
- );
19111
- this.dataId = viem.bytesToString(viem.toBytes(decoder[1])).replaceAll("\0", "");
19112
- this.signers = decoder.slice(2, 11);
19113
- this.signersThreshold = Number(decoder[12]);
19114
- this.dataServiceId = ["GMX", "BAL"].includes(this.dataId) ? "redstone-arbitrum-prod" : "redstone-primary-prod";
19140
+ get(address) {
19141
+ return this.#feeds.get(address);
19115
19142
  }
19116
- get state() {
19117
- return {
19118
- ...this.contractData,
19119
- contractType: "PF_REDSTONE_ORACLE",
19120
- dataId: this.dataId,
19121
- signers: this.signers,
19122
- signersThreshold: this.signersThreshold,
19123
- skipCheck: true
19124
- };
19143
+ mustGet(address) {
19144
+ return this.#feeds.mustGet(address);
19125
19145
  }
19126
- };
19127
- var RedstoneUpdater = class extends SDKConstruct {
19128
- #logger;
19129
- #cache = /* @__PURE__ */ new Map();
19130
- #historicalTimestamp;
19131
- constructor(sdk) {
19132
- super(sdk);
19133
- this.#logger = childLogger("RedstoneUpdater", sdk.logger);
19146
+ create(data) {
19147
+ const feed = this.#create(data);
19148
+ this.#feeds.upsert(data.baseParams.addr, feed);
19149
+ return feed;
19134
19150
  }
19135
- setHistoricalTimestamp(timestamp) {
19136
- this.#historicalTimestamp = timestamp;
19151
+ /**
19152
+ * Set redstone historical timestamp
19153
+ * @param timestampMs in milliseconds
19154
+ */
19155
+ setRedstoneHistoricalTimestamp(timestampMs) {
19156
+ this.#redstoneUpdater.setHistoricalTimestamp(timestampMs);
19137
19157
  }
19138
- async getUpdateTxs(feeds) {
19139
- this.#logger?.debug(
19140
- `generating update transactions for ${feeds.length} redstone price feeds`
19158
+ #create(data) {
19159
+ const contractType = bytes32ToString(
19160
+ data.baseParams.contractType
19141
19161
  );
19142
- const groupedFeeds = {};
19143
- const priceFeeds = /* @__PURE__ */ new Map();
19144
- for (const feed of feeds) {
19145
- const key = `${feed.dataServiceId}:${feed.signersThreshold}`;
19146
- if (!groupedFeeds[key]) {
19147
- groupedFeeds[key] = /* @__PURE__ */ new Set();
19148
- }
19149
- groupedFeeds[key].add(feed.dataId);
19150
- priceFeeds.set(feed.dataId, feed);
19162
+ switch (contractType) {
19163
+ case "PF_CHAINLINK_ORACLE":
19164
+ return new ChainlinkPriceFeedContract(this.sdk, data);
19165
+ case "PF_YEARN_ORACLE":
19166
+ return new YearnPriceFeedContract(this.sdk, data);
19167
+ case "PF_CURVE_STABLE_LP_ORACLE":
19168
+ return new CurveStablePriceFeedContract(this.sdk, data);
19169
+ case "PF_WSTETH_ORACLE":
19170
+ return new WstETHPriceFeedContract(this.sdk, data);
19171
+ case "PF_BOUNDED_ORACLE":
19172
+ return new BoundedPriceFeedContract(this.sdk, data);
19173
+ case "PF_COMPOSITE_ORACLE":
19174
+ return new CompositePriceFeedContract(this.sdk, data);
19175
+ case "PF_BALANCER_STABLE_LP_ORACLE":
19176
+ return new BalancerStablePriceFeedContract(this.sdk, data);
19177
+ case "PF_BALANCER_WEIGHTED_LP_ORACLE":
19178
+ return new BalancerWeightedPriceFeedContract(this.sdk, data);
19179
+ case "PF_CURVE_CRYPTO_LP_ORACLE":
19180
+ return new CurveCryptoPriceFeedContract(this.sdk, data);
19181
+ case "PF_REDSTONE_ORACLE":
19182
+ return new RedstonePriceFeedContract(this.sdk, data);
19183
+ case "PF_ERC4626_ORACLE":
19184
+ return new Erc4626PriceFeedContract(this.sdk, data);
19185
+ case "PF_CURVE_USD_ORACLE":
19186
+ return new CurveUSDPriceFeedContract(this.sdk, data);
19187
+ case "PF_ZERO_ORACLE":
19188
+ return new ZeroPriceFeedContract(this.sdk, data);
19189
+ case "PF_MELLOW_LRT_ORACLE":
19190
+ return new MellowLRTPriceFeedContract(this.sdk, data);
19191
+ default:
19192
+ throw new Error(`Price feed type ${contractType} not supported, `);
19193
+ }
19194
+ }
19195
+ };
19196
+ function rawTxToMulticallPriceUpdate(tx) {
19197
+ const { to, callData } = tx;
19198
+ const { args, functionName } = viem.decodeFunctionData({
19199
+ abi: iUpdatablePriceFeedAbi,
19200
+ data: callData
19201
+ });
19202
+ return {
19203
+ abi: iUpdatablePriceFeedAbi,
19204
+ address: to,
19205
+ functionName,
19206
+ args
19207
+ };
19208
+ }
19209
+
19210
+ // src/sdk/market/PriceOracleContract.ts
19211
+ var PriceOracleContract = class extends BaseContract {
19212
+ /**
19213
+ * Underlying token of market to which this price oracle belongs
19214
+ */
19215
+ underlying;
19216
+ /**
19217
+ * Mapping Token => [PriceFeed Address, stalenessPeriod]
19218
+ */
19219
+ mainPriceFeeds = {};
19220
+ /**
19221
+ * Mapping Token => [PriceFeed Address, stalenessPeriod]
19222
+ */
19223
+ reservePriceFeeds = {};
19224
+ /**
19225
+ * Mapping Token => Price in underlying
19226
+ */
19227
+ mainPrices = new AddressMap();
19228
+ /**
19229
+ * Mapping Token => Price in underlying
19230
+ */
19231
+ reservePrices = new AddressMap();
19232
+ #priceFeedTree;
19233
+ constructor(sdk, data, underlying) {
19234
+ super(sdk, {
19235
+ ...data.baseParams,
19236
+ name: "PriceOracleV3",
19237
+ abi: priceOracleV3Abi
19238
+ });
19239
+ this.underlying = underlying;
19240
+ const { priceFeedMapping, priceFeedStructure } = data;
19241
+ this.#priceFeedTree = priceFeedStructure;
19242
+ for (const node of priceFeedStructure) {
19243
+ sdk.priceFeeds.create(node);
19151
19244
  }
19152
- const results = [];
19153
- for (const [key, group] of Object.entries(groupedFeeds)) {
19154
- const [dataServiceId, signersStr] = key.split(":");
19155
- const uniqueSignersCount = parseInt(signersStr, 10);
19156
- this.#logger?.debug(
19157
- `fetching redstone payloads for ${group.size} data feeds in ${dataServiceId} with ${uniqueSignersCount} signers: ${Array.from(group).join(", ")}`
19158
- );
19159
- const payloads = await this.#getPayloads(
19160
- dataServiceId,
19161
- group,
19162
- uniqueSignersCount
19163
- );
19164
- for (const { dataFeedId, data, timestamp } of payloads) {
19165
- const priceFeed = priceFeeds.get(dataFeedId);
19166
- if (!priceFeed) {
19167
- throw new Error(`cannot get price feed address for ${dataFeedId}`);
19245
+ priceFeedMapping.forEach((node) => {
19246
+ const { token, priceFeed, reserve, stalenessPeriod } = node;
19247
+ const ref = new PriceFeedRef(sdk, priceFeed, stalenessPeriod);
19248
+ const price = this.#priceFeedTree.find(
19249
+ (n) => n.baseParams.addr === priceFeed
19250
+ )?.answer?.price;
19251
+ if (reserve) {
19252
+ this.reservePriceFeeds[token] = ref;
19253
+ if (price) {
19254
+ this.reservePrices.upsert(token, price);
19255
+ }
19256
+ } else {
19257
+ this.mainPriceFeeds[token] = ref;
19258
+ if (price) {
19259
+ this.mainPrices.upsert(token, price);
19168
19260
  }
19169
- results.push({
19170
- priceFeed: priceFeed.address.toLowerCase(),
19171
- tx: priceFeed.createRawTx({
19172
- functionName: "updatePrice",
19173
- args: [data],
19174
- description: `updating price for ${dataFeedId} [${priceFeed.address}]`
19175
- }),
19176
- timestamp
19177
- });
19178
19261
  }
19179
- }
19180
- this.#logger?.debug(
19181
- `generated ${results.length} update transactions for redstone price feeds`
19262
+ this.#labelPriceFeed(priceFeed, reserve ? "Reserve" : "Main", token);
19263
+ });
19264
+ this.logger?.debug(
19265
+ `Got ${Object.keys(this.mainPriceFeeds).length} main and ${Object.keys(this.reservePriceFeeds).length} reserve price feeds`
19182
19266
  );
19183
- return results;
19184
19267
  }
19185
19268
  /**
19186
- * Gets redstone payloads in one request for multiple feeds with the same dataServiceId and uniqueSignersCount
19187
- * If historicalTimestamp is set, responses will be cached
19188
- * @param dataServiceId
19189
- * @param dataFeedsIds
19190
- * @param uniqueSignersCount
19269
+ * Returns main and reserve price feeds for given tokens
19270
+ * @param tokens
19271
+ * @param opts Option to include main/reserve feeds only, defaults to both
19191
19272
  * @returns
19192
19273
  */
19193
- async #getPayloads(dataServiceId, dataFeedsIds, uniqueSignersCount) {
19194
- const fromCache = [];
19195
- const uncached = [];
19196
- for (const dataFeedId of dataFeedsIds) {
19197
- const key = cacheKey(
19198
- dataServiceId,
19199
- dataFeedId,
19200
- uniqueSignersCount,
19201
- this.#historicalTimestamp
19202
- );
19203
- const cached = this.#cache.get(key);
19204
- if (this.#historicalTimestamp && !!cached) {
19205
- fromCache.push(cached);
19206
- } else {
19207
- uncached.push(dataFeedId);
19208
- }
19209
- }
19210
- const fromRedstone = await this.#fetchPayloads(
19211
- dataServiceId,
19212
- new Set(uncached),
19213
- uniqueSignersCount
19214
- );
19215
- if (this.#historicalTimestamp) {
19216
- for (const resp of fromRedstone) {
19217
- const key = cacheKey(
19218
- dataServiceId,
19219
- resp.dataFeedId,
19220
- uniqueSignersCount,
19221
- this.#historicalTimestamp
19222
- );
19223
- this.#cache.set(key, resp);
19224
- }
19225
- }
19226
- return [...fromCache, ...fromRedstone];
19274
+ priceFeedsForTokens(tokens, opts) {
19275
+ const main = opts?.main ?? true;
19276
+ const reserve = opts?.reserve ?? true;
19277
+ return tokens.flatMap((t) => [
19278
+ main ? this.mainPriceFeeds[t]?.priceFeed : void 0,
19279
+ reserve ? this.reservePriceFeeds[t]?.priceFeed : void 0
19280
+ ]).filter((f) => !!f);
19227
19281
  }
19228
19282
  /**
19229
- * Fetches redstone payloads in one request for multiple feeds with the same dataServiceId and uniqueSignersCount
19230
- * Payloads are loaded in one request to avoid redstone rate limit
19231
- * @param dataServiceId
19232
- * @param dataFeedsIds
19233
- * @param uniqueSignersCount
19283
+ * Generates updates for all updateable price feeds in this oracle (including dependencies)
19234
19284
  * @returns
19235
19285
  */
19236
- async #fetchPayloads(dataServiceId, dataFeedsIds, uniqueSignersCount) {
19237
- if (dataFeedsIds.size === 0) {
19238
- return [];
19239
- }
19240
- const dataPackagesIds = Array.from(dataFeedsIds);
19241
- const wrapper = new evmConnector.DataServiceWrapper({
19242
- dataServiceId,
19243
- dataPackagesIds,
19244
- uniqueSignersCount,
19245
- historicalTimestamp: this.#historicalTimestamp
19246
- });
19247
- const dataPayload = await wrapper.prepareRedstonePayload(true);
19248
- const parsed = redstoneProtocol.RedstonePayload.parse(viem.toBytes(`0x${dataPayload}`));
19249
- const packagesByDataFeedId = groupDataPackages(parsed.signedDataPackages);
19250
- return dataPackagesIds.map((dataFeedId) => {
19251
- const signedDataPackages = packagesByDataFeedId[dataFeedId];
19252
- if (!signedDataPackages) {
19253
- throw new Error(`cannot find data packages for ${dataFeedId}`);
19254
- }
19255
- if (signedDataPackages.length !== uniqueSignersCount) {
19256
- throw new Error(
19257
- `got ${signedDataPackages.length} data packages for ${dataFeedId}, but expected ${uniqueSignersCount}`
19258
- );
19286
+ async updatePriceFeeds() {
19287
+ const updatables = [];
19288
+ for (const node of this.#priceFeedTree) {
19289
+ if (node.updatable) {
19290
+ updatables.push(this.sdk.priceFeeds.mustGet(node.baseParams.addr));
19259
19291
  }
19260
- return getCalldataWithTimestamp(
19261
- dataFeedId,
19262
- signedDataPackages,
19263
- wrapper.getUnsignedMetadata()
19264
- );
19265
- });
19292
+ }
19293
+ return this.sdk.priceFeeds.generatePriceFeedsUpdateTxs(updatables);
19266
19294
  }
19267
- };
19268
- function cacheKey(dataServiceId, dataFeedId, uniqueSignersCount, historicalTimestamp = 0) {
19269
- return `${dataServiceId}:${dataFeedId}:${uniqueSignersCount}:${historicalTimestamp}`;
19270
- }
19271
- function groupDataPackages(signedDataPackages) {
19272
- const packagesByDataFeedId = {};
19273
- for (const p of signedDataPackages) {
19274
- const { dataPoints } = p.dataPackage;
19275
- const dataFeedId0 = dataPoints[0].dataFeedId;
19276
- for (const dp of dataPoints) {
19277
- if (dp.dataFeedId !== dataFeedId0) {
19278
- throw new Error(
19279
- `data package contains data points with different dataFeedIds: ${dp.dataFeedId} and ${dataFeedId0}`
19280
- );
19281
- }
19295
+ /**
19296
+ * Converts previously obtained price updates into CreditFacade multicall entries
19297
+ * @param creditFacade
19298
+ * @param updates
19299
+ * @returns
19300
+ */
19301
+ onDemandPriceUpdates(updates) {
19302
+ const result = [];
19303
+ if (!updates) {
19304
+ return result;
19282
19305
  }
19283
- if (!packagesByDataFeedId[dataFeedId0]) {
19284
- packagesByDataFeedId[dataFeedId0] = [];
19306
+ const { txs } = updates;
19307
+ for (const tx of txs) {
19308
+ const { to: priceFeed, callData } = tx;
19309
+ const [token, reserve] = this.#findTokenForPriceFeed(priceFeed);
19310
+ const { args } = viem.decodeFunctionData({
19311
+ abi: iUpdatablePriceFeedAbi,
19312
+ data: callData
19313
+ });
19314
+ const data = args[0];
19315
+ result.push({
19316
+ token,
19317
+ reserve,
19318
+ data
19319
+ });
19285
19320
  }
19286
- packagesByDataFeedId[dataFeedId0].push(p);
19321
+ return result;
19287
19322
  }
19288
- return packagesByDataFeedId;
19289
- }
19290
- function getCalldataWithTimestamp(dataFeedId, packages, unsignedMetadata) {
19291
- const originalPayload = redstoneProtocol.RedstonePayload.prepare(packages, unsignedMetadata);
19292
- const originalPayloadLength = originalPayload.length / 2;
19293
- const bytesToAdd = 32 - originalPayloadLength % 32;
19294
- const newUnsignedMetadata = unsignedMetadata + "_".repeat(bytesToAdd);
19295
- const payload = redstoneProtocol.RedstonePayload.prepare(packages, newUnsignedMetadata);
19296
- let timestamp = 0;
19297
- for (const p of packages) {
19298
- const newTimestamp = p.dataPackage.timestampMilliseconds / 1e3;
19299
- if (timestamp === 0) {
19300
- timestamp = newTimestamp;
19301
- } else if (timestamp !== newTimestamp) {
19302
- throw new Error("Timestamps are not equal");
19323
+ /**
19324
+ * Tries to convert amount of token into underlying of current market
19325
+ * @param token
19326
+ * @param amount
19327
+ * @param reserve
19328
+ * @returns
19329
+ */
19330
+ convertToUnderlying(token, amount, reserve = false) {
19331
+ return this.convert(token, this.underlying, amount, reserve);
19332
+ }
19333
+ /**
19334
+ * Tries to convert amount of from one token to another, using latest known prices
19335
+ * @param from
19336
+ * @param to
19337
+ * @param amount
19338
+ * @param reserve
19339
+ */
19340
+ convert(from, to, amount, reserve = false) {
19341
+ if (from === to) {
19342
+ return amount;
19303
19343
  }
19344
+ const fromPrice = reserve ? this.reservePrices.mustGet(from) : this.mainPrices.mustGet(from);
19345
+ const fromScale = 10n ** BigInt(this.sdk.marketRegister.tokensMeta.mustGet(from).decimals);
19346
+ const toPrice = reserve ? this.reservePrices.mustGet(to) : this.mainPrices.mustGet(to);
19347
+ const toScale = 10n ** BigInt(this.sdk.marketRegister.tokensMeta.mustGet(to).decimals);
19348
+ return amount * fromPrice * toScale / (toPrice * fromScale);
19304
19349
  }
19305
- return {
19306
- dataFeedId,
19307
- data: viem.encodeAbiParameters(
19308
- [{ type: "uint256" }, { type: "bytes" }],
19309
- [BigInt(timestamp), `0x${payload}`]
19310
- ),
19311
- timestamp
19312
- };
19313
- }
19314
-
19315
- // src/sdk/market/pricefeeds/WstETHPriceFeed.ts
19316
- var WstETHPriceFeedContract = class extends AbstractLPPriceFeedContract {
19317
- constructor(sdk, args) {
19318
- super(sdk, {
19319
- ...args,
19320
- name: "WstETHPriceFeed",
19321
- abi: wstEthPriceFeedAbi
19350
+ /**
19351
+ * Loads new prices for this oracle from PriceFeedCompressor
19352
+ * Does not update price feeds, only updates prices
19353
+ */
19354
+ async updatePrices() {
19355
+ const { txs } = await this.updatePriceFeeds();
19356
+ const resp = await simulateMulticall(this.provider.publicClient, {
19357
+ contracts: [
19358
+ ...txs.map(rawTxToMulticallPriceUpdate),
19359
+ {
19360
+ abi: iPriceFeedCompressorAbi,
19361
+ address: this.sdk.addressProvider.getLatestVersion(
19362
+ AP_PRICE_FEED_COMPRESSOR
19363
+ ),
19364
+ functionName: "getPriceFeeds",
19365
+ args: [this.address]
19366
+ }
19367
+ ],
19368
+ allowFailure: false,
19369
+ gas: 550000000n,
19370
+ batchSize: 0
19371
+ // we cannot have price updates and compressor request in different batches
19322
19372
  });
19323
- }
19324
- get state() {
19325
- return {
19326
- ...this.contractData,
19327
- contractType: this.priceFeedType,
19328
- skipCheck: true,
19329
- pricefeeds: [this.underlyingPriceFeeds[0].state]
19330
- };
19331
- }
19332
- async getValue() {
19333
- return await this.sdk.provider.publicClient.readContract({
19334
- abi: iwstEthAbi,
19335
- address: this.lpContract,
19336
- functionName: "stEthPerToken"
19373
+ const [entries, tree] = resp.pop();
19374
+ entries.forEach(({ token, priceFeed, reserve }) => {
19375
+ const price = tree.find((n) => n.baseParams.addr === priceFeed)?.answer?.price;
19376
+ if (reserve && price) {
19377
+ this.reservePrices.upsert(token, price);
19378
+ } else if (price) {
19379
+ this.mainPrices.upsert(token, price);
19380
+ }
19337
19381
  });
19338
19382
  }
19339
- };
19340
-
19341
- // src/sdk/market/pricefeeds/YearnPriceFeed.ts
19342
- var YearnPriceFeedContract = class extends AbstractLPPriceFeedContract {
19343
- constructor(sdk, args) {
19344
- super(sdk, {
19345
- ...args,
19346
- name: "YearnPriceFeed",
19347
- abi: yearnPriceFeedAbi
19383
+ #labelPriceFeed(address, usage, token) {
19384
+ this.sdk.provider.addressLabels.set(address, (label) => {
19385
+ const { symbol } = this.sdk.marketRegister.tokensMeta.mustGet(token);
19386
+ let pricefeedTag = `${symbol}.${usage}`;
19387
+ if (label) {
19388
+ pricefeedTag = `${label}, ${pricefeedTag}`;
19389
+ }
19390
+ return pricefeedTag;
19348
19391
  });
19349
19392
  }
19393
+ /**
19394
+ * Helper method to find "attachment point" of price feed (makes sense for updatable price feeds only) - token (in v3.0 can be ticker) and main/reserve flag
19395
+ *
19396
+ * @param priceFeed
19397
+ * @returns
19398
+ */
19399
+ #findTokenForPriceFeed(priceFeed) {
19400
+ for (const [token, pf] of Object.entries(this.mainPriceFeeds)) {
19401
+ if (pf.address === priceFeed) {
19402
+ return [token, false];
19403
+ }
19404
+ }
19405
+ for (const [token, pf] of Object.entries(this.reservePriceFeeds)) {
19406
+ if (pf.address === priceFeed) {
19407
+ return [token, true];
19408
+ }
19409
+ }
19410
+ const ticker = priceFeedToTicker[this.sdk.provider.networkType][priceFeed];
19411
+ if (ticker) {
19412
+ return [ticker, false];
19413
+ }
19414
+ throw new Error(`cannot find token for price feed ${priceFeed}`);
19415
+ }
19350
19416
  get state() {
19351
19417
  return {
19352
- ...this.contractData,
19353
- contractType: this.priceFeedType,
19354
- skipCheck: true,
19355
- pricefeeds: [this.underlyingPriceFeeds[0].state]
19418
+ priceOracleV3: this.contractData,
19419
+ mainPriceFeeds: Object.fromEntries(
19420
+ Object.entries(this.mainPriceFeeds).map(([token, v]) => [
19421
+ token,
19422
+ v.state
19423
+ ])
19424
+ ),
19425
+ reservePriceFeeds: Object.fromEntries(
19426
+ Object.entries(this.reservePriceFeeds).map(([token, v]) => [
19427
+ token,
19428
+ v.state
19429
+ ])
19430
+ )
19356
19431
  };
19357
19432
  }
19358
- async getValue() {
19359
- return await this.sdk.provider.publicClient.readContract({
19360
- abi: iyVaultAbi,
19361
- address: this.lpContract,
19362
- functionName: "pricePerShare"
19363
- });
19364
- }
19433
+ };
19434
+ var priceFeedToTicker = {
19435
+ Mainnet: {
19436
+ "0x6F13996411743d22566176482B6b677Ec4eb6cE6": "0x8C23b9E4CB9884e807294c4b4C33820333cC613c",
19437
+ "0xa7cB34Cd731486F61cfDb7ff5F6fC7B40537eD76": "0xFb56Fb16B4F33A875b01881Da7458E09D286208e",
19438
+ "0xcf1FDc8DC6e83B38729d58C117BE704bb2AC362a": "0xf08D818be34C82cB5e3f33AC78F8268828764F17",
19439
+ "0xE683362b8ebcbfd9332CBB79BfAF9fC42073C49b": "0xBdb778F566b6cEd70D3d329DD1D14E221fFe1ba5",
19440
+ "0xB72A69e2182bE87bda706B7Ff9A539AC78338C61": "0x7fF63E75F48aad6F4bE97E75C6421f348f19fE7F",
19441
+ "0xd7396fA3aFB9833293Ce2149EEb3Dbf5380B1e0D": "0xB0EA0EC3Fd4947348816f76768b3a56249d47EEc"
19442
+ },
19443
+ Arbitrum: {
19444
+ "0xcB44ADd611f75F03191f8f1A2e2AF7a0113eadd1": "0x07299E4E806e4253727084c0493fFDf6fB2dBa3D",
19445
+ "0x354A63F07A5c1605920794aFFF09963b6DF897a9": "0x15094B05e679c9B7fDde6FB8e6BDa930ff1D6a62"
19446
+ },
19447
+ Optimism: {
19448
+ "0xF23C91b1E3B7FD9174c82F7Fb2BD270C3CfcC3CE": "0x658f8e60c57ad62a9299ef6c7b1da9a0d1d1e681"
19449
+ },
19450
+ Base: {}
19365
19451
  };
19366
19452
 
19367
- // src/sdk/market/pricefeeds/ZeroPriceFeed.ts
19368
- var ZeroPriceFeedContract = class extends AbstractPriceFeedContract {
19369
- constructor(sdk, args) {
19370
- super(sdk, {
19371
- ...args,
19372
- name: "ZeroPriceFeed",
19373
- abi: zeroPriceFeedAbi,
19374
- decimals: 8
19375
- });
19453
+ // src/sdk/market/MarketFactory.ts
19454
+ var MarketFactory = class {
19455
+ riskCurator;
19456
+ poolFactory;
19457
+ priceOracle;
19458
+ creditManagers = [];
19459
+ constructor(sdk, marketData) {
19460
+ this.riskCurator = marketData.owner;
19461
+ for (const t of marketData.tokens) {
19462
+ sdk.marketRegister.tokensMeta.upsert(t.addr, t);
19463
+ sdk.provider.addressLabels.set(t.addr, t.symbol);
19464
+ }
19465
+ this.poolFactory = new PoolFactory(sdk, marketData);
19466
+ for (let i = 0; i < marketData.creditManagers.length; i++) {
19467
+ this.creditManagers.push(new CreditFactory(sdk, marketData, i));
19468
+ }
19469
+ this.priceOracle = new PriceOracleContract(
19470
+ sdk,
19471
+ marketData.priceOracleData,
19472
+ marketData.pool.underlying
19473
+ );
19376
19474
  }
19377
19475
  get state() {
19378
19476
  return {
19379
- ...this.contractData,
19380
- contractType: "PF_ZERO_ORACLE",
19381
- skipCheck: true,
19382
- stalenessPeriod: 0,
19383
- pricefeeds: []
19477
+ pool: this.poolFactory.state,
19478
+ creditManagers: this.creditManagers.map((cm) => cm.state),
19479
+ priceOracle: this.priceOracle.state
19384
19480
  };
19385
19481
  }
19386
19482
  };
19387
19483
 
19388
- // src/sdk/market/pricefeeds/PriceFeedsRegister.ts
19389
- var PriceFeedRegister = class extends SDKConstruct {
19390
- logger;
19391
- #hooks = new Hooks();
19392
- #feeds = new AddressMap();
19393
- #redstoneUpdater;
19394
- // public readonly zeroPriceFeed: ZeroPriceFeedContract;
19395
- constructor(sdk) {
19396
- super(sdk);
19397
- this.logger = childLogger("PriceFeedRegister", sdk.logger);
19398
- this.#redstoneUpdater = new RedstoneUpdater(sdk);
19399
- }
19400
- addHook = this.#hooks.addHook.bind(this.#hooks);
19401
- removeHook = this.#hooks.removeHook.bind(this.#hooks);
19484
+ // src/sdk/market/MarketRegister.ts
19485
+ var MarketRegister = class {
19486
+ #logger;
19402
19487
  /**
19403
- * Returns RawTxs to update price feeds
19404
- * @param priceFeeds top-level price feeds, actual updatable price feeds will be derived. If not provided will use all price feeds that are attached
19405
- * @returns
19488
+ * Mapping pool.name -> MarketFactory
19406
19489
  */
19407
- async generatePriceFeedsUpdateTxs(priceFeeds) {
19408
- const priceFeedz = priceFeeds ?? Object.values(this.#feeds);
19409
- const updateables = priceFeedz.flatMap((pf) => pf.updatableDependencies());
19410
- const txs = [];
19411
- const redstonePFs = [];
19412
- for (const pf of updateables) {
19413
- if (pf instanceof RedstonePriceFeedContract) {
19414
- redstonePFs.push(pf);
19415
- }
19416
- }
19417
- let maxTimestamp = 0;
19418
- if (redstonePFs.length > 0) {
19419
- const redstoneUpdates = await this.#redstoneUpdater.getUpdateTxs(redstonePFs);
19420
- for (const { tx, timestamp } of redstoneUpdates) {
19421
- if (timestamp > maxTimestamp) {
19422
- maxTimestamp = timestamp;
19490
+ #markets = {};
19491
+ /**
19492
+ * Token metadata such as symbol and decimals, common across all markets
19493
+ */
19494
+ tokensMeta = new AddressMap();
19495
+ #sdk;
19496
+ constructor(sdk) {
19497
+ this.#sdk = sdk;
19498
+ this.#logger = childLogger("MarketRegister", sdk.logger);
19499
+ }
19500
+ async loadMarkets(curators) {
19501
+ this.#logger?.debug("loading markets");
19502
+ const marketCompressorAddress = this.#sdk.addressProvider.getAddress(
19503
+ AP_MARKET_COMPRESSOR,
19504
+ 310
19505
+ );
19506
+ const markets = await this.#sdk.provider.publicClient.readContract({
19507
+ address: marketCompressorAddress,
19508
+ abi: iMarketCompressorAbi,
19509
+ functionName: "getMarkets",
19510
+ args: [
19511
+ {
19512
+ curators,
19513
+ pools: [],
19514
+ underlying: ADDRESS_0X0
19423
19515
  }
19424
- txs.push(tx);
19425
- }
19516
+ ],
19517
+ // It's passed as ...rest in viem readContract action, but this might change
19518
+ // @ts-ignore
19519
+ gas: 500000000n
19520
+ });
19521
+ for (const data of markets) {
19522
+ this.#markets[data.pool.name] = new MarketFactory(this.#sdk, data);
19426
19523
  }
19427
- const result = { txs, timestamp: maxTimestamp };
19428
- await this.#hooks.triggerHooks("updatesGenerated", result);
19429
- return result;
19524
+ this.#logger?.info(`loaded ${markets.length} markets`);
19430
19525
  }
19431
- get(address) {
19432
- return this.#feeds.get(address);
19526
+ get state() {
19527
+ return Object.values(this.#markets).map((market) => market.state);
19433
19528
  }
19434
- mustGet(address) {
19435
- return this.#feeds.mustGet(address);
19529
+ get poolState() {
19530
+ return Object.values(this.#markets).map((market) => market.poolFactory.state);
19436
19531
  }
19437
- create(data) {
19438
- const feed = this.#create(data);
19439
- this.#feeds.upsert(data.baseParams.addr, feed);
19440
- return feed;
19532
+ get creditManagerState() {
19533
+ return Object.values(this.#markets).flatMap(
19534
+ (market) => market.creditManagers.map((cm) => cm.state)
19535
+ );
19441
19536
  }
19442
- /**
19443
- * Set redstone historical timestamp
19444
- * @param timestampMs in milliseconds
19445
- */
19446
- setRedstoneHistoricalTimestamp(timestampMs) {
19447
- this.#redstoneUpdater.setHistoricalTimestamp(timestampMs);
19537
+ getPoolFactories() {
19538
+ return this.markets.map((market) => market.poolFactory);
19539
+ }
19540
+ get creditManagers() {
19541
+ return this.markets.flatMap((market) => market.creditManagers);
19542
+ }
19543
+ findCreditManager(creditManager) {
19544
+ for (const market of this.markets) {
19545
+ for (const cm of market.creditManagers) {
19546
+ if (cm.creditManager.address === creditManager) {
19547
+ return cm;
19548
+ }
19549
+ }
19550
+ }
19551
+ throw new Error(`cannot find credit manager ${creditManager}`);
19448
19552
  }
19449
- #create(data) {
19450
- const contractType = bytes32ToString(
19451
- data.baseParams.contractType
19553
+ findByCreditManager(creditManager) {
19554
+ const market = Object.values(this.#markets).find(
19555
+ (m) => m.creditManagers.some(
19556
+ (cm) => cm.creditManager.address.toLowerCase() === creditManager.toLowerCase()
19557
+ )
19452
19558
  );
19453
- switch (contractType) {
19454
- case "PF_CHAINLINK_ORACLE":
19455
- return new ChainlinkPriceFeedContract(this.sdk, data);
19456
- case "PF_YEARN_ORACLE":
19457
- return new YearnPriceFeedContract(this.sdk, data);
19458
- case "PF_CURVE_STABLE_LP_ORACLE":
19459
- return new CurveStablePriceFeedContract(this.sdk, data);
19460
- case "PF_WSTETH_ORACLE":
19461
- return new WstETHPriceFeedContract(this.sdk, data);
19462
- case "PF_BOUNDED_ORACLE":
19463
- return new BoundedPriceFeedContract(this.sdk, data);
19464
- case "PF_COMPOSITE_ORACLE":
19465
- return new CompositePriceFeedContract(this.sdk, data);
19466
- case "PF_BALANCER_STABLE_LP_ORACLE":
19467
- return new BalancerStablePriceFeedContract(this.sdk, data);
19468
- case "PF_BALANCER_WEIGHTED_LP_ORACLE":
19469
- return new BalancerWeightedPriceFeedContract(this.sdk, data);
19470
- case "PF_CURVE_CRYPTO_LP_ORACLE":
19471
- return new CurveCryptoPriceFeedContract(this.sdk, data);
19472
- case "PF_REDSTONE_ORACLE":
19473
- return new RedstonePriceFeedContract(this.sdk, data);
19474
- case "PF_ERC4626_ORACLE":
19475
- return new Erc4626PriceFeedContract(this.sdk, data);
19476
- case "PF_CURVE_USD_ORACLE":
19477
- return new CurveUSDPriceFeedContract(this.sdk, data);
19478
- case "PF_ZERO_ORACLE":
19479
- return new ZeroPriceFeedContract(this.sdk, data);
19480
- case "PF_MELLOW_LRT_ORACLE":
19481
- return new MellowLRTPriceFeedContract(this.sdk, data);
19482
- default:
19483
- throw new Error(`Price feed type ${contractType} not supported, `);
19559
+ if (!market) {
19560
+ throw new Error(`cannot find market for credit manager ${creditManager}`);
19484
19561
  }
19562
+ return market;
19563
+ }
19564
+ get markets() {
19565
+ return Object.values(this.#markets);
19566
+ }
19567
+ async tvl() {
19568
+ const creditManagers = this.creditManagers;
19569
+ const tvls = await Promise.all(creditManagers.map((cm) => cm.tvl()));
19570
+ return tvls.reduce(
19571
+ (acc, curr) => {
19572
+ acc.tvl += curr.tvl;
19573
+ acc.tvlUSD += curr.tvlUSD;
19574
+ return acc;
19575
+ },
19576
+ { tvl: 0n, tvlUSD: 0n }
19577
+ );
19485
19578
  }
19486
19579
  };
19487
19580
 
19488
- // src/sdk/market/PriceOracleContract.ts
19489
- var PriceOracleContract = class extends BaseContract {
19490
- /**
19491
- * Underlying token of market to which this price oracle belongs
19492
- */
19493
- underlying;
19494
- /**
19495
- * Mapping Token => [PriceFeed Address, stalenessPeriod]
19496
- */
19497
- mainPriceFeeds = {};
19581
+ // src/sdk/accounts/CreditAccountsService.ts
19582
+ var CreditAccountsService = class extends SDKConstruct {
19583
+ #compressor;
19584
+ constructor(sdk) {
19585
+ super(sdk);
19586
+ this.#compressor = sdk.addressProvider.getLatestVersion(
19587
+ AP_CREDIT_ACCOUNT_COMPRESSOR
19588
+ );
19589
+ }
19498
19590
  /**
19499
- * Mapping Token => [PriceFeed Address, stalenessPeriod]
19591
+ * Returns single credit account data, or undefined if it's not found
19592
+ * Performs all necessary price feed updates under the hood
19593
+ * @param account
19594
+ * @returns
19500
19595
  */
19501
- reservePriceFeeds = {};
19596
+ async getCreditAccountData(account) {
19597
+ let raw;
19598
+ try {
19599
+ raw = await this.provider.publicClient.readContract({
19600
+ abi: iCreditAccountCompressorAbi,
19601
+ address: this.#compressor,
19602
+ functionName: "getCreditAccountData",
19603
+ args: [account]
19604
+ });
19605
+ } catch (e) {
19606
+ return void 0;
19607
+ }
19608
+ if (raw.success) {
19609
+ return raw;
19610
+ }
19611
+ const { txs: priceUpdateTxs, timestamp: _ } = await this.sdk.priceFeeds.generatePriceFeedsUpdateTxs();
19612
+ const resp = await simulateMulticall(this.provider.publicClient, {
19613
+ account: this.provider.account,
19614
+ contracts: [
19615
+ ...priceUpdateTxs.map(rawTxToMulticallPriceUpdate),
19616
+ {
19617
+ abi: iCreditAccountCompressorAbi,
19618
+ address: this.#compressor,
19619
+ functionName: "getCreditAccountData",
19620
+ args: [account]
19621
+ }
19622
+ ],
19623
+ allowFailure: false,
19624
+ gas: 550000000n,
19625
+ batchSize: 0
19626
+ // we cannot have price updates and compressor request in different batches
19627
+ });
19628
+ const cad = resp.pop();
19629
+ return cad;
19630
+ }
19502
19631
  /**
19503
- * Mapping Token => Price in underlying
19632
+ * Methods to get all credit accounts with some optional filtering
19633
+ * Performs all necessary price feed updates under the hood
19634
+ *
19635
+ * TODO: do we want to expose pagination?
19636
+ * TODO: do we want to expose "reverting"?
19637
+ * TODO: do we want to expose MarketFilter in any way? If so, we need to check that the MarketFilter is compatibled with attached markets?
19638
+ * @param args
19639
+ * @returns returned credit accounts are sorted by health factor in ascending order
19504
19640
  */
19505
- mainPrices = new AddressMap();
19641
+ async getCreditAccounts(args) {
19642
+ const {
19643
+ creditManager,
19644
+ includeZeroDebt = false,
19645
+ maxHealthFactor = 65535,
19646
+ // TODO: this will change to bigint
19647
+ minHealthFactor = 0,
19648
+ owner = ADDRESS_0X0
19649
+ } = args ?? {};
19650
+ const arg0 = creditManager ?? {
19651
+ curators: [],
19652
+ pools: this.pools,
19653
+ underlying: ADDRESS_0X0
19654
+ };
19655
+ const caFilter = {
19656
+ owner,
19657
+ includeZeroDebt,
19658
+ minHealthFactor,
19659
+ maxHealthFactor
19660
+ };
19661
+ const { txs: priceUpdateTxs, timestamp: _ } = await this.sdk.priceFeeds.generatePriceFeedsUpdateTxs();
19662
+ const allCAs = [];
19663
+ for (const reverting of [false, true]) {
19664
+ let offset = 0n;
19665
+ do {
19666
+ const [accounts, newOffset] = await this.#getCreditAccounts(
19667
+ [arg0, { ...caFilter, reverting }, offset],
19668
+ priceUpdateTxs
19669
+ );
19670
+ allCAs.push(...accounts);
19671
+ offset = newOffset;
19672
+ } while (offset !== 0n);
19673
+ }
19674
+ return allCAs.sort((a, b) => Number(a.healthFactor - b.healthFactor));
19675
+ }
19506
19676
  /**
19507
- * Mapping Token => Price in underlying
19677
+ * Generates transaction to liquidate credit account
19678
+ * @param account
19679
+ * @param to Address to transfer underlying left after liquidation
19680
+ * @param slippage
19681
+ * @returns
19508
19682
  */
19509
- reservePrices = new AddressMap();
19510
- #priceFeedTree;
19511
- constructor(sdk, data, underlying) {
19512
- super(sdk, {
19513
- ...data.baseParams,
19514
- name: "PriceOracleV3",
19515
- abi: priceOracleV3Abi
19516
- });
19517
- this.underlying = underlying;
19518
- const { priceFeedMapping, priceFeedStructure } = data;
19519
- this.#priceFeedTree = priceFeedStructure;
19520
- for (const node of priceFeedStructure) {
19521
- sdk.priceFeeds.create(node);
19683
+ async fullyLiquidate(account, to, slippage = 50n) {
19684
+ const cm = this.sdk.marketRegister.findCreditManager(account.creditManager);
19685
+ const preview = await this.sdk.router.findBestClosePath(
19686
+ account,
19687
+ cm.creditManager,
19688
+ slippage
19689
+ );
19690
+ const priceUpdates = await this.getPriceUpdatesForFacade(account);
19691
+ const recipient = to ?? this.sdk.provider.account;
19692
+ if (!recipient) {
19693
+ throw new Error("liquidate account: assets recipient not specied");
19522
19694
  }
19523
- priceFeedMapping.forEach((node) => {
19524
- const { token, priceFeed, reserve, stalenessPeriod } = node;
19525
- const ref = new PriceFeedRef(sdk, priceFeed, stalenessPeriod);
19526
- const price = this.#priceFeedTree.find(
19527
- (n) => n.baseParams.addr === priceFeed
19528
- )?.answer?.price;
19529
- if (reserve) {
19530
- this.reservePriceFeeds[token] = ref;
19531
- if (price) {
19532
- this.reservePrices.upsert(token, price);
19533
- }
19534
- } else {
19535
- this.mainPriceFeeds[token] = ref;
19536
- if (price) {
19537
- this.mainPrices.upsert(token, price);
19538
- }
19539
- }
19540
- this.#labelPriceFeed(priceFeed, reserve ? "Reserve" : "Main", token);
19695
+ return cm.creditFacade.createRawTx({
19696
+ functionName: "liquidateCreditAccount",
19697
+ args: [
19698
+ account.creditAccount,
19699
+ recipient,
19700
+ [...priceUpdates, ...preview.calls]
19701
+ ],
19702
+ description: `fully liquidate ${account.creditAccount}`
19541
19703
  });
19542
- this.logger?.debug(
19543
- `Got ${Object.keys(this.mainPriceFeeds).length} main and ${Object.keys(this.reservePriceFeeds).length} reserve price feeds`
19544
- );
19545
19704
  }
19546
19705
  /**
19547
- * Returns main and reserve price feeds for given tokens
19548
- * @param tokens
19549
- * @param opts Option to include main/reserve feeds only, defaults to both
19706
+ * Closes credit account or sets debt to zero (but keep account)
19707
+ * @param operation
19708
+ * @param ca
19709
+ * @param assetsToKeep Tokens to withdraw from credit account
19710
+ * @param to Address to withdraw underlying to
19711
+ * @param slippage
19550
19712
  * @returns
19551
19713
  */
19552
- priceFeedsForTokens(tokens, opts) {
19553
- const main = opts?.main ?? true;
19554
- const reserve = opts?.reserve ?? true;
19555
- return tokens.flatMap((t) => [
19556
- main ? this.mainPriceFeeds[t].priceFeed : void 0,
19557
- reserve ? this.reservePriceFeeds[t].priceFeed : void 0
19558
- ]).filter((f) => !!f);
19714
+ async closeCreditAccount(operation, ca, assetsToKeep, to, slippage = 50n) {
19715
+ const cm = this.sdk.marketRegister.findCreditManager(ca.creditManager);
19716
+ const recipient = to ?? this.sdk.provider.account;
19717
+ if (!recipient) {
19718
+ throw new Error("close account: assets recipient not specied");
19719
+ }
19720
+ const calls = await this.#prepareCloseCreditAccount(
19721
+ ca,
19722
+ cm,
19723
+ assetsToKeep,
19724
+ recipient,
19725
+ slippage
19726
+ );
19727
+ return cm.creditFacade.createRawTx({
19728
+ functionName: operation === "close" ? "closeCreditAccount" : "multicall",
19729
+ args: [ca.creditAccount, calls],
19730
+ description: `${operation} account ${ca.creditAccount}`
19731
+ });
19559
19732
  }
19560
19733
  /**
19561
- * Generates updates for all updateable price feeds in this oracle (including dependencies)
19734
+ * Internal wrapper for CreditAccountCompressor.getCreditAccounts + price updates wrapped into multicall
19735
+ * @param args
19736
+ * @param priceUpdateTxs
19562
19737
  * @returns
19563
19738
  */
19564
- async updatePriceFeeds() {
19565
- const updatables = [];
19566
- for (const node of this.#priceFeedTree) {
19567
- if (node.updatable) {
19568
- updatables.push(this.sdk.priceFeeds.mustGet(node.baseParams.addr));
19569
- }
19739
+ async #getCreditAccounts(args, priceUpdateTxs) {
19740
+ if (priceUpdateTxs?.length) {
19741
+ const resp = await simulateMulticall(this.provider.publicClient, {
19742
+ account: this.provider.account,
19743
+ contracts: [
19744
+ ...priceUpdateTxs.map(rawTxToMulticallPriceUpdate),
19745
+ {
19746
+ abi: iCreditAccountCompressorAbi,
19747
+ address: this.#compressor,
19748
+ functionName: "getCreditAccounts",
19749
+ args
19750
+ }
19751
+ ],
19752
+ allowFailure: false,
19753
+ gas: 550000000n,
19754
+ batchSize: 0
19755
+ // we cannot have price updates and compressor request in different batches
19756
+ });
19757
+ const getCreditAccountsResp = resp.pop();
19758
+ return getCreditAccountsResp;
19570
19759
  }
19571
- return this.sdk.priceFeeds.generatePriceFeedsUpdateTxs(updatables);
19760
+ return this.provider.publicClient.readContract({
19761
+ abi: iCreditAccountCompressorAbi,
19762
+ address: this.#compressor,
19763
+ functionName: "getCreditAccounts",
19764
+ args
19765
+ });
19572
19766
  }
19573
19767
  /**
19574
- * Converts previously obtained price updates into CreditFacade multicall entries
19575
- * @param creditFacade
19576
- * @param updates
19768
+ * Returns raw txs that are needed to update all price feeds so that all credit accounts (possibly from different markets) compute
19769
+ * @param accounts
19577
19770
  * @returns
19578
19771
  */
19579
- onDemandPriceUpdates(updates) {
19580
- const result = [];
19581
- if (!updates) {
19582
- return result;
19772
+ async getUpdateForAccounts(accounts) {
19773
+ const tokensByPool = /* @__PURE__ */ new Map();
19774
+ const oracleByPool = /* @__PURE__ */ new Map();
19775
+ for (const acc of accounts) {
19776
+ const market = this.sdk.marketRegister.findByCreditManager(
19777
+ acc.creditManager
19778
+ );
19779
+ const pool = market.state.pool.pool.address;
19780
+ oracleByPool.set(pool, market.priceOracle);
19781
+ for (const t of acc.tokens) {
19782
+ if (t.balance > 10n) {
19783
+ const tokens = tokensByPool.get(pool) ?? /* @__PURE__ */ new Set();
19784
+ tokens.add(t.token);
19785
+ tokensByPool.set(pool, tokens);
19786
+ }
19787
+ }
19583
19788
  }
19584
- const { txs } = updates;
19585
- for (const tx of txs) {
19586
- const { to: priceFeed, callData } = tx;
19587
- const [token, reserve] = this.#findTokenForPriceFeed(priceFeed);
19588
- const { args } = viem.decodeFunctionData({
19589
- abi: iUpdatablePriceFeedAbi,
19590
- data: callData
19591
- });
19592
- const data = args[0];
19593
- result.push({
19594
- token,
19595
- reserve,
19596
- data
19597
- });
19789
+ const priceFeeds = [];
19790
+ for (const [pool, priceFeedFactory] of oracleByPool.entries()) {
19791
+ const tokens = Array.from(tokensByPool.get(pool) ?? []);
19792
+ priceFeeds.push(...priceFeedFactory.priceFeedsForTokens(tokens));
19598
19793
  }
19599
- return result;
19794
+ return this.sdk.priceFeeds.generatePriceFeedsUpdateTxs(priceFeeds);
19600
19795
  }
19601
19796
  /**
19602
- * Tries to convert amount of token into underlying of current market
19603
- * @param token
19604
- * @param amount
19605
- * @param reserve
19797
+ * Returns account price updates in a non-encoded format
19798
+ * @param acc
19606
19799
  * @returns
19607
19800
  */
19608
- convertToUnderlying(token, amount, reserve = false) {
19609
- return this.convert(token, this.underlying, amount, reserve);
19801
+ async getOnDemandPriceUpdates(acc) {
19802
+ const market = this.sdk.marketRegister.findByCreditManager(
19803
+ acc.creditManager
19804
+ );
19805
+ const update = await this.getUpdateForAccounts([acc]);
19806
+ return market.priceOracle.onDemandPriceUpdates(update);
19610
19807
  }
19611
19808
  /**
19612
- * Tries to convert amount of from one token to another, using latest known prices
19613
- * @param from
19614
- * @param to
19615
- * @param amount
19616
- * @param reserve
19809
+ * Returns price updates in format that is accepted by various credit facade methods (multicall, close/liquidate, etc...)
19810
+ * @param acc
19811
+ * @returns
19617
19812
  */
19618
- convert(from, to, amount, reserve = false) {
19619
- if (from === to) {
19620
- return amount;
19813
+ async getPriceUpdatesForFacade(acc) {
19814
+ const cm = this.sdk.marketRegister.findCreditManager(acc.creditManager);
19815
+ const updates = await this.getOnDemandPriceUpdates(acc);
19816
+ return updates.map(({ token, reserve, data }) => ({
19817
+ target: cm.creditFacade.address,
19818
+ callData: viem.encodeFunctionData({
19819
+ abi: iCreditFacadeV3MulticallAbi,
19820
+ functionName: "onDemandPriceUpdate",
19821
+ args: [token, reserve, data]
19822
+ })
19823
+ }));
19824
+ }
19825
+ async #prepareCloseCreditAccount(ca, cm, assetsToKeep, to, slippage = 50n) {
19826
+ const closePath = await this.sdk.router.findBestClosePath(
19827
+ ca,
19828
+ cm.creditManager,
19829
+ slippage
19830
+ );
19831
+ const priceUpdates = await this.getPriceUpdatesForFacade(ca);
19832
+ return [
19833
+ ...priceUpdates,
19834
+ ...closePath.calls,
19835
+ ...this.#prepareDisableQuotas(ca),
19836
+ ...this.#prepareDecreaseDebt(ca),
19837
+ ...this.#prepareDisableTokens(ca),
19838
+ ...assetsToKeep.map(
19839
+ (t) => this.#prepareWithdrawToken(ca, t, MAX_UINT256, to)
19840
+ )
19841
+ ];
19842
+ }
19843
+ #prepareDisableQuotas(ca) {
19844
+ const calls = [];
19845
+ for (const { token, quota } of ca.tokens) {
19846
+ if (quota > 0n) {
19847
+ calls.push({
19848
+ target: ca.creditFacade,
19849
+ callData: viem.encodeFunctionData({
19850
+ abi: iCreditFacadeV3MulticallAbi,
19851
+ functionName: "updateQuota",
19852
+ args: [token, MIN_INT96, 0n]
19853
+ })
19854
+ });
19855
+ }
19621
19856
  }
19622
- const fromPrice = reserve ? this.reservePrices.mustGet(from) : this.mainPrices.mustGet(from);
19623
- const fromScale = 10n ** BigInt(this.sdk.marketRegister.tokensMeta.mustGet(from).decimals);
19624
- const toPrice = reserve ? this.reservePrices.mustGet(to) : this.mainPrices.mustGet(to);
19625
- const toScale = 10n ** BigInt(this.sdk.marketRegister.tokensMeta.mustGet(to).decimals);
19626
- return amount * fromPrice * toScale / (toPrice * fromScale);
19857
+ return calls;
19627
19858
  }
19628
- // async loadPrices(
19629
- // priceUpdatesTxs: Array<RawTx>,
19630
- // block: bigint,
19631
- // ): Promise<{
19632
- // mainPrices: Record<Address, bigint>;
19633
- // reservePrices: Record<Address, bigint>;
19634
- // }> {
19635
- // const priceUpdateCalls: Array<MultiCallStruct> = priceUpdatesTxs.map(
19636
- // tx => ({
19637
- // target: tx.to,
19638
- // callData: tx.callData,
19639
- // allowFailure: false,
19640
- // }),
19641
- // );
19642
- // const getPricesRawCalls = (reserve: boolean): Array<MultiCallStruct> => {
19643
- // return Object.keys(
19644
- // reserve ? this.reservePriceFeeds : this.mainPriceFeeds,
19645
- // ).map(token => ({
19646
- // target: this.address,
19647
- // callData: encodeFunctionData({
19648
- // functionName: "getPriceRaw",
19649
- // args: [token as Address, reserve],
19650
- // abi: this.abi,
19651
- // }),
19652
- // allowFailure: true,
19653
- // }));
19654
- // };
19655
- // const { result } = await this.v3.publicClient.simulateContract({
19656
- // address: MULTICALL_ADDRESS,
19657
- // abi: multicall3Abi,
19658
- // functionName: "aggregate3",
19659
- // args: [
19660
- // [
19661
- // ...priceUpdateCalls,
19662
- // ...getPricesRawCalls(false),
19663
- // ...getPricesRawCalls(true),
19664
- // ],
19665
- // ],
19666
- // chain: this.v3.publicClient.chain!,
19667
- // account: this.v3.walletClient.account!,
19668
- // gas: 550_000_000n,
19669
- // // blockNumber: BigInt(block),
19670
- // });
19671
- // const returnRawPrices = (
19672
- // result as Array<{ success: boolean; returnData: Hex }>
19673
- // ).slice(priceUpdateCalls.length);
19674
- // const prices = returnRawPrices.map(callReturn =>
19675
- // callReturn.success
19676
- // ? decodeFunctionResult({
19677
- // functionName: "getPrice",
19678
- // abi: this.abi,
19679
- // data: callReturn.returnData! as Hex,
19680
- // })
19681
- // : 0n,
19682
- // ) as Array<bigint>;
19683
- // const mainPrices: Record<Address, bigint> = {};
19684
- // const reservePrices: Record<Address, bigint> = {};
19685
- // const mainPFlength = Object.keys(this.mainPriceFeeds).length;
19686
- // prices.forEach((price, i) => {
19687
- // if (i < mainPFlength) {
19688
- // mainPrices[Object.keys(this.mainPriceFeeds)[i] as Address] = price;
19689
- // } else {
19690
- // reservePrices[
19691
- // Object.keys(this.reservePriceFeeds)[i - mainPFlength] as Address
19692
- // ] = price;
19693
- // }
19694
- // });
19695
- // return { mainPrices, reservePrices };
19696
- // }
19697
- #labelPriceFeed(address, usage, token) {
19698
- this.sdk.provider.addressLabels.set(address, (label) => {
19699
- const { symbol } = this.sdk.marketRegister.tokensMeta.mustGet(token);
19700
- let pricefeedTag = `${symbol}.${usage}`;
19701
- if (label) {
19702
- pricefeedTag = `${label}, ${pricefeedTag}`;
19859
+ #prepareDecreaseDebt(ca) {
19860
+ if (ca.totalDebtUSD > 0n) {
19861
+ return [
19862
+ {
19863
+ target: ca.creditFacade,
19864
+ callData: viem.encodeFunctionData({
19865
+ abi: iCreditFacadeV3MulticallAbi,
19866
+ functionName: "decreaseDebt",
19867
+ args: [MAX_UINT256]
19868
+ })
19869
+ }
19870
+ ];
19871
+ }
19872
+ return [];
19873
+ }
19874
+ #prepareDisableTokens(ca) {
19875
+ const calls = [];
19876
+ for (const t of ca.tokens) {
19877
+ if (t.token !== ca.underlying && (t.mask & ca.enabledTokensMask) !== 0n && t.quota === 0n) {
19878
+ calls.push({
19879
+ target: ca.creditFacade,
19880
+ callData: viem.encodeFunctionData({
19881
+ abi: iCreditFacadeV3MulticallAbi,
19882
+ functionName: "disableToken",
19883
+ args: [t.token]
19884
+ })
19885
+ });
19703
19886
  }
19704
- return pricefeedTag;
19705
- });
19887
+ }
19888
+ return calls;
19889
+ }
19890
+ #prepareWithdrawToken(ca, token, amount, to) {
19891
+ return {
19892
+ target: ca.creditFacade,
19893
+ callData: viem.encodeFunctionData({
19894
+ abi: iCreditFacadeV3MulticallAbi,
19895
+ functionName: "withdrawCollateral",
19896
+ args: [token, amount, to]
19897
+ })
19898
+ };
19706
19899
  }
19707
19900
  /**
19708
- * Helper method to find "attachment point" of price feed (makes sense for updatable price feeds only) - token (in v3.0 can be ticker) and main/reserve flag
19709
- *
19710
- * @param priceFeed
19711
- * @returns
19901
+ * Returns addresses of pools of attached markets
19712
19902
  */
19713
- #findTokenForPriceFeed(priceFeed) {
19714
- for (const [token, pf] of Object.entries(this.mainPriceFeeds)) {
19715
- if (pf.address === priceFeed) {
19716
- return [token, false];
19903
+ get pools() {
19904
+ return this.sdk.marketRegister.poolState.map((p) => p.pool.address);
19905
+ }
19906
+ };
19907
+ var AddressProviderContractV3_1 = class extends BaseContract {
19908
+ #addresses = {};
19909
+ versions = {};
19910
+ latest = {};
19911
+ constructor(sdk, address) {
19912
+ super(sdk, {
19913
+ addr: address,
19914
+ name: "AddressProviderV3",
19915
+ abi: iAddressProviderV3_1Abi
19916
+ });
19917
+ }
19918
+ parseFunctionParams(params) {
19919
+ switch (params.functionName) {
19920
+ case "setAddress": {
19921
+ if (params.args.length !== 3) {
19922
+ const [key2, saveVersion2] = params.args;
19923
+ return [key2, `${saveVersion2}`];
19924
+ }
19925
+ const [key, value, saveVersion] = params.args;
19926
+ return [viem.bytesToString(viem.toBytes(key)), value, `${saveVersion}`];
19717
19927
  }
19928
+ default:
19929
+ return void 0;
19718
19930
  }
19719
- for (const [token, pf] of Object.entries(this.reservePriceFeeds)) {
19720
- if (pf.address === priceFeed) {
19721
- return [token, true];
19722
- }
19931
+ }
19932
+ setInternalAddress(key, address, version) {
19933
+ if (!this.#addresses[key]) {
19934
+ this.#addresses[key] = {};
19935
+ }
19936
+ this.#addresses[key][version] = address;
19937
+ if (!this.latest[key] || version > this.latest[key]) {
19938
+ this.latest[key] = version;
19723
19939
  }
19724
- throw new Error(`cannot find token for price feed ${priceFeed}`);
19725
- }
19726
- get state() {
19727
- return {
19728
- priceOracleV3: this.contractData,
19729
- mainPriceFeeds: Object.fromEntries(
19730
- Object.entries(this.mainPriceFeeds).map(([token, v]) => [
19731
- token,
19732
- v.state
19733
- ])
19734
- ),
19735
- reservePriceFeeds: Object.fromEntries(
19736
- Object.entries(this.reservePriceFeeds).map(([token, v]) => [
19737
- token,
19738
- v.state
19739
- ])
19740
- )
19741
- };
19940
+ if (!this.versions[key]) {
19941
+ this.versions[key] = /* @__PURE__ */ new Set();
19942
+ }
19943
+ this.versions[key].add(version);
19742
19944
  }
19743
- };
19744
-
19745
- // src/sdk/market/MarketFactory.ts
19746
- var MarketFactory = class {
19747
- riskCurator;
19748
- poolFactory;
19749
- priceOracle;
19750
- creditManagers = [];
19751
- constructor(sdk, marketData) {
19752
- this.riskCurator = marketData.owner;
19753
- for (const t of marketData.tokens) {
19754
- sdk.marketRegister.tokensMeta.upsert(t.addr, t);
19755
- sdk.provider.addressLabels.set(t.addr, t.symbol);
19945
+ getAddress(contract, version = NO_VERSION) {
19946
+ if (!this.#addresses[contract]) {
19947
+ throw new Error(`Address ${contract}, version: ${version} not found`);
19756
19948
  }
19757
- this.poolFactory = new PoolFactory(sdk, marketData);
19758
- for (let i = 0; i < marketData.creditManagers.length; i++) {
19759
- this.creditManagers.push(new CreditFactory(sdk, marketData, i));
19949
+ const result = this.#addresses[contract][version];
19950
+ if (!result) {
19951
+ throw new Error(`Address ${contract}, version: ${version} not found`);
19760
19952
  }
19761
- this.priceOracle = new PriceOracleContract(
19762
- sdk,
19763
- marketData.priceOracleData,
19764
- marketData.pool.underlying
19953
+ return result;
19954
+ }
19955
+ getLatestVersion(contract) {
19956
+ if (!this.latest[contract]) {
19957
+ throw new Error(`Latest version for ${contract} not found`);
19958
+ }
19959
+ this.logger?.debug(
19960
+ `Latest version found for ${contract} : ${this.latest[contract]}`
19765
19961
  );
19962
+ return this.getAddress(contract, this.latest[contract]);
19963
+ }
19964
+ async fetchState(toBlock) {
19965
+ const entries = await this.contract.read.getAllSavedContracts({
19966
+ blockNumber: toBlock
19967
+ });
19968
+ entries.forEach((log) => {
19969
+ this.setInternalAddress(log.key, log.value, Number(log.version));
19970
+ });
19971
+ this.version = 310;
19766
19972
  }
19767
19973
  get state() {
19768
19974
  return {
19769
- pool: this.poolFactory.state,
19770
- creditManagers: this.creditManagers.map((cm) => cm.state),
19771
- priceOracle: this.priceOracle.state
19975
+ ...this.contractData,
19976
+ addresses: this.#addresses
19772
19977
  };
19773
19978
  }
19979
+ parseLog(log) {
19980
+ const parsedLog = viem.parseEventLogs({
19981
+ abi: this.abi,
19982
+ logs: [log]
19983
+ })[0];
19984
+ switch (parsedLog.eventName) {
19985
+ case "SetAddress": {
19986
+ const parsedLog2 = viem.parseEventLogs({
19987
+ abi: this.abi,
19988
+ eventName: "SetAddress",
19989
+ logs: [log]
19990
+ })[0];
19991
+ const key = parsedLog2.args.key;
19992
+ this.setInternalAddress(
19993
+ key,
19994
+ parsedLog.args.value,
19995
+ Number(parsedLog2.args.version)
19996
+ );
19997
+ break;
19998
+ }
19999
+ default:
20000
+ this.logger?.warn(`Unknown event: ${parsedLog.eventName}`);
20001
+ break;
20002
+ }
20003
+ }
19774
20004
  };
19775
-
19776
- // src/sdk/market/MarketRegister.ts
19777
- var MarketRegister = class {
19778
- #logger;
19779
- /**
19780
- * Mapping pool.name -> MarketFactory
19781
- */
19782
- #markets = {};
19783
- /**
19784
- * Token metadata such as symbol and decimals, common across all markets
19785
- */
19786
- tokensMeta = new AddressMap();
19787
- #sdk;
19788
- constructor(sdk) {
19789
- this.#sdk = sdk;
19790
- this.#logger = childLogger("MarketRegister", sdk.logger);
20005
+ var BotListContract = class extends BaseContract {
20006
+ approvedCreditManagers = /* @__PURE__ */ new Set();
20007
+ constructor(sdk, address) {
20008
+ super(sdk, { addr: address, name: "BotListV3", abi: botListV3Abi });
19791
20009
  }
19792
- async loadMarkets(curators) {
19793
- this.#logger?.debug("loading markets");
19794
- const marketCompressorAddress = this.#sdk.addressProvider.getAddress(
19795
- AP_MARKET_COMPRESSOR,
19796
- 310
19797
- );
19798
- const markets = await this.#sdk.provider.publicClient.readContract({
19799
- address: marketCompressorAddress,
19800
- abi: iMarketCompressorAbi,
19801
- functionName: "getMarkets",
19802
- args: [
19803
- {
19804
- curators,
19805
- pools: [],
19806
- underlying: ADDRESS_0X0
19807
- }
19808
- ],
19809
- // It's passed as ...rest in viem readContract action, but this might change
19810
- // @ts-ignore
19811
- gas: 500000000n
19812
- });
19813
- for (const data of markets) {
19814
- this.#markets[data.pool.name] = new MarketFactory(this.#sdk, data);
20010
+ parseFunctionParams(params) {
20011
+ switch (params.functionName) {
20012
+ case "setCreditManagerApprovedStatus": {
20013
+ const [creditManager, status] = params.args;
20014
+ return [this.addressLabels.get(creditManager), `${status}`];
20015
+ }
20016
+ case "setBotSpecialPermissions": {
20017
+ const [bot, creditManager, permissions] = params.args;
20018
+ return [
20019
+ this.addressLabels.get(bot),
20020
+ this.addressLabels.get(creditManager),
20021
+ botPermissionsToString(permissions)
20022
+ ];
20023
+ }
20024
+ default:
20025
+ return void 0;
19815
20026
  }
19816
- this.#logger?.info(`loaded ${markets.length} markets`);
19817
20027
  }
19818
- get state() {
19819
- return Object.values(this.#markets).map((market) => market.state);
19820
- }
19821
- get poolState() {
19822
- return Object.values(this.#markets).map((market) => market.poolFactory.state);
20028
+ async fetchState(toBlock) {
20029
+ const logs = await this.provider.publicClient.getContractEvents({
20030
+ address: this.address,
20031
+ abi: this.abi,
20032
+ fromBlock: 0n,
20033
+ toBlock
20034
+ });
20035
+ logs.forEach((e) => this.parseLog(e));
19823
20036
  }
19824
- get creditManagerState() {
19825
- return Object.values(this.#markets).flatMap(
19826
- (market) => market.creditManagers.map((cm) => cm.state)
19827
- );
20037
+ parseLog(log) {
20038
+ const parsedLog = viem.parseEventLogs({
20039
+ abi: this.abi,
20040
+ logs: [log]
20041
+ })[0];
20042
+ switch (parsedLog.eventName) {
20043
+ case "SetCreditManagerApprovedStatus":
20044
+ if (parsedLog.args.approved) {
20045
+ this.approvedCreditManagers.add(parsedLog.args.creditManager);
20046
+ } else {
20047
+ this.approvedCreditManagers.delete(parsedLog.args.creditManager);
20048
+ }
20049
+ break;
20050
+ case "SetBotSpecialPermissions":
20051
+ this.logger?.debug(
20052
+ `Bot ${parsedLog.args.bot} has been given permissions ${botPermissionsToString(
20053
+ parsedLog.args.permissions
20054
+ )} for credit manager ${parsedLog.args.creditManager}`
20055
+ );
20056
+ break;
20057
+ default:
20058
+ this.logger?.warn(`Unknown event: ${parsedLog.eventName}`);
20059
+ break;
20060
+ }
19828
20061
  }
19829
- getPoolFactories() {
19830
- return this.markets.map((market) => market.poolFactory);
20062
+ get state() {
20063
+ return {
20064
+ ...this.contractData
20065
+ };
19831
20066
  }
19832
- get creditManagers() {
19833
- return this.markets.flatMap((market) => market.creditManagers);
20067
+ };
20068
+
20069
+ // src/sdk/core/GearStakingV3Contract.ts
20070
+ var GearStakingContract = class extends BaseContract {
20071
+ constructor(sdk, address) {
20072
+ super(sdk, { addr: address, name: "GearStakingV3", abi: gearStakingV3Abi });
19834
20073
  }
19835
- findCreditManager(creditManager) {
19836
- for (const market of this.markets) {
19837
- for (const cm of market.creditManagers) {
19838
- if (cm.creditManager.address === creditManager) {
19839
- return cm;
19840
- }
20074
+ parseFunctionParams(params) {
20075
+ switch (params.functionName) {
20076
+ case "setVotingContractStatus": {
20077
+ const [address, status] = params.args;
20078
+ return [this.addressLabels.get(address), VotingContractStatus[status]];
19841
20079
  }
20080
+ default:
20081
+ return void 0;
19842
20082
  }
19843
- throw new Error(`cannot find credit manager ${creditManager}`);
19844
- }
19845
- findByCreditManager(creditManager) {
19846
- const market = Object.values(this.#markets).find(
19847
- (m) => m.creditManagers.some(
19848
- (cm) => cm.creditManager.address.toLowerCase() === creditManager.toLowerCase()
19849
- )
19850
- );
19851
- if (!market) {
19852
- throw new Error(`cannot find market for credit manager ${creditManager}`);
19853
- }
19854
- return market;
19855
20083
  }
19856
- get markets() {
19857
- return Object.values(this.#markets);
19858
- }
19859
- async tvl() {
19860
- const creditManagers = this.creditManagers;
19861
- const tvls = await Promise.all(creditManagers.map((cm) => cm.tvl()));
19862
- return tvls.reduce(
19863
- (acc, curr) => {
19864
- acc.tvl += curr.tvl;
19865
- acc.tvlUSD += curr.tvlUSD;
19866
- return acc;
19867
- },
19868
- { tvl: 0n, tvlUSD: 0n }
19869
- );
20084
+ get state() {
20085
+ return {
20086
+ ...this.contractData,
20087
+ successor: ADDRESS_0X0,
20088
+ migrator: ADDRESS_0X0
20089
+ };
19870
20090
  }
19871
20091
  };
19872
20092
  var PathOptionFactory = class _PathOptionFactory {
@@ -20809,6 +21029,7 @@ exports.AP_INSOLVENCY_CHECKER = AP_INSOLVENCY_CHECKER;
20809
21029
  exports.AP_MARKET_COMPRESSOR = AP_MARKET_COMPRESSOR;
20810
21030
  exports.AP_MULTI_PAUSE = AP_MULTI_PAUSE;
20811
21031
  exports.AP_PARTIAL_LIQUIDATION_BOT = AP_PARTIAL_LIQUIDATION_BOT;
21032
+ exports.AP_PRICE_FEED_COMPRESSOR = AP_PRICE_FEED_COMPRESSOR;
20812
21033
  exports.AP_PRICE_ORACLE = AP_PRICE_ORACLE;
20813
21034
  exports.AP_ROUTER = AP_ROUTER;
20814
21035
  exports.AP_TREASURY = AP_TREASURY;
@@ -20908,5 +21129,6 @@ exports.json_parse = json_parse;
20908
21129
  exports.json_stringify = json_stringify;
20909
21130
  exports.numberWithCommas = numberWithCommas;
20910
21131
  exports.percentFmt = percentFmt;
21132
+ exports.rawTxToMulticallPriceUpdate = rawTxToMulticallPriceUpdate;
20911
21133
  exports.simulateMulticall = simulateMulticall;
20912
21134
  exports.toHumanFormat = toHumanFormat;