@1delta/margin-fetcher 5.0.46 → 5.0.47

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.ts CHANGED
@@ -273,7 +273,19 @@ interface MulticallRetryParams {
273
273
  logErrors?: boolean;
274
274
  }
275
275
  type MulticallRetryFunction = (params: MulticallRetryParams) => Promise<any[]>;
276
- type GetEvmClientFunction = (chain: string, rpcId?: number) => PublicClient;
276
+ /**
277
+ * Options a caller may ask the client factory for.
278
+ *
279
+ * `timeoutMs` is optional in both directions: a factory that ignores it still
280
+ * satisfies this type (TypeScript accepts a 2-arg function here), and every
281
+ * existing implementation does exactly that. Only the sharded multicall passes
282
+ * it, and only because viem's 10s default turns one stalled request into a 10s
283
+ * response for the whole batch.
284
+ */
285
+ interface GetEvmClientOptions {
286
+ timeoutMs?: number;
287
+ }
288
+ type GetEvmClientFunction = (chain: string, rpcId?: number, options?: GetEvmClientOptions) => PublicClient;
277
289
 
278
290
  type SerializedBigNumber = string;
279
291
  interface LenderUserQuery {
@@ -5623,8 +5635,15 @@ interface FlashLoanLiquidityForAsset {
5623
5635
  source: string;
5624
5636
  fee: string;
5625
5637
  availableRaw: string;
5626
- available: number;
5627
- decimals: number;
5638
+ /**
5639
+ * `availableRaw` scaled by `decimals`. ABSENT when the asset's decimals
5640
+ * could not be resolved — neither from the token list nor from an on-chain
5641
+ * `decimals()` — because a guessed scale silently misreports liquidity by
5642
+ * orders of magnitude. `availableRaw` is always exact; prefer it.
5643
+ */
5644
+ available?: number;
5645
+ /** ABSENT when unresolvable — see `available`. */
5646
+ decimals?: number;
5628
5647
  }
5629
5648
  type FlashLiquiditiesOnChain = {
5630
5649
  [asset: string]: FlashLoanLiquidityForAsset[];
package/dist/index.js CHANGED
@@ -14976,8 +14976,46 @@ var ComptrollerAbi = [
14976
14976
  outputs: [{ internalType: "uint256", name: "", type: "uint256" }],
14977
14977
  stateMutability: "view",
14978
14978
  type: "function"
14979
+ },
14980
+ // Per-market pause guardians. Classic Compound V2 keeps them as public
14981
+ // mappings on the Comptroller — the cToken lenses do NOT expose them, so a
14982
+ // fork on the standard lens path can only learn its pause state from here.
14983
+ // `mintAllowed` / `borrowAllowed` hard-revert ("mint is paused" /
14984
+ // "borrow is paused") when set, which is why they gate `depositsEnabled` /
14985
+ // `borrowingEnabled` rather than merely capping an amount.
14986
+ {
14987
+ inputs: [{ internalType: "address", name: "", type: "address" }],
14988
+ name: "mintGuardianPaused",
14989
+ outputs: [{ internalType: "bool", name: "", type: "bool" }],
14990
+ stateMutability: "view",
14991
+ type: "function"
14992
+ },
14993
+ {
14994
+ inputs: [{ internalType: "address", name: "", type: "address" }],
14995
+ name: "borrowGuardianPaused",
14996
+ outputs: [{ internalType: "bool", name: "", type: "bool" }],
14997
+ stateMutability: "view",
14998
+ type: "function"
14979
14999
  }
14980
15000
  ];
15001
+ var COMPTROLLER_PAUSE_READ_LENDERS = [
15002
+ Lender.COMPOUND_V2,
15003
+ Lender.CREAM_FINANCE,
15004
+ Lender.FLUX_FINANCE,
15005
+ Lender.WE_PIGGY,
15006
+ Lender.GAMMA,
15007
+ Lender.CAPY_FI,
15008
+ Lender.LODESTAR,
15009
+ Lender.TENDER
15010
+ ];
15011
+ var usesComptrollerPauseReads = (lender) => COMPTROLLER_PAUSE_READ_LENDERS.includes(lender);
15012
+ var parsePausedFlag = (raw) => typeof raw === "boolean" ? raw : void 0;
15013
+ var resolveMarketPause = (token, live) => {
15014
+ const mintPaused = live?.mintPaused ?? token?.mintPaused;
15015
+ const borrowPaused = live?.borrowPaused ?? token?.borrowPaused;
15016
+ if (mintPaused === void 0 && borrowPaused === void 0) return void 0;
15017
+ return { mintPaused, borrowPaused };
15018
+ };
14981
15019
 
14982
15020
  // src/lending/public-data/compound-v2/publicCallBuild.ts
14983
15021
  var buildCompoundV2StyleLenderReserveCall = (chainId, lender) => {
@@ -15058,6 +15096,18 @@ var buildCompoundV2StyleLenderReserveCall = (chainId, lender) => {
15058
15096
  name: "liquidationIncentiveMantissa",
15059
15097
  params: []
15060
15098
  });
15099
+ if (usesComptrollerPauseReads(lender)) {
15100
+ for (const name of ["mintGuardianPaused", "borrowGuardianPaused"]) {
15101
+ for (const tk of tokens) {
15102
+ calls.push({
15103
+ abi: ComptrollerAbi,
15104
+ address: stdComptroller,
15105
+ name,
15106
+ params: [tk.cToken]
15107
+ });
15108
+ }
15109
+ }
15110
+ }
15061
15111
  }
