@1delta/margin-fetcher 5.0.46 → 5.0.48

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.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
  }
@@ -19386,6 +19453,19 @@ function parseVault(vault, chainId, prices, additionalYields, tokenList, liquidi
19386
19453
  const meta = tokenList[token];
19387
19454
  return toOracleKey(meta?.assetGroup) ?? toGenericPriceKey(token, chainId);
19388
19455
  };
19456
+ const basketRate = (legs) => {
19457
+ let value = 0;
19458
+ let weighted = 0;
19459
+ for (const leg of legs) {
19460
+ const v = leg.amount * (prices[priceKeyFor(leg.token)] ?? 0);
19461
+ if (v <= 0) continue;
19462
+ value += v;
19463
+ weighted += v * leg.rate;
19464
+ }
19465
+ return value > 0 ? weighted / value : legs[0]?.rate ?? 0;
19466
+ };
19467
+ const basketSupplyRate = isSmartCol ? basketRate(colLegs) : void 0;
19468
+ const basketBorrowRate = isSmartDebt ? basketRate(debtLegs) : void 0;
19389
19469
  const irmTotals = (state, decimals) => state ? {
19390
19470
  irmTotalDeposits: Number(parseRawAmount(state.totalSupply, decimals)),
19391
19471
  irmTotalDebt: Number(parseRawAmount(state.totalBorrow, decimals))
@@ -19451,8 +19531,49 @@ function parseVault(vault, chainId, prices, additionalYields, tokenList, liquidi
19451
19531
  * it a smart market is described as an ordinary pool — the generic
19452
19532
  * builder cannot tell them apart.
19453
19533
  */
19534
+ /**
19535
+ * THIS ROW'S ASSET IS NOT INDEPENDENTLY HOLDABLE.
19536
+ *
19537
+ * LENDER-AGNOSTIC ON PURPOSE — it sits on the ROW, not inside `fluid`,
19538
+ * because the shape is not Fluid's alone: Lista SmartLP's collateral
19539
+ * receipt, GMX's GM/GLV baskets and Fluid's smart sides are all market
19540
+ * sides whose position unit is a multi-token basket (the same set
19541
+ * LP_ACTIONS_PLAN.md enumerates). Any of them can set this, and a
19542
+ * consumer branches on it once instead of learning each protocol.
19543
+ *
19544
+ * True means: depositing any leg mints a share of a basket over ALL of
19545
+ * them, and the POOL — not the user — sets and continuously re-sets the
19546
+ * ratio. There is no way to "hold the ETH leg" of a USDC+ETH side; the
19547
+ * split drifts with every trade and with the range shifting around its
19548
+ * centre price.
19549
+ *
19550
+ * Three consequences a consumer must handle: "you deposited X" is false
19551
+ * from the first block; a single leg's rate is right per DOLLAR but is
19552
+ * NOT the position's rate (use the basket rate); and an exit has to be
19553
+ * sized in shares rather than in this token.
19554
+ */
19555
+ ...isSmartVault ? { autoBalanced: true } : {},
19454
19556
  fluid: isSmartVault ? {
19455
19557
  vaultType,
19558
+ /**
19559
+ * The rate of the POSITION, not of this leg.
19560
+ *
19561
+ * A per-leg rate is individually correct — every dollar in the LP
19562
+ * earns the DEX trading yield regardless of which token it sits in,
19563
+ * so `legRate = liquidityRate + tradingRate` is right per dollar.
19564
+ * What it is NOT is the vault's APR: ranking or headlining a single
19565
+ * leg overstates the position whenever the legs differ. On the live
19566
+ * USDC+ETH/USDC+ETH vault the legs read 11.81 % and 8.19 % while
19567
+ * the position earns 10.33 % — and a "best APR" taken as the max
19568
+ * over legs shows 11.81 %.
19569
+ *
19570
+ * Value-weighted by each leg's own share of the side, which is the
19571
+ * pool's composition as this vault holds it. Fluid's own UI
19572
+ * publishes exactly this figure (10.33 % supply / −1.72 % borrow),
19573
+ * and these match it to the basis point.
19574
+ */
19575
+ basketSupplyRate,
19576
+ basketBorrowRate,
19456
19577
  isSmartCol,
19457
19578
  isSmartDebt,
19458
19579
  /** Both legs of the collateral LP, in token0/token1 order. */
@@ -27655,7 +27776,31 @@ var maxRetries = 3;
27655
27776
  var MULTICALL_FAILURE = "0x";
27656
27777
  var isFailedCall = (value) => value === void 0 || value === null || value === MULTICALL_FAILURE;
27657
27778
  var MULTICALL_REPAIR_ROUNDS = 3;
27658
- var MAX_CALLS_PER_SHARD = 400;
27779
+ var MAX_CALLS_PER_SHARD = 300;
27780
+ var ONE_REQUEST_BATCH_BYTES = 1e6;
27781
+ var MAX_INFLIGHT_REQUESTS = 6;
27782
+ var HEDGE_DELAY_MS = 1200;
27783
+ var SHARD_TIMEOUT_MS = 4e3;
27784
+ var MIN_ADAPTIVE_SHARD = 25;
27785
+ var ADAPTIVE_CAP_TTL_MS = 6e4;
27786
+ var endpointCallCap = /* @__PURE__ */ new Map();
27787
+ var capFor = (url, requested) => {
27788
+ const hit = endpointCallCap.get(url);
27789
+ if (!hit) return requested;
27790
+ if (Date.now() - hit.at > ADAPTIVE_CAP_TTL_MS) {
27791
+ endpointCallCap.delete(url);
27792
+ return requested;
27793
+ }
27794
+ return Math.min(requested, hit.calls);
27795
+ };
27796
+ var noteEndpointRefusal = (url, calls) => {
27797
+ if (calls <= MIN_ADAPTIVE_SHARD) return;
27798
+ const next = Math.max(MIN_ADAPTIVE_SHARD, Math.floor(calls / 2));
27799
+ const current = endpointCallCap.get(url);
27800
+ if (current && Date.now() - current.at <= ADAPTIVE_CAP_TTL_MS && current.calls <= next)
27801
+ return;
27802
+ endpointCallCap.set(url, { calls: next, at: Date.now() });
27803
+ };
27659
27804
  var DETERMINISTIC_ERROR_NAMES = /* @__PURE__ */ new Set([
27660
27805
  "ContractFunctionRevertedError",
27661
27806
  "RawContractError",
@@ -27679,13 +27824,13 @@ var REPAIR_BACKOFF_JITTER_MS = 60;
27679
27824
  var sleep = (ms) => new Promise((resolve) => setTimeout(resolve, Math.max(0, ms)));
27680
27825
  var backoffForRound = (round) => REPAIR_BACKOFF_BASE_MS * 2 ** round + Math.random() * REPAIR_BACKOFF_JITTER_MS;
27681
27826
  var endpointUrl = (client, rpcId) => client?.transport?.url ?? `rpc#${rpcId}`;
27682
- var resolveEndpoint = (chainId, getEvmClient17, startRpcId, tried, maxProbe = 12) => {
27827
+ var resolveEndpoint = (chainId, getEvmClient17, startRpcId, tried, maxProbe = 12, timeoutMs) => {
27683
27828
  let fallback = null;
27684
27829
  for (let probe = 0; probe < maxProbe; probe++) {
27685
27830
  const rpcId = startRpcId + probe;
27686
27831
  let client;
27687
27832
  try {
27688
- client = getEvmClient17(chainId, rpcId);
27833
+ client = getEvmClient17(chainId, rpcId, { timeoutMs });
27689
27834
  } catch {
27690
27835
  break;
27691
27836
  }
@@ -27894,39 +28039,30 @@ var multicallShardedAbiArray = async (chainId, abi, calls, getEvmClient17, poolS
27894
28039
  functionName: name,
27895
28040
  args: params
27896
28041
  }));
28042
+ const requestBytes = batchSize < MULTICALL_DEFAULT_BATCH_SIZE ? batchSize : ONE_REQUEST_BATCH_BYTES;
28043
+ const workerBudget = Math.max(1, Math.floor(MAX_INFLIGHT_REQUESTS / 2));
28044
+ const workers = Math.max(1, Math.min(poolSize, workerBudget));
27897
28045
  const shardSize = Math.max(
27898
28046
  1,
27899
- Math.min(MAX_CALLS_PER_SHARD, Math.ceil(contracts.length / poolSize))
28047
+ Math.min(MAX_CALLS_PER_SHARD, Math.ceil(contracts.length / workers))
27900
28048
  );
27901
28049
  const batches = [];
27902
28050
  for (let i = 0; i < contracts.length; i += shardSize) {
27903
28051
  batches.push({ start: i, items: contracts.slice(i, i + shardSize) });
27904
28052
  }
27905
28053
  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
- };
28054
+ const request = async (endpoint, items) => {
27919
28055
  try {
27920
28056
  const returnData = await endpoint.client.multicall({
27921
28057
  allowFailure,
27922
- batchSize: size,
27923
- contracts: batch.items
28058
+ batchSize: requestBytes,
28059
+ contracts: items
27924
28060
  });
27925
- let slots = toSlots(returnData, allowFailure);
27926
- if (allowFailure && attemptsLeft > 0 && slots.length > 0 && slots.every((s) => isFailedCall(s.value) && !s.permanent)) {
28061
+ const slots = toSlots(returnData, allowFailure);
28062
+ if (allowFailure && slots.length > 0 && slots.every((s) => isFailedCall(s.value) && !s.permanent)) {
27927
28063
  if (logs)
27928
28064
  console.log(
27929
- `sharded multicall shard fully failed on rpc ${endpoint.rpcId}, failing over`
28065
+ `sharded multicall: rpc ${endpoint.rpcId} answered ${items.length} calls with nothing`
27930
28066
  );
27931
28067
  options?.onEndpointFailure?.({
27932
28068
  chainId,
@@ -27934,61 +28070,165 @@ var multicallShardedAbiArray = async (chainId, abi, calls, getEvmClient17, poolS
27934
28070
  rpcId: endpoint.rpcId,
27935
28071
  kind: "slots"
27936
28072
  });
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;
28073
+ noteEndpointRefusal(endpoint.url, items.length);
28074
+ return null;
27962
28075
  }
28076
+ return slots;
27963
28077
  } catch (error) {
27964
- if (logs) console.log("error in sharded multicall batch", error);
28078
+ if (logs) console.log("error in sharded multicall request", error);
27965
28079
  options?.onEndpointFailure?.({
27966
28080
  chainId,
27967
28081
  url: endpoint.url,
27968
28082
  rpcId: endpoint.rpcId,
27969
28083
  kind: "transport"
27970
28084
  });
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
28085
+ noteEndpointRefusal(endpoint.url, items.length);
28086
+ return null;
28087
+ }
28088
+ };
28089
+ const firstUsable = (attempts) => new Promise((resolve) => {
28090
+ let pending = attempts.length;
28091
+ let settled = false;
28092
+ const lose = () => {
28093
+ if (!settled && --pending === 0) resolve(null);
28094
+ };
28095
+ for (const attempt of attempts) {
28096
+ attempt.then((won) => {
28097
+ if (settled) return;
28098
+ if (won) {
28099
+ settled = true;
28100
+ resolve(won);
28101
+ } else lose();
28102
+ }, lose);
28103
+ }
28104
+ });
28105
+ const attemptHedged = async (items, startRpcId, tried) => {
28106
+ const primary = resolveEndpoint(
28107
+ chainId,
28108
+ getEvmClient17,
28109
+ startRpcId,
28110
+ tried,
28111
+ 12,
28112
+ SHARD_TIMEOUT_MS
28113
+ );
28114
+ if (!primary) {
28115
+ if (logs)
28116
+ console.log(
28117
+ `sharded multicall: no untried endpoint left for chain ${chainId}`
28118
+ );
28119
+ return null;
28120
+ }
28121
+ let timer;
28122
+ let hedgeStarted = false;
28123
+ let beginHedge = () => {
28124
+ };
28125
+ const hedged = new Promise((resolve) => {
28126
+ beginHedge = () => {
28127
+ if (hedgeStarted) return;
28128
+ hedgeStarted = true;
28129
+ clearTimeout(timer);
28130
+ const alt = resolveEndpoint(
28131
+ chainId,
28132
+ getEvmClient17,
28133
+ primary.rpcId + 1,
28134
+ tried,
28135
+ 12,
28136
+ SHARD_TIMEOUT_MS
28137
+ );
28138
+ if (!alt) return resolve(null);
28139
+ const altItems = items.slice(0, capFor(alt.url, items.length));
28140
+ if (altItems.length < items.length) return resolve(null);
28141
+ request(alt, items).then(
28142
+ (slots) => resolve(slots ? { slots, rpcId: alt.rpcId } : null)
28143
+ );
28144
+ };
28145
+ });
28146
+ timer = setTimeout(() => beginHedge(), HEDGE_DELAY_MS);
28147
+ const direct = request(primary, items).then((slots) => {
28148
+ if (slots) return { slots, rpcId: primary.rpcId };
28149
+ beginHedge();
28150
+ return null;
28151
+ });
28152
+ const won = await firstUsable([direct, hedged]);
28153
+ clearTimeout(timer);
28154
+ return won;
28155
+ };
28156
+ const runBatch = async (batch, rpcId, attemptsLeft, tried) => {
28157
+ if (!batch.items.length) return;
28158
+ const won = await attemptHedged(batch.items, rpcId, tried);
28159
+ if (!won) {
28160
+ if (attemptsLeft > 0 && batch.items.length > 1) {
28161
+ const half = Math.ceil(batch.items.length / 2);
28162
+ await sleep(backoffForRound(0));
28163
+ await runBatch(
28164
+ { start: batch.start, items: batch.items.slice(0, half) },
28165
+ rpcId,
28166
+ attemptsLeft - 1,
28167
+ tried
28168
+ );
28169
+ await runBatch(
28170
+ { start: batch.start + half, items: batch.items.slice(half) },
28171
+ rpcId + 1,
28172
+ attemptsLeft - 1,
28173
+ tried
28174
+ );
28175
+ }
28176
+ return;
28177
+ }
28178
+ let slots = won.slots;
28179
+ if (allowFailure && retryFailed) {
28180
+ slots = await repairFailedSlots(
28181
+ chainId,
28182
+ batch.items,
28183
+ slots,
28184
+ getEvmClient17,
28185
+ won.rpcId + 1,
28186
+ requestBytes,
28187
+ logs,
28188
+ MULTICALL_REPAIR_ROUNDS,
28189
+ { tried, onEndpointFailure: options?.onEndpointFailure }
27979
28190
  );
27980
28191
  }
28192
+ recordPermanentFailures(slots, batch.start, permanentFailures);
28193
+ for (let j = 0; j < slots.length; j++) {
28194
+ results[batch.start + j] = slots[j].value;
28195
+ }
27981
28196
  };
27982
- const workers = Math.max(1, Math.min(poolSize, batches.length));
27983
28197
  let cursor = 0;
27984
28198
  const worker = async (workerId) => {
27985
28199
  while (true) {
27986
28200
  const idx = cursor++;
27987
28201
  if (idx >= batches.length) break;
27988
- await runBatch(batches[idx], workerId, retries, batchSize, /* @__PURE__ */ new Set());
28202
+ const batch = batches[idx];
28203
+ const tried = /* @__PURE__ */ new Set();
28204
+ const start = resolveEndpoint(
28205
+ chainId,
28206
+ getEvmClient17,
28207
+ workerId,
28208
+ void 0,
28209
+ 12,
28210
+ SHARD_TIMEOUT_MS
28211
+ );
28212
+ const cap = start ? capFor(start.url, batch.items.length) : batch.items.length;
28213
+ for (let off = 0; off < batch.items.length; off += cap) {
28214
+ await runBatch(
28215
+ {
28216
+ start: batch.start + off,
28217
+ items: batch.items.slice(off, off + cap)
28218
+ },
28219
+ workerId,
28220
+ retries,
28221
+ tried
28222
+ );
28223
+ }
27989
28224
  }
27990
28225
  };
27991
- await Promise.all(Array.from({ length: workers }, (_3, w) => worker(w)));
28226
+ await Promise.all(
28227
+ Array.from(
28228
+ { length: Math.min(workers, batches.length) },
28229
+ (_3, w) => worker(w)
28230
+ )
28231
+ );
27992
28232
  return results;
27993
28233
  };
27994
28234
  function prepareMulticallInputs(abi, calls) {
@@ -53819,6 +54059,19 @@ var FlashAbi = [
53819
54059
  name: "FLASHLOAN_PREMIUM_TOTAL",
53820
54060
  inputs: []
53821
54061
  },
54062
+ {
54063
+ inputs: [],
54064
+ name: "decimals",
54065
+ outputs: [
54066
+ {
54067
+ internalType: "uint8",
54068
+ name: "",
54069
+ type: "uint8"
54070
+ }
54071
+ ],
54072
+ stateMutability: "view",
54073
+ type: "function"
54074
+ },
53822
54075
  {
53823
54076
  inputs: [
53824
54077
  {
@@ -53841,6 +54094,14 @@ var FlashAbi = [
53841
54094
  ];
53842
54095
  var DEFAULT_BATCH_SIZE = 4096;
53843
54096
  var isValidResult = (v) => typeof v === "bigint";
54097
+ var NATIVE_DECIMALS = 18;
54098
+ function parseDecimalsResult(v) {
54099
+ const n = typeof v === "bigint" ? Number(v) : v;
54100
+ if (typeof n !== "number" || !Number.isInteger(n) || n < 0 || n > 36) {
54101
+ return void 0;
54102
+ }
54103
+ return n;
54104
+ }
53844
54105
  var FLASHLOAN_ENABLED_MASK = BigInt(
53845
54106
  "0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF7FFFFFFFFFFFFFFF"
53846
54107
  );
@@ -53906,6 +54167,20 @@ async function fetchFlashLiquidityForChain(chain, multicallRetry, list = {}) {
53906
54167
  const balancerV3Calls = buildBalanceCalls(balancerV3s);
53907
54168
  const uniswapV4s = RELEVANT_UNISWAP_V4_FORKS[chain] ?? [];
53908
54169
  const uniswapV4Calls = buildBalanceCalls(uniswapV4s);
54170
+ const decimalsByAsset = {
54171
+ [zeroAddress]: NATIVE_DECIMALS
54172
+ };
54173
+ for (const asset of unifiedAssets) {
54174
+ const fromList = list[asset]?.decimals;
54175
+ if (typeof fromList === "number") decimalsByAsset[asset] = fromList;
54176
+ }
54177
+ const assetsMissingDecimals = unifiedAssets.filter(
54178
+ (asset) => decimalsByAsset[asset] === void 0
54179
+ );
54180
+ const decimalsCalls = assetsMissingDecimals.map((address) => ({
54181
+ name: "decimals",
54182
+ address
54183
+ }));
53909
54184
  const calls = [
53910
54185
  ...aaveCalls,
53911
54186
  ...balancerV2Calls,
@@ -53913,6 +54188,8 @@ async function fetchFlashLiquidityForChain(chain, multicallRetry, list = {}) {
53913
54188
  ...balancerV3Calls,
53914
54189
  ...uniswapV4Calls
53915
54190
  ];
54191
+ const decimalsOffset = calls.length;
54192
+ calls.push(...decimalsCalls);
53916
54193
  const rawResults = await multicallRetry({
53917
54194
  chain,
53918
54195
  calls,
@@ -53922,6 +54199,10 @@ async function fetchFlashLiquidityForChain(chain, multicallRetry, list = {}) {
53922
54199
  providerId: 0,
53923
54200
  allowFailure: true
53924
54201
  });
54202
+ assetsMissingDecimals.forEach((asset, i) => {
54203
+ const parsed = parseDecimalsResult(rawResults[decimalsOffset + i]);
54204
+ if (parsed !== void 0) decimalsByAsset[asset] = parsed;
54205
+ });
53925
54206
  let liquidity = {};
53926
54207
  let currentOffset = 0;
53927
54208
  aaveProtocols.forEach((aave) => {
@@ -53934,7 +54215,7 @@ async function fetchFlashLiquidityForChain(chain, multicallRetry, list = {}) {
53934
54215
  const rawAmount = data[2 * i];
53935
54216
  const config = data[2 * i + 1];
53936
54217
  if (typeof rawAmount !== "bigint" || typeof config !== "bigint") return;
53937
- const decimals = list[asset]?.decimals;
54218
+ const decimals = decimalsByAsset[asset];
53938
54219
  const enabled = !AAVE_V3_LENDERS.includes(aave) || getFlashLoanEnabled(config);
53939
54220
  if (enabled && rawAmount > 0n && FLASH_LOAN_IDS[aave] !== void 0) {
53940
54221
  if (!liquidity[asset]) liquidity[asset] = [];
@@ -53945,7 +54226,7 @@ async function fetchFlashLiquidityForChain(chain, multicallRetry, list = {}) {
53945
54226
  source: getAaveTypePoolAddress(chain, aave),
53946
54227
  fee: fee.toString(),
53947
54228
  availableRaw: rawAmount.toString(),
53948
- available: Number(formatUnits(rawAmount, decimals ?? 18)),
54229
+ available: decimals === void 0 ? void 0 : Number(formatUnits(rawAmount, decimals)),
53949
54230
  decimals
53950
54231
  });
53951
54232
  }
@@ -53956,7 +54237,7 @@ async function fetchFlashLiquidityForChain(chain, multicallRetry, list = {}) {
53956
54237
  const data = rawResults.slice(currentOffset, callLen + currentOffset);
53957
54238
  currentOffset += callLen;
53958
54239
  unifiedAssets.forEach((asset, i) => {
53959
- const decimals = list[asset]?.decimals;
54240
+ const decimals = decimalsByAsset[asset];
53960
54241
  const rawAmount = data[i];
53961
54242
  if (isValidResult(rawAmount) && rawAmount > 0n) {
53962
54243
  if (!liquidity[asset]) liquidity[asset] = [];
@@ -53967,7 +54248,7 @@ async function fetchFlashLiquidityForChain(chain, multicallRetry, list = {}) {
53967
54248
  source: balancer.address,
53968
54249
  fee: "0",
53969
54250
  availableRaw: rawAmount.toString(),
53970
- available: Number(formatUnits(rawAmount, decimals ?? 18)),
54251
+ available: decimals === void 0 ? void 0 : Number(formatUnits(rawAmount, decimals)),
53971
54252
  decimals
53972
54253
  });
53973
54254
  }
@@ -53981,7 +54262,7 @@ async function fetchFlashLiquidityForChain(chain, multicallRetry, list = {}) {
53981
54262
  const rawAmount = data[i];
53982
54263
  if (isValidResult(rawAmount) && rawAmount > 0n) {
53983
54264
  if (!liquidity[asset]) liquidity[asset] = [];
53984
- const decimals = list[asset]?.decimals;
54265
+ const decimals = decimalsByAsset[asset];
53985
54266
  liquidity[asset].push({
53986
54267
  id: FLASH_LOAN_IDS[morpho.pool],
53987
54268
  name: morpho.pool,
@@ -53989,7 +54270,7 @@ async function fetchFlashLiquidityForChain(chain, multicallRetry, list = {}) {
53989
54270
  source: morpho.address,
53990
54271
  fee: "0",
53991
54272
  availableRaw: rawAmount.toString(),
53992
- available: Number(formatUnits(rawAmount, decimals ?? 18)),
54273
+ available: decimals === void 0 ? void 0 : Number(formatUnits(rawAmount, decimals)),
53993
54274
  decimals
53994
54275
  });
53995
54276
  }
@@ -54000,7 +54281,7 @@ async function fetchFlashLiquidityForChain(chain, multicallRetry, list = {}) {
54000
54281
  const data = rawResults.slice(currentOffset, callLen + currentOffset);
54001
54282
  currentOffset += callLen;
54002
54283
  unifiedAssets.forEach((asset, i) => {
54003
- const decimals = list[asset]?.decimals;
54284
+ const decimals = decimalsByAsset[asset];
54004
54285
  const rawAmount = data[i];
54005
54286
  if (isValidResult(rawAmount) && rawAmount > 0n) {
54006
54287
  if (!liquidity[asset]) liquidity[asset] = [];
@@ -54011,7 +54292,7 @@ async function fetchFlashLiquidityForChain(chain, multicallRetry, list = {}) {
54011
54292
  source: balancerV3.address,
54012
54293
  fee: "0",
54013
54294
  availableRaw: rawAmount.toString(),
54014
- available: Number(formatUnits(rawAmount, decimals ?? 18)),
54295
+ available: decimals === void 0 ? void 0 : Number(formatUnits(rawAmount, decimals)),
54015
54296
  decimals
54016
54297
  });
54017
54298
  }
@@ -54022,7 +54303,7 @@ async function fetchFlashLiquidityForChain(chain, multicallRetry, list = {}) {
54022
54303
  const data = rawResults.slice(currentOffset, callLen + currentOffset);
54023
54304
  currentOffset += callLen;
54024
54305
  unifiedAssets.forEach((asset, i) => {
54025
- const decimals = list[asset]?.decimals;
54306
+ const decimals = decimalsByAsset[asset];
54026
54307
  const rawAmount = data[i];
54027
54308
  if (isValidResult(rawAmount) && rawAmount > 0n) {
54028
54309
  if (!liquidity[asset]) liquidity[asset] = [];
@@ -54033,7 +54314,7 @@ async function fetchFlashLiquidityForChain(chain, multicallRetry, list = {}) {
54033
54314
  source: uniV4.address,
54034
54315
  fee: "0",
54035
54316
  availableRaw: rawAmount.toString(),
54036
- available: Number(formatUnits(rawAmount, decimals ?? 18)),
54317
+ available: decimals === void 0 ? void 0 : Number(formatUnits(rawAmount, decimals)),
54037
54318
  decimals
54038
54319
  });
54039
54320
  }
@@ -54050,7 +54331,9 @@ function attachPricesToFlashLiquidity(chainId, liq, prices, list = {}) {
54050
54331
  const price2 = prices[priceKey] ?? 0;
54051
54332
  liqCopy[asset] = entry.map((e) => ({
54052
54333
  ...e,
54053
- availableUSD: e.available * price2
54334
+ // absent when the token amount itself is unknown — never 0, which reads
54335
+ // as "no liquidity" rather than "not priced"
54336
+ availableUSD: e.available === void 0 ? void 0 : e.available * price2
54054
54337
  }));
54055
54338
  });
54056
54339
  return liqCopy;
@@ -65653,9 +65936,8 @@ function borrowDescription(b) {
65653
65936
  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
65937
  );
65655
65938
  } else {
65656
- parts.push(
65657
- `You pay ${pct(b.rate.apr)}${b.rate.isLocked ? ", locked for the term" : ", floating with utilization"}.`
65658
- );
65939
+ 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" : "";
65940
+ parts.push(`You pay ${pct(b.rate.apr)}${how}.`);
65659
65941
  }
65660
65942
  parts.push(
65661
65943
  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 +65956,27 @@ function borrowDescription(b) {
65674
65956
  `It matures on ${shortDate(b.maturity.maturity)}${what ? `; if nothing is done then, ${what}` : ""}.`
65675
65957
  );
65676
65958
  }
65959
+ const withheld = (b.fees ?? []).filter(
65960
+ (f) => f.when === "entry" && f.basis === "principal" && f.unit === "percent" && f.value > 0
65961
+ );
65962
+ if (withheld.length > 0) {
65963
+ const total = withheld.reduce((a, f) => a + f.value, 0);
65964
+ const named = withheld.map((f) => `${f.label.toLowerCase()} ${pct(f.value)}`).join(" + ");
65965
+ parts.push(
65966
+ `${pct(total)} of anything you borrow is withheld at open` + (withheld.length > 1 ? ` (${named})` : "") + `, so you receive ${pct(100 - total)} of what you owe.`
65967
+ );
65968
+ }
65677
65969
  if (b.liquidation.trigger === "time") {
65678
65970
  parts.push(
65679
65971
  "Liquidation here is triggered by TIME, not price \u2014 being late is the trigger, and being over-collateralised does not protect you."
65680
65972
  );
65681
- } else if (b.liquidation.liquidationLtv != null) {
65973
+ } else if (b.liquidation.model === "auction" && !(b.liquidation.liquidationLtv > 0)) {
65974
+ const pen = b.liquidation.penalty;
65975
+ const win = b.liquidation.windowSecs;
65976
+ parts.push(
65977
+ "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)}` : "") + "."
65978
+ );
65979
+ } else if (b.liquidation.liquidationLtv != null && b.liquidation.liquidationLtv > 0) {
65682
65980
  const pen = b.liquidation.penalty;
65683
65981
  parts.push(
65684
65982
  `Liquidation starts at ${pct(b.liquidation.liquidationLtv * 100)} LTV` + (pen != null ? `, with a ${pct(pen * 100)} penalty` : "") + "."