15062
15112
  return calls;
15063
15113
  };
@@ -15513,7 +15563,10 @@ function convertSingleEntry(opts) {
15513
15563
  if (perMarketIncentive !== void 0 && Number(perMarketIncentive) > 0) {
15514
15564
  liquidationPenalty = Math.max(
15515
15565
  Number(
15516
- parseRawAmount(perMarketIncentive.toString(), RESERVE_MANTISSA_DECIMALS)
15566
+ parseRawAmount(
15567
+ perMarketIncentive.toString(),
15568
+ RESERVE_MANTISSA_DECIMALS
15569
+ )
15517
15570
  ) - 1,
15518
15571
  0
15519
15572
  );
@@ -15534,7 +15587,11 @@ function convertSingleEntry(opts) {
15534
15587
  );
15535
15588
  const liquidity = totalSupplyUnderlying - totalDebt;
15536
15589
  const price2 = prices[asset.assetGroup] ?? 0;
15537
- const pausedActions = currentEntry.pausedActions;
15590
+ const pausedActions = { ...currentEntry.pausedActions };
15591
+ if (opts.pause?.mintPaused !== void 0)
15592
+ pausedActions[0 /* MINT */] = opts.pause.mintPaused;
15593
+ if (opts.pause?.borrowPaused !== void 0)
15594
+ pausedActions[2 /* BORROW */] = opts.pause.borrowPaused;
15538
15595
  const allPaused = pausedActions[0 /* MINT */] && pausedActions[2 /* BORROW */] && pausedActions[1 /* REDEEM */] && pausedActions[3 /* REPAY */];
15539
15596
  const poolId = asset.address;
15540
15597
  const collateralActive = Boolean(currentEntry?.isListed);
@@ -16366,7 +16423,9 @@ var getCompoundV2DataConverter = (lender, chainId, prices, additionalYields, tok
16366
16423
  const isSumer = isSumerType(lender);
16367
16424
  const stdComptroller = isSumer ? void 0 : getCompoundV2Comptroller(chainId, lender);
16368
16425
  const stdComptrollerCalls = stdComptroller ? 2 : 0;
16369
- const expectedNumberOfCalls = isSumer ? tokenCount + SUMER_GROUP_COUNT : tokenCount + stdComptrollerCalls;
16426
+ const pauseReads = Boolean(stdComptroller) && usesComptrollerPauseReads(lender);
16427
+ const pauseCalls = pauseReads ? tokenCount * 2 : 0;
16428
+ const expectedNumberOfCalls = isSumer ? tokenCount + SUMER_GROUP_COUNT : tokenCount + stdComptrollerCalls + pauseCalls;
16370
16429
  const reader = getReader(lender, chainId);
16371
16430
  return [
16372
16431
  (data) => {
@@ -16403,6 +16462,8 @@ var getCompoundV2DataConverter = (lender, chainId, prices, additionalYields, tok
16403
16462
  globalCloseFactor = closeFactorMantissa;
16404
16463
  globalLiquidationPenalty = incentiveMantissa !== void 0 ? Math.max(incentiveMantissa - 1, 0) : void 0;
16405
16464
  }
16465
+ const mintPausedBase = tokenCount + stdComptrollerCalls;
16466
+ const borrowPausedBase = mintPausedBase + tokenCount;
16406
16467
  for (let i = 0; i < tokenCount; i++) {
16407
16468
  const { underlying, cToken } = compoundV2Tokens2[i];
16408
16469
  if (!cToken || !underlying) continue;
@@ -16419,7 +16480,13 @@ var getCompoundV2DataConverter = (lender, chainId, prices, additionalYields, tok
16419
16480
  additionalYields,
16420
16481
  cToken,
16421
16482
  closeFactor: globalCloseFactor,
16422
- liquidationPenalty: globalLiquidationPenalty
16483
+ liquidationPenalty: globalLiquidationPenalty,
16484
+ // live guardian read first, then the static fallback published on
16485
+ // the token entry for forks that expose no getter at all
16486
+ pause: pauseReads ? resolveMarketPause(compoundV2Tokens2[i], {
16487
+ mintPaused: parsePausedFlag(data[mintPausedBase + i]),
16488
+ borrowPaused: parsePausedFlag(data[borrowPausedBase + i])
16489
+ }) : void 0
16423
16490
  });
16424
16491
  out.data[entry.marketUid] = entry;
16425
16492
  }
@@ -27655,7 +27722,31 @@ var maxRetries = 3;
27655
27722
  var MULTICALL_FAILURE = "0x";
27656
27723
  var isFailedCall = (value) => value === void 0 || value === null || value === MULTICALL_FAILURE;
27657
27724
  var MULTICALL_REPAIR_ROUNDS = 3;
27658
- var MAX_CALLS_PER_SHARD = 400;
27725
+ var MAX_CALLS_PER_SHARD = 300;
27726
+ var ONE_REQUEST_BATCH_BYTES = 1e6;
27727
+ var MAX_INFLIGHT_REQUESTS = 6;
27728
+ var HEDGE_DELAY_MS = 1200;
27729
+ var SHARD_TIMEOUT_MS = 4e3;
27730
+ var MIN_ADAPTIVE_SHARD = 25;
27731
+ var ADAPTIVE_CAP_TTL_MS = 6e4;
27732
+ var endpointCallCap = /* @__PURE__ */ new Map();
27733
+ var capFor = (url, requested) => {
27734
+ const hit = endpointCallCap.get(url);
27735
+ if (!hit) return requested;
27736
+ if (Date.now() - hit.at > ADAPTIVE_CAP_TTL_MS) {
27737
+ endpointCallCap.delete(url);
27738
+ return requested;
27739
+ }
27740
+ return Math.min(requested, hit.calls);
27741
+ };
27742
+ var noteEndpointRefusal = (url, calls) => {
27743
+ if (calls <= MIN_ADAPTIVE_SHARD) return;
27744
+ const next = Math.max(MIN_ADAPTIVE_SHARD, Math.floor(calls / 2));
27745
+ const current = endpointCallCap.get(url);
27746
+ if (current && Date.now() - current.at <= ADAPTIVE_CAP_TTL_MS && current.calls <= next)
27747
+ return;
27748
+ endpointCallCap.set(url, { calls: next, at: Date.now() });
27749
+ };
27659
27750
  var DETERMINISTIC_ERROR_NAMES = /* @__PURE__ */ new Set([
27660
27751
  "ContractFunctionRevertedError",
27661
27752
  "RawContractError",
@@ -27679,13 +27770,13 @@ var REPAIR_BACKOFF_JITTER_MS = 60;
27679
27770
  var sleep = (ms) => new Promise((resolve) => setTimeout(resolve, Math.max(0, ms)));
27680
27771
  var backoffForRound = (round) => REPAIR_BACKOFF_BASE_MS * 2 ** round + Math.random() * REPAIR_BACKOFF_JITTER_MS;
27681
27772
  var endpointUrl = (client, rpcId) => client?.transport?.url ?? `rpc#${rpcId}`;
27682
- var resolveEndpoint = (chainId, getEvmClient17, startRpcId, tried, maxProbe = 12) => {
27773
+ var resolveEndpoint = (chainId, getEvmClient17, startRpcId, tried, maxProbe = 12, timeoutMs) => {
27683
27774
  let fallback = null;
27684
27775
  for (let probe = 0; probe < maxProbe; probe++) {
27685
27776
  const rpcId = startRpcId + probe;
27686
27777
  let client;
27687
27778
  try {
27688
- client = getEvmClient17(chainId, rpcId);
27779
+ client = getEvmClient17(chainId, rpcId, { timeoutMs });
27689
27780
  } catch {
27690
27781
  break;
27691
27782
  }
@@ -27894,39 +27985,30 @@ var multicallShardedAbiArray = async (chainId, abi, calls, getEvmClient17, poolS
27894
27985
  functionName: name,
27895
27986
  args: params
27896
27987
  }));
27988
+ const requestBytes = batchSize < MULTICALL_DEFAULT_BATCH_SIZE ? batchSize : ONE_REQUEST_BATCH_BYTES;
27989
+ const workerBudget = Math.max(1, Math.floor(MAX_INFLIGHT_REQUESTS / 2));
27990
+ const workers = Math.max(1, Math.min(poolSize, workerBudget));
27897
27991
  const shardSize = Math.max(
27898
27992
  1,
27899
- Math.min(MAX_CALLS_PER_SHARD, Math.ceil(contracts.length / poolSize))
27993
+ Math.min(MAX_CALLS_PER_SHARD, Math.ceil(contracts.length / workers))
27900
27994
  );
27901
27995
  const batches = [];
27902
27996
  for (let i = 0; i < contracts.length; i += shardSize) {
27903
27997
  batches.push({ start: i, items: contracts.slice(i, i + shardSize) });
27904
27998
  }
27905
27999
  const results = new Array(contracts.length);
27906
- const runBatch = async (batch, rpcId, attemptsLeft, size, tried) => {
27907
- const endpoint = resolveEndpoint(chainId, getEvmClient17, rpcId, tried);
27908
- if (!endpoint) {
27909
- if (logs)
27910
- console.log(
27911
- `sharded multicall: no untried endpoint left for chain ${chainId}`
27912
- );
27913
- return;
27914
- }
27915
- const shardOptions = {
27916
- tried,
27917
- onEndpointFailure: options?.onEndpointFailure
27918
- };
28000
+ const request = async (endpoint, items) => {
27919
28001
  try {
27920
28002
  const returnData = await endpoint.client.multicall({
27921
28003
  allowFailure,
27922
- batchSize: size,
27923
- contracts: batch.items
28004
+ batchSize: requestBytes,
28005
+ contracts: items
27924
28006
  });
27925
- let slots = toSlots(returnData, allowFailure);
27926
- if (allowFailure && attemptsLeft > 0 && slots.length > 0 && slots.every((s) => isFailedCall(s.value) && !s.permanent)) {
28007
+ const slots = toSlots(returnData, allowFailure);
28008
+ if (allowFailure && slots.length > 0 && slots.every((s) => isFailedCall(s.value) && !s.permanent)) {
27927
28009
  if (logs)
27928
28010
  console.log(
27929
- `sharded multicall shard fully failed on rpc ${endpoint.rpcId}, failing over`
28011
+ `sharded multicall: rpc ${endpoint.rpcId} answered ${items.length} calls with nothing`
27930
28012
  );
27931
28013
  options?.onEndpointFailure?.({
27932
28014
  chainId,
@@ -27934,61 +28016,165 @@ var multicallShardedAbiArray = async (chainId, abi, calls, getEvmClient17, poolS
27934
28016
  rpcId: endpoint.rpcId,
27935
28017
  kind: "slots"
27936
28018
  });
27937
- await sleep(backoffForRound(0));
27938
- return runBatch(
27939
- batch,
27940
- endpoint.rpcId + 1,
27941
- attemptsLeft - 1,
27942
- size,
27943
- tried
27944
- );
27945
- }
27946
- if (allowFailure && retryFailed) {
27947
- slots = await repairFailedSlots(
27948
- chainId,
27949
- batch.items,
27950
- slots,
27951
- getEvmClient17,
27952
- endpoint.rpcId + 1,
27953
- size,
27954
- logs,
27955
- MULTICALL_REPAIR_ROUNDS,
27956
- shardOptions
27957
- );
27958
- }
27959
- recordPermanentFailures(slots, batch.start, permanentFailures);
27960
- for (let j = 0; j < slots.length; j++) {
27961
- results[batch.start + j] = slots[j].value;
28019
+ noteEndpointRefusal(endpoint.url, items.length);
28020
+ return null;
27962
28021
  }
28022
+ return slots;
27963
28023
  } catch (error) {
27964
- if (logs) console.log("error in sharded multicall batch", error);
28024
+ if (logs) console.log("error in sharded multicall request", error);
27965
28025
  options?.onEndpointFailure?.({
27966
28026
  chainId,
27967
28027
  url: endpoint.url,
27968
28028
  rpcId: endpoint.rpcId,
27969
28029
  kind: "transport"
27970
28030
  });
27971
- if (attemptsLeft <= 0) throw error;
27972
- await sleep(backoffForRound(0));
27973
- return runBatch(
27974
- batch,
27975
- endpoint.rpcId + 1,
27976
- attemptsLeft - 1,
27977
- Math.max(1, Math.floor(size / 2)),
27978
- tried
28031
+ noteEndpointRefusal(endpoint.url, items.length);
28032
+ return null;
28033
+ }
28034
+ };
28035
+ const firstUsable = (attempts) => new Promise((resolve) => {
28036
+ let pending = attempts.length;
28037
+ let settled = false;
28038
+ const lose = () => {
28039
+ if (!settled && --pending === 0) resolve(null);
28040
+ };
28041
+ for (const attempt of attempts) {
28042
+ attempt.then((won) => {
28043
+ if (settled) return;
28044
+ if (won) {
28045
+ settled = true;
28046
+ resolve(won);
28047
+ } else lose();
28048
+ }, lose);
28049
+ }
28050
+ });
28051
+ const attemptHedged = async (items, startRpcId, tried) => {
28052
+ const primary = resolveEndpoint(
28053
+ chainId,
28054
+ getEvmClient17,
28055
+ startRpcId,
28056
+ tried,
28057
+ 12,
28058
+ SHARD_TIMEOUT_MS
28059
+ );
28060
+ if (!primary) {
28061
+ if (logs)
28062
+ console.log(
28063
+ `sharded multicall: no untried endpoint left for chain ${chainId}`
28064
+ );
28065
+ return null;
28066
+ }
28067
+ let timer;
28068
+ let hedgeStarted = false;
28069
+ let beginHedge = () => {
28070
+ };
28071
+ const hedged = new Promise((resolve) => {
28072
+ beginHedge = () => {
28073
+ if (hedgeStarted) return;
28074
+ hedgeStarted = true;
28075
+ clearTimeout(timer);
28076
+ const alt = resolveEndpoint(
28077
+ chainId,
28078
+ getEvmClient17,
28079
+ primary.rpcId + 1,
28080
+ tried,
28081
+ 12,
28082
+ SHARD_TIMEOUT_MS
28083
+ );
28084
+ if (!alt) return resolve(null);
28085
+ const altItems = items.slice(0, capFor(alt.url, items.length));
28086
+ if (altItems.length < items.length) return resolve(null);
28087
+ request(alt, items).then(
28088
+ (slots) => resolve(slots ? { slots, rpcId: alt.rpcId } : null)
28089
+ );
28090
+ };
28091
+ });
28092
+ timer = setTimeout(() => beginHedge(), HEDGE_DELAY_MS);
28093
+ const direct = request(primary, items).then((slots) => {
28094
+ if (slots) return { slots, rpcId: primary.rpcId };
28095
+ beginHedge();
28096
+ return null;
28097
+ });
28098
+ const won = await firstUsable([direct, hedged]);
28099
+ clearTimeout(timer);
28100
+ return won;
28101
+ };
28102
+ const runBatch = async (batch, rpcId, attemptsLeft, tried) => {
28103
+ if (!batch.items.length) return;
28104
+ const won = await attemptHedged(batch.items, rpcId, tried);
28105
+ if (!won) {
28106
+ if (attemptsLeft > 0 && batch.items.length > 1) {
28107
+ const half = Math.ceil(batch.items.length / 2);
28108
+ await sleep(backoffForRound(0));
28109
+ await runBatch(
28110
+ { start: batch.start, items: batch.items.slice(0, half) },
28111
+ rpcId,
28112
+ attemptsLeft - 1,
28113
+ tried
28114
+ );
28115
+ await runBatch(
28116
+ { start: batch.start + half, items: batch.items.slice(half) },
28117
+ rpcId + 1,
28118
+ attemptsLeft - 1,
28119
+ tried
28120
+ );
28121
+ }
28122
+ return;
28123
+ }
28124
+ let slots = won.slots;
28125
+ if (allowFailure && retryFailed) {
28126
+ slots = await repairFailedSlots(
28127
+ chainId,
28128
+ batch.items,
28129
+ slots,
28130
+ getEvmClient17,
28131
+ won.rpcId + 1,
28132
+ requestBytes,
28133
+ logs,
28134
+ MULTICALL_REPAIR_ROUNDS,
28135
+ { tried, onEndpointFailure: options?.onEndpointFailure }
27979
28136
  );
27980
28137
  }
28138
+ recordPermanentFailures(slots, batch.start, permanentFailures);
28139
+ for (let j = 0; j < slots.length; j++) {
28140
+ results[batch.start + j] = slots[j].value;
28141
+ }
27981
28142
  };
27982
- const workers = Math.max(1, Math.min(poolSize, batches.length));
27983
28143
  let cursor = 0;
27984
28144
  const worker = async (workerId) => {
27985
28145
  while (true) {
27986
28146
  const idx = cursor++;
27987
28147
  if (idx >= batches.length) break;
27988
- await runBatch(batches[idx], workerId, retries, batchSize, /* @__PURE__ */ new Set());
28148
+ const batch = batches[idx];
28149
+ const tried = /* @__PURE__ */ new Set();
28150
+ const start = resolveEndpoint(
28151
+ chainId,
28152
+ getEvmClient17,
28153
+ workerId,
28154
+ void 0,
28155
+ 12,
28156
+ SHARD_TIMEOUT_MS
28157
+ );
28158
+ const cap = start ? capFor(start.url, batch.items.length) : batch.items.length;
28159
+ for (let off = 0; off < batch.items.length; off += cap) {
28160
+ await runBatch(
28161
+ {
28162
+ start: batch.start + off,
28163
+ items: batch.items.slice(off, off + cap)
28164
+ },
28165
+ workerId,
28166
+ retries,
28167
+ tried
28168
+ );
28169
+ }
27989
28170
  }
27990
28171
  };
27991
- await Promise.all(Array.from({ length: workers }, (_3, w) => worker(w)));
28172
+ await Promise.all(
28173
+ Array.from(
28174
+ { length: Math.min(workers, batches.length) },
28175
+ (_3, w) => worker(w)
28176
+ )
28177
+ );
27992
28178
  return results;
27993
28179
  };
27994
28180
  function prepareMulticallInputs(abi, calls) {
@@ -53819,6 +54005,19 @@ var FlashAbi = [
53819
54005
  name: "FLASHLOAN_PREMIUM_TOTAL",
53820
54006
  inputs: []
53821
54007
  },
54008
+ {
54009
+ inputs: [],
54010
+ name: "decimals",
54011
+ outputs: [
54012
+ {
54013
+ internalType: "uint8",
54014
+ name: "",
54015
+ type: "uint8"
54016
+ }
54017
+ ],
54018
+ stateMutability: "view",
54019
+ type: "function"
54020
+ },
53822
54021
  {
53823
54022
  inputs: [
53824
54023
  {
@@ -53841,6 +54040,14 @@ var FlashAbi = [
53841
54040
  ];
53842
54041
  var DEFAULT_BATCH_SIZE = 4096;
53843
54042
  var isValidResult = (v) => typeof v === "bigint";
54043
+ var NATIVE_DECIMALS = 18;
54044
+ function parseDecimalsResult(v) {
54045
+ const n = typeof v === "bigint" ? Number(v) : v;
54046
+ if (typeof n !== "number" || !Number.isInteger(n) || n < 0 || n > 36) {
54047
+ return void 0;
54048
+ }
54049
+ return n;
54050
+ }
53844
54051
  var FLASHLOAN_ENABLED_MASK = BigInt(
53845
54052
  "0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF7FFFFFFFFFFFFFFF"
53846
54053
  );
@@ -53906,6 +54113,20 @@ async function fetchFlashLiquidityForChain(chain, multicallRetry, list = {}) {
53906
54113
  const balancerV3Calls = buildBalanceCalls(balancerV3s);
53907
54114
  const uniswapV4s = RELEVANT_UNISWAP_V4_FORKS[chain] ?? [];
53908
54115
  const uniswapV4Calls = buildBalanceCalls(uniswapV4s);
54116
+ const decimalsByAsset = {
54117
+ [zeroAddress]: NATIVE_DECIMALS
54118
+ };
54119
+ for (const asset of unifiedAssets) {
54120
+ const fromList = list[asset]?.decimals;
54121
+ if (typeof fromList === "number") decimalsByAsset[asset] = fromList;
54122
+ }
54123
+ const assetsMissingDecimals = unifiedAssets.filter(
54124
+ (asset) => decimalsByAsset[asset] === void 0
54125
+ );
54126
+ const decimalsCalls = assetsMissingDecimals.map((address) => ({
54127
+ name: "decimals",
54128
+ address
54129
+ }));
53909
54130
  const calls = [
53910
54131
  ...aaveCalls,
53911
54132
  ...balancerV2Calls,
@@ -53913,6 +54134,8 @@ async function fetchFlashLiquidityForChain(chain, multicallRetry, list = {}) {
53913
54134
  ...balancerV3Calls,
53914
54135
  ...uniswapV4Calls
53915
54136
  ];
54137
+ const decimalsOffset = calls.length;
54138
+ calls.push(...decimalsCalls);
53916
54139
  const rawResults = await multicallRetry({
53917
54140
  chain,
53918
54141
  calls,
@@ -53922,6 +54145,10 @@ async function fetchFlashLiquidityForChain(chain, multicallRetry, list = {}) {
53922
54145
  providerId: 0,
53923
54146
  allowFailure: true
53924
54147
  });
54148
+ assetsMissingDecimals.forEach((asset, i) => {
54149
+ const parsed = parseDecimalsResult(rawResults[decimalsOffset + i]);
54150
+ if (parsed !== void 0) decimalsByAsset[asset] = parsed;
54151
+ });
53925
54152
  let liquidity = {};
53926
54153
  let currentOffset = 0;
53927
54154
  aaveProtocols.forEach((aave) => {
@@ -53934,7 +54161,7 @@ async function fetchFlashLiquidityForChain(chain, multicallRetry, list = {}) {
53934
54161
  const rawAmount = data[2 * i];
53935
54162
  const config = data[2 * i + 1];
53936
54163
  if (typeof rawAmount !== "bigint" || typeof config !== "bigint") return;
53937
- const decimals = list[asset]?.decimals;
54164
+ const decimals = decimalsByAsset[asset];
53938
54165
  const enabled = !AAVE_V3_LENDERS.includes(aave) || getFlashLoanEnabled(config);
53939
54166
  if (enabled && rawAmount > 0n && FLASH_LOAN_IDS[aave] !== void 0) {
53940
54167
  if (!liquidity[asset]) liquidity[asset] = [];
@@ -53945,7 +54172,7 @@ async function fetchFlashLiquidityForChain(chain, multicallRetry, list = {}) {
53945
54172
  source: getAaveTypePoolAddress(chain, aave),
53946
54173
  fee: fee.toString(),
53947
54174
  availableRaw: rawAmount.toString(),
53948
- available: Number(formatUnits(rawAmount, decimals ?? 18)),
54175
+ available: decimals === void 0 ? void 0 : Number(formatUnits(rawAmount, decimals)),
53949
54176
  decimals
53950
54177
  });
53951
54178
  }
@@ -53956,7 +54183,7 @@ async function fetchFlashLiquidityForChain(chain, multicallRetry, list = {}) {
53956
54183
  const data = rawResults.slice(currentOffset, callLen + currentOffset);
53957
54184
  currentOffset += callLen;
53958
54185
  unifiedAssets.forEach((asset, i) => {
53959
- const decimals = list[asset]?.decimals;
54186
+ const decimals = decimalsByAsset[asset];
53960
54187
  const rawAmount = data[i];
53961
54188
  if (isValidResult(rawAmount) && rawAmount > 0n) {
53962
54189
  if (!liquidity[asset]) liquidity[asset] = [];
@@ -53967,7 +54194,7 @@ async function fetchFlashLiquidityForChain(chain, multicallRetry, list = {}) {
53967
54194
  source: balancer.address,
53968
54195
  fee: "0",
53969
54196
  availableRaw: rawAmount.toString(),
53970
- available: Number(formatUnits(rawAmount, decimals ?? 18)),
54197
+ available: decimals === void 0 ? void 0 : Number(formatUnits(rawAmount, decimals)),
53971
54198
  decimals
53972
54199
  });
53973
54200
  }
@@ -53981,7 +54208,7 @@ async function fetchFlashLiquidityForChain(chain, multicallRetry, list = {}) {
53981
54208
  const rawAmount = data[i];
53982
54209
  if (isValidResult(rawAmount) && rawAmount > 0n) {
53983
54210
  if (!liquidity[asset]) liquidity[asset] = [];
53984
- const decimals = list[asset]?.decimals;
54211
+ const decimals = decimalsByAsset[asset];
53985
54212
  liquidity[asset].push({
53986
54213
  id: FLASH_LOAN_IDS[morpho.pool],
53987
54214
  name: morpho.pool,
@@ -53989,7 +54216,7 @@ async function fetchFlashLiquidityForChain(chain, multicallRetry, list = {}) {
53989
54216
  source: morpho.address,
53990
54217
  fee: "0",
53991
54218
  availableRaw: rawAmount.toString(),
53992
- available: Number(formatUnits(rawAmount, decimals ?? 18)),
54219
+ available: decimals === void 0 ? void 0 : Number(formatUnits(rawAmount, decimals)),
53993
54220
  decimals
53994
54221
  });
53995
54222
  }
@@ -54000,7 +54227,7 @@ async function fetchFlashLiquidityForChain(chain, multicallRetry, list = {}) {
54000
54227
  const data = rawResults.slice(currentOffset, callLen + currentOffset);
54001
54228
  currentOffset += callLen;
54002
54229
  unifiedAssets.forEach((asset, i) => {
54003
- const decimals = list[asset]?.decimals;
54230
+ const decimals = decimalsByAsset[asset];
54004
54231
  const rawAmount = data[i];
54005
54232
  if (isValidResult(rawAmount) && rawAmount > 0n) {
54006
54233
  if (!liquidity[asset]) liquidity[asset] = [];
@@ -54011,7 +54238,7 @@ async function fetchFlashLiquidityForChain(chain, multicallRetry, list = {}) {
54011
54238
  source: balancerV3.address,
54012
54239
  fee: "0",
54013
54240
  availableRaw: rawAmount.toString(),
54014
- available: Number(formatUnits(rawAmount, decimals ?? 18)),
54241
+ available: decimals === void 0 ? void 0 : Number(formatUnits(rawAmount, decimals)),
54015
54242
  decimals
54016
54243
  });
54017
54244
  }
@@ -54022,7 +54249,7 @@ async function fetchFlashLiquidityForChain(chain, multicallRetry, list = {}) {
54022
54249
  const data = rawResults.slice(currentOffset, callLen + currentOffset);
54023
54250
  currentOffset += callLen;
54024
54251
  unifiedAssets.forEach((asset, i) => {
54025
- const decimals = list[asset]?.decimals;
54252
+ const decimals = decimalsByAsset[asset];
54026
54253
  const rawAmount = data[i];
54027
54254
  if (isValidResult(rawAmount) && rawAmount > 0n) {
54028
54255
  if (!liquidity[asset]) liquidity[asset] = [];
@@ -54033,7 +54260,7 @@ async function fetchFlashLiquidityForChain(chain, multicallRetry, list = {}) {
54033
54260
  source: uniV4.address,
54034
54261
  fee: "0",
54035
54262
  availableRaw: rawAmount.toString(),
54036
- available: Number(formatUnits(rawAmount, decimals ?? 18)),
54263
+ available: decimals === void 0 ? void 0 : Number(formatUnits(rawAmount, decimals)),
54037
54264
  decimals
54038
54265
  });
54039
54266
  }
@@ -54050,7 +54277,9 @@ function attachPricesToFlashLiquidity(chainId, liq, prices, list = {}) {
54050
54277
  const price2 = prices[priceKey] ?? 0;
54051
54278
  liqCopy[asset] = entry.map((e) => ({
54052
54279
  ...e,
54053
- availableUSD: e.available * price2
54280
+ // absent when the token amount itself is unknown — never 0, which reads
54281
+ // as "no liquidity" rather than "not priced"
54282
+ availableUSD: e.available === void 0 ? void 0 : e.available * price2
54054
54283
  }));
54055
54284
  });
54056
54285
  return liqCopy;
@@ -65653,9 +65882,8 @@ function borrowDescription(b) {
65653
65882
  b.rate.minApr != null && b.rate.maxApr != null ? `You choose your own rate between ${pct(b.rate.minApr)} and ${pct(b.rate.maxApr)}; a lower rate is cheaper but makes you redeemed first.` : "You choose your own interest rate; a lower rate is cheaper but makes you redeemed first."
65654
65883
  );
65655
65884
  } else {
65656
- parts.push(
65657
- `You pay ${pct(b.rate.apr)}${b.rate.isLocked ? ", locked for the term" : ", floating with utilization"}.`
65658
- );
65885
+ const how = b.rate.isLocked ? ", locked for the term" : b.rate.kind === "variable-curve" ? ", floating with utilization" : b.rate.kind === "variable-managed" ? ", set by the protocol rather than floating with utilization" : "";
65886
+ parts.push(`You pay ${pct(b.rate.apr)}${how}.`);
65659
65887
  }
65660
65888
  parts.push(
65661
65889
  b.debtShape === "static-face" ? "The amount owed is fixed at trade time and does not accrue." : b.debtShape === "prepaid" ? "The debt principal is static; the cost is the prepaid interest token burning down." : "The amount owed grows continuously as interest accrues."
@@ -65674,11 +65902,27 @@ function borrowDescription(b) {
65674
65902
  `It matures on ${shortDate(b.maturity.maturity)}${what ? `; if nothing is done then, ${what}` : ""}.`
65675
65903
  );
65676
65904
  }
65905
+ const withheld = (b.fees ?? []).filter(
65906
+ (f) => f.when === "entry" && f.basis === "principal" && f.unit === "percent" && f.value > 0
65907
+ );
65908
+ if (withheld.length > 0) {
65909
+ const total = withheld.reduce((a, f) => a + f.value, 0);
65910
+ const named = withheld.map((f) => `${f.label.toLowerCase()} ${pct(f.value)}`).join(" + ");
65911
+ parts.push(
65912
+ `${pct(total)} of anything you borrow is withheld at open` + (withheld.length > 1 ? ` (${named})` : "") + `, so you receive ${pct(100 - total)} of what you owe.`
65913
+ );
65914
+ }
65677
65915
  if (b.liquidation.trigger === "time") {
65678
65916
  parts.push(
65679
65917
  "Liquidation here is triggered by TIME, not price \u2014 being late is the trigger, and being over-collateralised does not protect you."
65680
65918
  );
65681
- } else if (b.liquidation.liquidationLtv != null) {
65919
+ } else if (b.liquidation.model === "auction" && !(b.liquidation.liquidationLtv > 0)) {
65920
+ const pen = b.liquidation.penalty;
65921
+ const win = b.liquidation.windowSecs;
65922
+ parts.push(
65923
+ "Liquidation is a public auction rather than a loan-to-value threshold" + (win ? `, open for ${duration(win)} once started` : "") + (pen != null ? `, and the liquidator is paid ${pct(pen * 100)}` : "") + "."
65924
+ );
65925
+ } else if (b.liquidation.liquidationLtv != null && b.liquidation.liquidationLtv > 0) {
65682
65926
  const pen = b.liquidation.penalty;
65683
65927
  parts.push(
65684
65928
  `Liquidation starts at ${pct(b.liquidation.liquidationLtv * 100)} LTV` + (pen != null ? `, with a ${pct(pen * 100)} penalty` : "") + "."