@morpho-dev/router 0.7.0 → 0.7.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.js CHANGED
@@ -152,7 +152,7 @@ function startActiveSpan(tracer, name, fn) {
152
152
  //#endregion
153
153
  //#region package.json
154
154
  var name = "@morpho-dev/router";
155
- var version = "0.7.0";
155
+ var version = "0.7.2";
156
156
  var description = "Router package for Morpho protocol";
157
157
 
158
158
  //#endregion
@@ -331,8 +331,8 @@ const chains$2 = {
331
331
  name: "ethereum-virtual-testnet",
332
332
  custom: {
333
333
  morpho: {
334
- address: "0x11a002d45db720ed47a80d2f3489cba5b833eaf5",
335
- blockCreated: 0
334
+ address: "0x634b095371e4e45feed94c1a45c37798e173ea50",
335
+ blockCreated: 23226700
336
336
  },
337
337
  morphoBlue: {
338
338
  address: "0xBBBBBbbBBb9cC5e90e3b3Af64bdAF62C37EEFFCb",
@@ -561,7 +561,7 @@ async function deployContract(client, account, bytecode, name) {
561
561
  */
562
562
  async function startAnvil(parameters) {
563
563
  const args = buildAnvilArgs(parameters);
564
- const logFd = fs.openSync(parameters.logPath, "a");
564
+ const logFd = fs.openSync(parameters.logPath, "w");
565
565
  const subprocess = spawn("anvil", args, {
566
566
  detached: true,
567
567
  stdio: [
@@ -1483,73 +1483,10 @@ function create$20(parameters) {
1483
1483
  //#region src/core/Callback.ts
1484
1484
  let Type$1 = /* @__PURE__ */ function(Type) {
1485
1485
  Type["BuyWithEmptyCallback"] = "buy_with_empty_callback";
1486
- Type["BuyERC20"] = "buy_erc20";
1487
- Type["BuyVaultV1Callback"] = "buy_vault_v1_callback";
1488
- Type["SellERC20Callback"] = "sell_erc20_callback";
1486
+ Type["SellWithEmptyCallback"] = "sell_with_empty_callback";
1489
1487
  return Type;
1490
1488
  }({});
1491
1489
  const isEmptyCallback = (offer) => offer.callback.data === "0x";
1492
- function decode$1(type, data) {
1493
- switch (type) {
1494
- case Type$1.BuyERC20: return decodeBuyERC20(data);
1495
- case Type$1.BuyVaultV1Callback: return decodeBuyVaultV1Callback(data);
1496
- case Type$1.SellERC20Callback: return decodeSellERC20Callback(data);
1497
- default: throw new Error("Invalid callback type");
1498
- }
1499
- }
1500
- /**
1501
- * Decodes BuyERC20 callback data into positions.
1502
- * @param data - The ABI-encoded callback data containing token addresses and amounts.
1503
- * @returns Array of positions with contract address and amount.
1504
- * @throws If data is empty, malformed, or arrays have mismatched lengths.
1505
- */
1506
- function decodeBuyERC20(data) {
1507
- if (!data || data === "0x") throw new Error("Empty callback data");
1508
- let tokens;
1509
- let amounts;
1510
- try {
1511
- [tokens, amounts] = decodeAbiParameters([{ type: "address[]" }, { type: "uint256[]" }], data);
1512
- } catch (_) {
1513
- throw new Error("Invalid BuyERC20 callback data");
1514
- }
1515
- if (tokens.length !== amounts.length) throw new Error("Mismatched array lengths");
1516
- return tokens.map((token, index) => ({
1517
- contract: token,
1518
- amount: amounts[index]
1519
- }));
1520
- }
1521
- function decodeBuyVaultV1Callback(data) {
1522
- if (!data || data === "0x") throw new Error("Empty callback data");
1523
- try {
1524
- const [vaults, amounts] = decodeAbiParameters([{ type: "address[]" }, { type: "uint256[]" }], data);
1525
- if (vaults.length !== amounts.length) throw new Error("Mismatched array lengths");
1526
- return vaults.map((v, i) => ({
1527
- contract: v,
1528
- amount: amounts[i]
1529
- }));
1530
- } catch (_) {
1531
- throw new Error("Invalid BuyVaultV1Callback callback data");
1532
- }
1533
- }
1534
- function decodeSellERC20Callback(data) {
1535
- if (!data || data === "0x") throw new Error("Empty callback data");
1536
- try {
1537
- const [collaterals, amounts] = decodeAbiParameters([{ type: "address[]" }, { type: "uint256[]" }], data);
1538
- if (collaterals.length !== amounts.length) throw new Error("Mismatched array lengths");
1539
- return collaterals.map((c, i) => ({
1540
- contract: c,
1541
- amount: amounts[i]
1542
- }));
1543
- } catch (_) {
1544
- throw new Error("Invalid SellERC20Callback callback data");
1545
- }
1546
- }
1547
- function encodeBuyVaultV1Callback(parameters) {
1548
- return encodeAbiParameters([{ type: "address[]" }, { type: "uint256[]" }], [parameters.vaults, parameters.amounts]);
1549
- }
1550
- function encodeSellERC20Callback(parameters) {
1551
- return encodeAbiParameters([{ type: "address[]" }, { type: "uint256[]" }], [parameters.collaterals, parameters.amounts]);
1552
- }
1553
1490
 
1554
1491
  //#endregion
1555
1492
  //#region src/core/Maturity.ts
@@ -1671,26 +1608,6 @@ var InvalidOptionError$1 = class extends BaseError {
1671
1608
 
1672
1609
  //#endregion
1673
1610
  //#region src/gatekeeper/GateConfig.ts
1674
- /**
1675
- * Attempts to infer the configured callback type from a callback address on a chain.
1676
- * Skips the empty callback type as it does not carry addresses.
1677
- *
1678
- * @param chain - Chain name for which to infer the callback type
1679
- * @param address - Callback contract address
1680
- * @returns The callback type when found, otherwise undefined
1681
- */
1682
- function getCallbackType(chain, address) {
1683
- return configs[chain].callbacks?.find((c) => c.type !== Type$1.BuyWithEmptyCallback && c.addresses.includes(address?.toLowerCase()))?.type;
1684
- }
1685
- /**
1686
- * Returns the list of allowed non-empty callback addresses for a chain.
1687
- *
1688
- * @param chain - Chain name
1689
- * @returns Array of allowed callback addresses (lowercased). Empty when none configured
1690
- */
1691
- const getCallbackAddresses = (chain) => {
1692
- return configs[chain].callbacks?.filter((c) => c.type !== Type$1.BuyWithEmptyCallback).flatMap((c) => c.addresses) ?? [];
1693
- };
1694
1611
  const assets = {
1695
1612
  [ChainId.ETHEREUM.toString()]: [
1696
1613
  "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
@@ -1762,63 +1679,19 @@ const oracles$1 = {
1762
1679
  };
1763
1680
  const configs = {
1764
1681
  ethereum: {
1765
- callbacks: [
1766
- {
1767
- type: Type$1.BuyVaultV1Callback,
1768
- addresses: ["0x3333333333333333333333333333333333333333", "0x4444444444444444444444444444444444444444"],
1769
- vaultFactories: ["0xA9c3D3a366466Fa809d1Ae982Fb2c46E5fC41101", "0x1897A8997241C1cD4bD0698647e4EB7213535c24"]
1770
- },
1771
- {
1772
- type: Type$1.SellERC20Callback,
1773
- addresses: ["0x1111111111111111111111111111111111111111", "0x2222222222222222222222222222222222222222"]
1774
- },
1775
- { type: Type$1.BuyWithEmptyCallback }
1776
- ],
1682
+ callbacks: [{ type: Type$1.BuyWithEmptyCallback }, { type: Type$1.SellWithEmptyCallback }],
1777
1683
  maturities: [MaturityType.EndOfMonth, MaturityType.EndOfNextMonth]
1778
1684
  },
1779
1685
  base: {
1780
- callbacks: [
1781
- {
1782
- type: Type$1.BuyVaultV1Callback,
1783
- addresses: ["0x3333333333333333333333333333333333333333", "0x4444444444444444444444444444444444444444"],
1784
- vaultFactories: ["0xA9c3D3a366466Fa809d1Ae982Fb2c46E5fC41101", "0xFf62A7c278C62eD665133147129245053Bbf5918"]
1785
- },
1786
- {
1787
- type: Type$1.SellERC20Callback,
1788
- addresses: ["0x1111111111111111111111111111111111111111", "0x2222222222222222222222222222222222222222"]
1789
- },
1790
- { type: Type$1.BuyWithEmptyCallback }
1791
- ],
1686
+ callbacks: [{ type: Type$1.BuyWithEmptyCallback }, { type: Type$1.SellWithEmptyCallback }],
1792
1687
  maturities: [MaturityType.EndOfMonth, MaturityType.EndOfNextMonth]
1793
1688
  },
1794
1689
  "ethereum-virtual-testnet": {
1795
- callbacks: [
1796
- {
1797
- type: Type$1.BuyVaultV1Callback,
1798
- addresses: ["0x3333333333333333333333333333333333333333", "0x4444444444444444444444444444444444444444"],
1799
- vaultFactories: ["0xA9c3D3a366466Fa809d1Ae982Fb2c46E5fC41101", "0x1897A8997241C1cD4bD0698647e4EB7213535c24"]
1800
- },
1801
- {
1802
- type: Type$1.SellERC20Callback,
1803
- addresses: ["0x1111111111111111111111111111111111111111", "0x2222222222222222222222222222222222222222"]
1804
- },
1805
- { type: Type$1.BuyWithEmptyCallback }
1806
- ],
1690
+ callbacks: [{ type: Type$1.BuyWithEmptyCallback }, { type: Type$1.SellWithEmptyCallback }],
1807
1691
  maturities: [MaturityType.EndOfMonth, MaturityType.EndOfNextMonth]
1808
1692
  },
1809
1693
  anvil: {
1810
- callbacks: [
1811
- {
1812
- type: Type$1.BuyVaultV1Callback,
1813
- addresses: ["0x3333333333333333333333333333333333333333", "0x4444444444444444444444444444444444444444"],
1814
- vaultFactories: ["0xA9c3D3a366466Fa809d1Ae982Fb2c46E5fC41101", "0x1897A8997241C1cD4bD0698647e4EB7213535c24"]
1815
- },
1816
- {
1817
- type: Type$1.SellERC20Callback,
1818
- addresses: ["0x1111111111111111111111111111111111111111", "0x2222222222222222222222222222222222222222"]
1819
- },
1820
- { type: Type$1.BuyWithEmptyCallback }
1821
- ],
1694
+ callbacks: [{ type: Type$1.BuyWithEmptyCallback }, { type: Type$1.SellWithEmptyCallback }],
1822
1695
  maturities: [MaturityType.EndOfMonth, MaturityType.EndOfNextMonth]
1823
1696
  }
1824
1697
  };
@@ -1841,6 +1714,61 @@ const MetaMorpho = parseAbi([
1841
1714
  //#region src/core/Abi/MetaMorphoFactory.ts
1842
1715
  const MetaMorphoFactory = parseAbi(["event CreateMetaMorpho(address indexed metaMorpho,address indexed caller,address initialOwner,uint256 initialTimelock,address indexed asset,string name,string symbol,bytes32 salt)", "function isMetaMorpho(address) view returns (bool)"]);
1843
1716
 
1717
+ //#endregion
1718
+ //#region src/core/Abi/MorphoV2.ts
1719
+ const MorphoV2 = parseAbi([
1720
+ "constructor()",
1721
+ "function collateralOf(bytes32 id, address user, address collateralToken) view returns (uint256)",
1722
+ "function consume(bytes32 group, uint256 amount)",
1723
+ "function consumed(address user, bytes32 group) view returns (uint256)",
1724
+ "function debtOf(bytes32 id, address user) view returns (uint256)",
1725
+ "function defaultFees(address loanToken, uint256 index) view returns (uint16)",
1726
+ "function feeSetter() view returns (address)",
1727
+ "function fees(bytes32 id) view returns (uint16[6])",
1728
+ "function flashLoan(address token, uint256 assets, address callback, bytes data)",
1729
+ "function isHealthy((address loanToken, (address token, uint256 lltv, address oracle)[] collaterals, uint256 maturity) obligation, bytes32 id, address borrower) view returns (bool)",
1730
+ "function liquidate((address loanToken, (address token, uint256 lltv, address oracle)[] collaterals, uint256 maturity) obligation, (uint256 collateralIndex, uint256 repaid, uint256 seized)[] seizures, address borrower, bytes data) returns ((uint256 collateralIndex, uint256 repaid, uint256 seized)[])",
1731
+ "function multicall(bytes[] calls)",
1732
+ "function obligationCreated(bytes32 id) view returns (bool)",
1733
+ "function obligationState(bytes32 id) view returns (uint128 totalUnits, uint128 totalShares, uint256 withdrawable, bool created)",
1734
+ "function owner() view returns (address)",
1735
+ "function repay((address loanToken, (address token, uint256 lltv, address oracle)[] collaterals, uint256 maturity) obligation, uint256 obligationUnits, address onBehalf)",
1736
+ "function session(address user) view returns (bytes32)",
1737
+ "function setDefaultTradingFee(address loanToken, uint256 index, uint256 newTradingFee)",
1738
+ "function setFeeSetter(address newFeeSetter)",
1739
+ "function setObligationTradingFee(bytes32 id, uint256 index, uint256 newTradingFee)",
1740
+ "function setOwner(address newOwner)",
1741
+ "function setTradingFeeRecipient(address recipient)",
1742
+ "function sharesOf(bytes32 id, address user) view returns (uint256)",
1743
+ "function shuffleSession()",
1744
+ "function supplyCollateral((address loanToken, (address token, uint256 lltv, address oracle)[] collaterals, uint256 maturity) obligation, address collateral, uint256 assets, address onBehalf)",
1745
+ "function take(uint256 buyerAssets, uint256 sellerAssets, uint256 obligationUnits, uint256 obligationShares, address taker, ((address loanToken, (address token, uint256 lltv, address oracle)[] collaterals, uint256 maturity) obligation, bool buy, address maker, uint256 assets, uint256 obligationUnits, uint256 obligationShares, uint256 start, uint256 expiry, uint256 tick, bytes32 group, bytes32 session, address callback, bytes callbackData) offer, (uint8 v, bytes32 r, bytes32 s) sig, bytes32 root, bytes32[] proof, address takerCallback, bytes takerCallbackData) returns (uint256, uint256, uint256, uint256)",
1746
+ "function totalShares(bytes32 id) view returns (uint256)",
1747
+ "function totalUnits(bytes32 id) view returns (uint256)",
1748
+ "function touchObligation((address loanToken, (address token, uint256 lltv, address oracle)[] collaterals, uint256 maturity) obligation) returns (bytes32)",
1749
+ "function tradingFee(bytes32 id, uint256 timeToMaturity) view returns (uint256)",
1750
+ "function tradingFeeRecipient() view returns (address)",
1751
+ "function withdraw((address loanToken, (address token, uint256 lltv, address oracle)[] collaterals, uint256 maturity) obligation, uint256 obligationUnits, uint256 shares, address onBehalf) returns (uint256, uint256)",
1752
+ "function withdrawCollateral((address loanToken, (address token, uint256 lltv, address oracle)[] collaterals, uint256 maturity) obligation, address collateral, uint256 assets, address onBehalf)",
1753
+ "function withdrawable(bytes32 id) view returns (uint256)",
1754
+ "event Constructor(address indexed owner)",
1755
+ "event Consume(address indexed user, bytes32 indexed group, uint256 amount)",
1756
+ "event FlashLoan(address indexed caller, address indexed token, uint256 assets)",
1757
+ "event Liquidate(address indexed caller, bytes32 indexed id, (uint256 collateralIndex, uint256 repaid, uint256 seized)[] seizures, address indexed borrower, uint256 totalRepaid, uint256 badDebt)",
1758
+ "event ObligationCreated(bytes32 indexed id, (address loanToken, (address token, uint256 lltv, address oracle)[] collaterals, uint256 maturity) obligation)",
1759
+ "event Repay(address indexed caller, bytes32 indexed id, uint256 obligationUnits, address indexed onBehalf)",
1760
+ "event SetDefaultTradingFee(address indexed loanToken, uint256 indexed index, uint256 newTradingFee)",
1761
+ "event SetFeeSetter(address indexed feeSetter)",
1762
+ "event SetObligationTradingFee(bytes32 indexed id, uint256 indexed index, uint256 newTradingFee)",
1763
+ "event SetOwner(address indexed owner)",
1764
+ "event SetTradingFeeRecipient(address indexed recipient)",
1765
+ "event ShuffleSession(address indexed user, bytes32 session)",
1766
+ "event SupplyCollateral(address caller, bytes32 indexed id, address indexed collateral, uint256 assets, address indexed onBehalf)",
1767
+ "event Take(address caller, bytes32 indexed id, address indexed maker, address indexed taker, bool offerIsBuy, uint256 buyerAssets, uint256 sellerAssets, uint256 obligationUnits, uint256 obligationShares, bool buyerIsLender, bool sellerIsBorrower, bytes32 group, uint256 consumed)",
1768
+ "event Withdraw(address indexed caller, bytes32 indexed id, uint256 obligationUnits, uint256 shares, address indexed onBehalf)",
1769
+ "event WithdrawCollateral(address caller, bytes32 indexed id, address indexed collateral, uint256 assets, address indexed onBehalf)"
1770
+ ]);
1771
+
1844
1772
  //#endregion
1845
1773
  //#region src/core/Abi/index.ts
1846
1774
  const Oracle = [{
@@ -2300,7 +2228,7 @@ function random(config) {
2300
2228
  const chain = config?.chains ? config.chains[int(config.chains.length)] : chains$2.ethereum;
2301
2229
  const loanToken = config?.loanTokens ? config.loanTokens[int(config.loanTokens.length)] : address();
2302
2230
  const collateralCandidates = config?.collateralTokens ? config.collateralTokens.filter((a) => a !== loanToken) : [address()];
2303
- const collateralAsset = collateralCandidates[int(collateralCandidates.length)];
2231
+ collateralCandidates[int(collateralCandidates.length)];
2304
2232
  const maturityOption = weightedChoice([["end_of_month", 1], ["end_of_next_month", 1]]);
2305
2233
  const maturity = config?.maturity ?? from$16(maturityOption);
2306
2234
  const lltv = from$15(weightedChoice([
@@ -2327,21 +2255,10 @@ function random(config) {
2327
2255
  const unit = BigInt(10) ** BigInt(loanTokenDecimals);
2328
2256
  const amountBase = BigInt(100 + int(999901));
2329
2257
  const assetsScaled = config?.assets ?? amountBase * unit;
2330
- const callbackBySide = (() => {
2331
- if (buy) return {
2332
- address: zeroAddress,
2333
- data: "0x"
2334
- };
2335
- const sellCallbackAddress = "0x3333333333333333333333333333333333333333";
2336
- const amount = assetsScaled * 1000000000000000000000n;
2337
- return {
2338
- address: sellCallbackAddress,
2339
- data: encodeSellERC20Callback({
2340
- collaterals: [collateralAsset],
2341
- amounts: [amount]
2342
- })
2343
- };
2344
- })();
2258
+ const emptyCallback = {
2259
+ address: zeroAddress,
2260
+ data: "0x"
2261
+ };
2345
2262
  return from$12({
2346
2263
  maker: config?.maker ?? address(),
2347
2264
  assets: assetsScaled,
@@ -2360,7 +2277,7 @@ function random(config) {
2360
2277
  ...random$1(),
2361
2278
  lltv
2362
2279
  })).sort((a, b) => a.asset.localeCompare(b.asset)),
2363
- callback: config?.callback ?? callbackBySide
2280
+ callback: config?.callback ?? emptyCallback
2364
2281
  });
2365
2282
  }
2366
2283
  const weightedChoice = (pairs) => {
@@ -2514,6 +2431,94 @@ function obligationId(offer) {
2514
2431
  }));
2515
2432
  }
2516
2433
  /**
2434
+ * ABI for the Take event emitted by the Morpho V2 contract.
2435
+ */
2436
+ const takeEvent = {
2437
+ type: "event",
2438
+ name: "Take",
2439
+ inputs: [
2440
+ {
2441
+ name: "caller",
2442
+ type: "address",
2443
+ indexed: false,
2444
+ internalType: "address"
2445
+ },
2446
+ {
2447
+ name: "id",
2448
+ type: "bytes32",
2449
+ indexed: true,
2450
+ internalType: "bytes32"
2451
+ },
2452
+ {
2453
+ name: "maker",
2454
+ type: "address",
2455
+ indexed: true,
2456
+ internalType: "address"
2457
+ },
2458
+ {
2459
+ name: "taker",
2460
+ type: "address",
2461
+ indexed: true,
2462
+ internalType: "address"
2463
+ },
2464
+ {
2465
+ name: "offerIsBuy",
2466
+ type: "bool",
2467
+ indexed: false,
2468
+ internalType: "bool"
2469
+ },
2470
+ {
2471
+ name: "buyerAssets",
2472
+ type: "uint256",
2473
+ indexed: false,
2474
+ internalType: "uint256"
2475
+ },
2476
+ {
2477
+ name: "sellerAssets",
2478
+ type: "uint256",
2479
+ indexed: false,
2480
+ internalType: "uint256"
2481
+ },
2482
+ {
2483
+ name: "obligationUnits",
2484
+ type: "uint256",
2485
+ indexed: false,
2486
+ internalType: "uint256"
2487
+ },
2488
+ {
2489
+ name: "obligationShares",
2490
+ type: "uint256",
2491
+ indexed: false,
2492
+ internalType: "uint256"
2493
+ },
2494
+ {
2495
+ name: "buyerIsLender",
2496
+ type: "bool",
2497
+ indexed: false,
2498
+ internalType: "bool"
2499
+ },
2500
+ {
2501
+ name: "sellerIsBorrower",
2502
+ type: "bool",
2503
+ indexed: false,
2504
+ internalType: "bool"
2505
+ },
2506
+ {
2507
+ name: "group",
2508
+ type: "bytes32",
2509
+ indexed: false,
2510
+ internalType: "bytes32"
2511
+ },
2512
+ {
2513
+ name: "consumed",
2514
+ type: "uint256",
2515
+ indexed: false,
2516
+ internalType: "uint256"
2517
+ }
2518
+ ],
2519
+ anonymous: false
2520
+ };
2521
+ /**
2517
2522
  * ABI for the Consume event emitted by the Obligation contract.
2518
2523
  */
2519
2524
  const consumedEvent = {
@@ -2939,12 +2944,10 @@ const maturity = ({ maturities }) => single("maturity", `Validates that offer ma
2939
2944
  const allowedMaturities = maturities.map((m) => from$16(m));
2940
2945
  if (!allowedMaturities.includes(offer.maturity)) return { message: `Maturity must be end of current month (${allowedMaturities[0]}) or end of next month (${allowedMaturities[1]}). Got: ${offer.maturity}` };
2941
2946
  });
2942
- const callback = ({ callbacks, allowedAddresses }) => single("callback", `Validates callbacks: buy empty callback is ${callbacks.includes(Type$1.BuyWithEmptyCallback) ? "allowed" : "not allowed"}; sell offers must use a non-empty callback; non-empty callbacks must target one of [${allowedAddresses.map((a) => a.toLowerCase()).join(", ")}]`, (offer) => {
2943
- if (isEmptyCallback(offer) && offer.buy && !callbacks?.find((c) => c === Type$1.BuyWithEmptyCallback)) return { message: "Buy offers with empty callback not allowed." };
2944
- if (isEmptyCallback(offer) && !offer.buy) return { message: "Sell offers require a non-empty callback." };
2945
- if (!isEmptyCallback(offer)) {
2946
- if (!allowedAddresses.includes(offer.callback.address?.toLowerCase())) return { message: `Callback address ${offer.callback.address} is not allowed.` };
2947
- }
2947
+ const callback = ({ callbacks }) => single("callback", `Validates callbacks: buy empty callback is ${callbacks.includes(Type$1.BuyWithEmptyCallback) ? "allowed" : "not allowed"}; sell empty callback is ${callbacks.includes(Type$1.SellWithEmptyCallback) ? "allowed" : "not allowed"}; non-empty callbacks are rejected`, (offer) => {
2948
+ if (!isEmptyCallback(offer)) return { message: "Non-empty callbacks are not supported." };
2949
+ if (isEmptyCallback(offer) && offer.buy && !callbacks.includes(Type$1.BuyWithEmptyCallback)) return { message: "Buy offers with empty callback not allowed." };
2950
+ if (isEmptyCallback(offer) && !offer.buy && !callbacks.includes(Type$1.SellWithEmptyCallback)) return { message: "Sell offers with empty callback not allowed." };
2948
2951
  });
2949
2952
  /**
2950
2953
  * A validation rule that checks if the offer's tokens are allowed for its chain.
@@ -3009,12 +3012,8 @@ const morphoRules = (chains) => {
3009
3012
  chains$1({ chains }),
3010
3013
  maturity({ maturities: [MaturityType.EndOfMonth, MaturityType.EndOfNextMonth] }),
3011
3014
  callback({
3012
- callbacks: [
3013
- Type$1.BuyWithEmptyCallback,
3014
- Type$1.BuyVaultV1Callback,
3015
- Type$1.SellERC20Callback
3016
- ],
3017
- allowedAddresses: chains.flatMap((c) => getCallbackAddresses(c.name))
3015
+ callbacks: [Type$1.BuyWithEmptyCallback, Type$1.SellWithEmptyCallback],
3016
+ allowedAddresses: []
3018
3017
  }),
3019
3018
  token({ assetsByChainId }),
3020
3019
  oracle({ oraclesByChainId })
@@ -3031,25 +3030,13 @@ const morphoRules = (chains) => {
3031
3030
  function buildConfigRules(chains) {
3032
3031
  const rules = [];
3033
3032
  for (const chain of chains) {
3034
- const config = configs[chain.name];
3035
- const maturities = config.maturities ?? [];
3033
+ const maturities = configs[chain.name].maturities ?? [];
3036
3034
  for (const maturityName of maturities) rules.push({
3037
3035
  type: "maturity",
3038
3036
  chain_id: chain.id,
3039
3037
  name: maturityName,
3040
3038
  timestamp: from$16(maturityName)
3041
3039
  });
3042
- const callbacks = config.callbacks ?? [];
3043
- for (const callback of callbacks) {
3044
- if (callback.type === Type$1.BuyWithEmptyCallback) continue;
3045
- if (!("addresses" in callback)) continue;
3046
- for (const address of callback.addresses) rules.push({
3047
- type: "callback",
3048
- chain_id: chain.id,
3049
- address: normalizeAddress(address),
3050
- callback_type: callback.type
3051
- });
3052
- }
3053
3040
  const loanTokens = assets[chain.id.toString()] ?? [];
3054
3041
  for (const address of loanTokens) rules.push({
3055
3042
  type: "loan_token",
@@ -3177,6 +3164,16 @@ function from$5(obligation, quote) {
3177
3164
 
3178
3165
  //#endregion
3179
3166
  //#region src/api/Schema/OfferResponse.ts
3167
+ function normalizeChainId(chainId) {
3168
+ const parsedChainId = Number(chainId);
3169
+ if (!Number.isInteger(parsedChainId) || parsedChainId <= 0) throw new Error(`Invalid chain id: ${String(chainId)}`);
3170
+ return parsedChainId;
3171
+ }
3172
+ function normalizeBlockNumber(blockNumber) {
3173
+ const parsedBlockNumber = Number(blockNumber);
3174
+ if (!Number.isInteger(parsedBlockNumber) || parsedBlockNumber < 0) throw new Error(`Invalid block number: ${String(blockNumber)}`);
3175
+ return parsedBlockNumber;
3176
+ }
3180
3177
  /**
3181
3178
  * Creates an `OfferResponse` matching the Solidity Offer struct layout.
3182
3179
  * @constructor
@@ -3184,6 +3181,8 @@ function from$5(obligation, quote) {
3184
3181
  * @returns The created `OfferResponse`. {@link OfferResponse}
3185
3182
  */
3186
3183
  function from$4(input) {
3184
+ const chainId = normalizeChainId(input.chainId);
3185
+ const blockNumber = normalizeBlockNumber(input.blockNumber);
3187
3186
  const base = {
3188
3187
  offer: {
3189
3188
  obligation: {
@@ -3210,15 +3209,15 @@ function from$4(input) {
3210
3209
  },
3211
3210
  offer_hash: input.hash,
3212
3211
  obligation_id: id({
3213
- chainId: input.chainId,
3212
+ chainId,
3214
3213
  loanToken: input.loanToken,
3215
3214
  collaterals: [...input.collaterals],
3216
3215
  maturity: input.maturity
3217
3216
  }),
3218
- chain_id: input.chainId,
3217
+ chain_id: chainId,
3219
3218
  consumed: input.consumed.toString(),
3220
3219
  takeable: input.takeable.toString(),
3221
- block_number: input.blockNumber
3220
+ block_number: blockNumber
3222
3221
  };
3223
3222
  if (!input.proof || !input.root || !input.signature) return {
3224
3223
  ...base,
@@ -3367,8 +3366,8 @@ const offerExample = {
3367
3366
  price: "2750000000000000000",
3368
3367
  group: "0x000000000000000000000000000000000000000000000000000000000008b8f4",
3369
3368
  session: "0x0000000000000000000000000000000000000000000000000000000000000000",
3370
- callback: "0x1111111111111111111111111111111111111111",
3371
- callback_data: "0x00000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000000100000000000000000000000034cf890db685fc536e05652fb41f02090c3fb751000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000108e644e3ab01184155270aa92a00000000000"
3369
+ callback: "0x0000000000000000000000000000000000000000",
3370
+ callback_data: "0x"
3372
3371
  },
3373
3372
  offer_hash: "0xac4bd8318ec914f89f8af913f162230575b0ac0696a19256bc12138c5cfe1427",
3374
3373
  obligation_id: "0x25690ae1aee324a005be565f3bcdd16dbf8daf7969b26c181c8b8f467dad9abc",
@@ -3420,25 +3419,10 @@ const validateOfferExample = {
3420
3419
  lltv: "860000000000000000"
3421
3420
  }],
3422
3421
  callback: {
3423
- address: "0x1111111111111111111111111111111111111111",
3424
- data: "0x00000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000000100000000000000000000000034cf890db685fc536e05652fb41f02090c3fb751000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000108e644e3ab01184155270aa92a00000000000"
3422
+ address: "0x0000000000000000000000000000000000000000",
3423
+ data: "0x"
3425
3424
  }
3426
3425
  };
3427
- const callbackTypesRequestExample = { callbacks: [{
3428
- chain_id: 1,
3429
- addresses: [
3430
- "0x1111111111111111111111111111111111111111",
3431
- "0x3333333333333333333333333333333333333333",
3432
- "0x9999999999999999999999999999999999999999"
3433
- ]
3434
- }] };
3435
- const callbackTypesResponseExample = [{
3436
- chain_id: 1,
3437
- sell_erc20_callback: ["0x1111111111111111111111111111111111111111"],
3438
- buy_erc20: ["0x5555555555555555555555555555555555555555"],
3439
- buy_vault_v1_callback: ["0x3333333333333333333333333333333333333333"],
3440
- not_supported: ["0x9999999999999999999999999999999999999999"]
3441
- }];
3442
3426
  const routerStatusExample = {
3443
3427
  status: "live",
3444
3428
  initialized: true,
@@ -3507,55 +3491,6 @@ __decorate([ApiProperty({
3507
3491
  type: "string",
3508
3492
  example: validateOfferExample.callback.data
3509
3493
  })], ValidateCallbackRequest.prototype, "data", void 0);
3510
- var CallbackTypesChainRequest = class {};
3511
- __decorate([ApiProperty({
3512
- type: "number",
3513
- example: callbackTypesRequestExample.callbacks[0].chain_id
3514
- })], CallbackTypesChainRequest.prototype, "chain_id", void 0);
3515
- __decorate([ApiProperty({
3516
- type: () => [String],
3517
- example: callbackTypesRequestExample.callbacks[0].addresses
3518
- })], CallbackTypesChainRequest.prototype, "addresses", void 0);
3519
- var CallbackTypesRequest = class {};
3520
- __decorate([ApiProperty({
3521
- type: () => [CallbackTypesChainRequest],
3522
- example: callbackTypesRequestExample.callbacks
3523
- })], CallbackTypesRequest.prototype, "callbacks", void 0);
3524
- var CallbackTypesChainResponse = class {};
3525
- __decorate([ApiProperty({
3526
- type: "number",
3527
- example: callbackTypesResponseExample[0].chain_id
3528
- })], CallbackTypesChainResponse.prototype, "chain_id", void 0);
3529
- __decorate([ApiProperty({
3530
- type: () => [String],
3531
- required: false,
3532
- example: callbackTypesResponseExample[0].buy_vault_v1_callback
3533
- })], CallbackTypesChainResponse.prototype, "buy_vault_v1_callback", void 0);
3534
- __decorate([ApiProperty({
3535
- type: () => [String],
3536
- required: false,
3537
- example: callbackTypesResponseExample[0].sell_erc20_callback
3538
- })], CallbackTypesChainResponse.prototype, "sell_erc20_callback", void 0);
3539
- __decorate([ApiProperty({
3540
- type: () => [String],
3541
- required: false,
3542
- example: callbackTypesResponseExample[0].buy_erc20
3543
- })], CallbackTypesChainResponse.prototype, "buy_erc20", void 0);
3544
- __decorate([ApiProperty({
3545
- type: () => [String],
3546
- example: callbackTypesResponseExample[0].not_supported
3547
- })], CallbackTypesChainResponse.prototype, "not_supported", void 0);
3548
- var CallbackTypesSuccessResponse = class extends SuccessResponse {};
3549
- __decorate([ApiProperty({
3550
- type: "string",
3551
- nullable: true,
3552
- example: "maturity:1:1730415600:end_of_next_month"
3553
- })], CallbackTypesSuccessResponse.prototype, "cursor", void 0);
3554
- __decorate([ApiProperty({
3555
- type: () => [CallbackTypesChainResponse],
3556
- description: "Callback types grouped by chain.",
3557
- example: callbackTypesResponseExample
3558
- })], CallbackTypesSuccessResponse.prototype, "data", void 0);
3559
3494
  var AskResponse = class {};
3560
3495
  __decorate([ApiProperty({
3561
3496
  type: "string",
@@ -4073,7 +4008,7 @@ __decorate([
4073
4008
  methods: ["post"],
4074
4009
  path: "/v1/validate",
4075
4010
  summary: "Validate offers",
4076
- description: "Validates offers against router validation rules. Returns unsigned payload + root on success, or issues only on validation failure."
4011
+ description: "Validates offers against router validation rules. Only empty callbacks (zero address, 0x data) are accepted. Returns unsigned payload + root on success, or issues only on validation failure."
4077
4012
  }),
4078
4013
  ApiBody({ type: ValidateOffersRequest }),
4079
4014
  ApiResponse({
@@ -4092,28 +4027,6 @@ ValidateController = __decorate([ApiTags("Make"), ApiResponse({
4092
4027
  description: "Bad Request",
4093
4028
  type: BadRequestResponse
4094
4029
  })], ValidateController);
4095
- let CallbacksController = class CallbacksController {
4096
- async resolveCallbackTypes() {}
4097
- };
4098
- __decorate([
4099
- ApiOperation({
4100
- methods: ["post"],
4101
- path: "/v1/callbacks",
4102
- summary: "Resolve callback types",
4103
- description: "Returns callback types for callback addresses grouped by chain."
4104
- }),
4105
- ApiBody({ type: CallbackTypesRequest }),
4106
- ApiResponse({
4107
- status: 200,
4108
- description: "Success",
4109
- type: CallbackTypesSuccessResponse
4110
- })
4111
- ], CallbacksController.prototype, "resolveCallbackTypes", null);
4112
- CallbacksController = __decorate([ApiTags("Make"), ApiResponse({
4113
- status: 400,
4114
- description: "Bad Request",
4115
- type: BadRequestResponse
4116
- })], CallbacksController);
4117
4030
  let OffersController = class OffersController {
4118
4031
  async getOffers() {}
4119
4032
  };
@@ -4263,12 +4176,6 @@ const configRulesMaturityExample = {
4263
4176
  name: "end_of_next_month",
4264
4177
  timestamp: 1730415600
4265
4178
  };
4266
- const configRulesCallbackExample = {
4267
- type: "callback",
4268
- chain_id: 1,
4269
- address: "0x1111111111111111111111111111111111111111",
4270
- callback_type: "sell_erc20_callback"
4271
- };
4272
4179
  const configRulesLoanTokenExample = {
4273
4180
  type: "loan_token",
4274
4181
  chain_id: 1,
@@ -4282,7 +4189,6 @@ const configRulesOracleExample = {
4282
4189
  const configRulesChecksumExample = "f1d2d2f924e986ac86fdf7b36c94bcdf";
4283
4190
  const configRulesPayloadExample = [
4284
4191
  configRulesMaturityExample,
4285
- configRulesCallbackExample,
4286
4192
  configRulesLoanTokenExample,
4287
4193
  configRulesOracleExample
4288
4194
  ];
@@ -4347,14 +4253,9 @@ __decorate([ApiProperty({
4347
4253
  })], ConfigRulesRuleResponse.prototype, "timestamp", void 0);
4348
4254
  __decorate([ApiProperty({
4349
4255
  type: "string",
4350
- example: configRulesCallbackExample.address,
4256
+ example: configRulesLoanTokenExample.address,
4351
4257
  required: false
4352
4258
  })], ConfigRulesRuleResponse.prototype, "address", void 0);
4353
- __decorate([ApiProperty({
4354
- type: "string",
4355
- example: configRulesCallbackExample.callback_type,
4356
- required: false
4357
- })], ConfigRulesRuleResponse.prototype, "callback_type", void 0);
4358
4259
  var ConfigRulesSuccessResponse = class {};
4359
4260
  __decorate([ApiProperty({ type: () => ConfigRulesMeta })], ConfigRulesSuccessResponse.prototype, "meta", void 0);
4360
4261
  __decorate([ApiProperty({
@@ -4415,7 +4316,7 @@ __decorate([
4415
4316
  methods: ["get"],
4416
4317
  path: "/v1/config/rules",
4417
4318
  summary: "Get config rules",
4418
- description: "Returns configured rules for supported chains."
4319
+ description: "Returns configured rules (maturities, loan tokens, oracles) for supported chains."
4419
4320
  }),
4420
4321
  ApiQuery({
4421
4322
  name: "cursor",
@@ -4594,8 +4495,7 @@ const OpenApi = async () => {
4594
4495
  ObligationsController,
4595
4496
  HealthController,
4596
4497
  UsersController,
4597
- ValidateController,
4598
- CallbacksController
4498
+ ValidateController
4599
4499
  ],
4600
4500
  document: {
4601
4501
  openapi: "3.1.0",
@@ -4851,16 +4751,6 @@ const GetBookParams = z$2.object({
4851
4751
  })
4852
4752
  });
4853
4753
  const ValidateOffersBody = z$2.object({ offers: z$2.array(z$2.unknown()).min(1, { message: "'offers' must contain at least 1 offer" }) }).strict();
4854
- const CallbackTypesBody = z$2.object({ callbacks: z$2.array(z$2.object({
4855
- chain_id: z$2.number().int().positive().meta({
4856
- description: "Chain id.",
4857
- example: 1
4858
- }),
4859
- addresses: z$2.array(z$2.string().regex(/^0x[a-fA-F0-9]{40}$/, { error: "Callback address must be a valid 20-byte address" }).transform((val) => val.toLowerCase())).meta({
4860
- description: "Callback contract addresses.",
4861
- example: ["0x1111111111111111111111111111111111111111", "0x3333333333333333333333333333333333333333"]
4862
- })
4863
- }).strict()) }).strict();
4864
4754
  const GetUserPositionsParams = z$2.object({
4865
4755
  ...PaginationQueryParams.shape,
4866
4756
  user_address: z$2.string().regex(/^0x[a-fA-F0-9]{40}$/, { error: "User address must be a valid 20-byte address" }).transform((val) => val.toLowerCase()).meta({
@@ -4879,7 +4769,6 @@ const schemas = {
4879
4769
  get_obligation: GetObligationParams,
4880
4770
  get_book: GetBookParams,
4881
4771
  validate_offers: ValidateOffersBody,
4882
- callback_types: CallbackTypesBody,
4883
4772
  get_user_positions: GetUserPositionsParams
4884
4773
  };
4885
4774
  function safeParse(action, query, error) {
@@ -5135,35 +5024,6 @@ async function validateOffers(body, gatekeeper) {
5135
5024
  }
5136
5025
  }
5137
5026
 
5138
- //#endregion
5139
- //#region src/gatekeeper/CallbackTypes.ts
5140
- /**
5141
- * Resolve callback types for a list of callback addresses grouped by chain.
5142
- * @param parameters - Resolve parameters. {@link resolveCallbackTypes.Parameters}
5143
- * @returns Callback types grouped by chain. {@link resolveCallbackTypes.ReturnType}
5144
- * @throws If a chain id is unknown.
5145
- */
5146
- function resolveCallbackTypes$2(parameters) {
5147
- const { chains, request } = parameters;
5148
- const chainsById = new Map(chains.map((chain) => [chain.id, chain]));
5149
- return request.callbacks.map(({ chain_id, addresses }) => {
5150
- const chain = chainsById.get(chain_id);
5151
- if (!chain) throw new Error(`Unknown chain id ${chain_id}`);
5152
- const buckets = /* @__PURE__ */ new Map();
5153
- const uniqueAddresses = new Set(addresses.map((address) => address.toLowerCase()));
5154
- for (const address of uniqueAddresses) {
5155
- const bucketKey = getCallbackType(chain.name, address) ?? "not_supported";
5156
- const list = buckets.get(bucketKey) ?? [];
5157
- list.push(address);
5158
- buckets.set(bucketKey, list);
5159
- }
5160
- const response = { chain_id };
5161
- for (const [type, list] of buckets.entries()) response[type] = list;
5162
- if (!response.not_supported) response.not_supported = [];
5163
- return response;
5164
- });
5165
- }
5166
-
5167
5027
  //#endregion
5168
5028
  //#region src/gatekeeper/Service.ts
5169
5029
  /**
@@ -5171,10 +5031,6 @@ function resolveCallbackTypes$2(parameters) {
5171
5031
  * @param parameters - App parameters including the {@link Gatekeeper} instance.
5172
5032
  * @returns Hono app exposing gatekeeper endpoints.
5173
5033
  */
5174
- const CallbackTypesRequestSchema = z$2.object({ callbacks: z$2.array(z$2.object({
5175
- chain_id: z$2.number(),
5176
- addresses: z$2.array(z$2.string().regex(/^0x[a-fA-F0-9]{40}$/))
5177
- })) });
5178
5034
  function createApp(parameters) {
5179
5035
  const { gatekeeper, chainRegistry } = parameters;
5180
5036
  const app = new Hono();
@@ -5183,12 +5039,12 @@ function createApp(parameters) {
5183
5039
  try {
5184
5040
  body = await c.req.json();
5185
5041
  } catch (err) {
5186
- const failure$5 = failure(err);
5187
- return c.json(failure$5.body, failure$5.statusCode);
5042
+ const failure$4 = failure(err);
5043
+ return c.json(failure$4.body, failure$4.statusCode);
5188
5044
  }
5189
5045
  if (body === null || typeof body !== "object") {
5190
- const failure$9 = failure(new BadRequestError("Request body must be a JSON object"));
5191
- return c.json(failure$9.body, failure$9.statusCode);
5046
+ const failure$3 = failure(new BadRequestError("Request body must be a JSON object"));
5047
+ return c.json(failure$3.body, failure$3.statusCode);
5192
5048
  }
5193
5049
  const { statusCode, body: payload } = await validateOffers(body, gatekeeper);
5194
5050
  return c.json(payload, statusCode);
@@ -5197,34 +5053,6 @@ function createApp(parameters) {
5197
5053
  const { statusCode, body } = await getConfigRules(c.req.query(), chainRegistry.list());
5198
5054
  return c.json(body, statusCode);
5199
5055
  });
5200
- app.post("/v1/callbacks", async (c) => {
5201
- let body;
5202
- try {
5203
- body = await c.req.json();
5204
- } catch (err) {
5205
- const failure$8 = failure(err);
5206
- return c.json(failure$8.body, failure$8.statusCode);
5207
- }
5208
- if (body === null || typeof body !== "object") {
5209
- const failure$6 = failure(new BadRequestError("Request body must be a JSON object"));
5210
- return c.json(failure$6.body, failure$6.statusCode);
5211
- }
5212
- try {
5213
- const request = CallbackTypesRequestSchema.parse(body);
5214
- const chainIds = new Set(chainRegistry.list().map((chain) => chain.id));
5215
- const unknown = request.callbacks.find((entry) => !chainIds.has(entry.chain_id));
5216
- if (unknown) throw new BadRequestError(`Unknown chain id ${unknown.chain_id}`);
5217
- const data = resolveCallbackTypes$2({
5218
- chains: chainRegistry.list(),
5219
- request
5220
- });
5221
- const response = success({ data });
5222
- return c.json(response.body, response.statusCode);
5223
- } catch (err) {
5224
- const failure$7 = failure(err);
5225
- return c.json(failure$7.body, failure$7.statusCode);
5226
- }
5227
- });
5228
5056
  return app;
5229
5057
  }
5230
5058
  /**
@@ -5320,1559 +5148,8 @@ function now() {
5320
5148
  }
5321
5149
 
5322
5150
  //#endregion
5323
- //#region src/indexer/collectors/CollectFunctions/collectConsumedEvents.ts
5324
- async function* collectConsumedEvents(parameters) {
5325
- let { db, collector, client, lastBlockNumber: blockNumber, epoch, options: { maxBatchSize = 1e3, blockWindow } = {} } = parameters;
5326
- const logger = getLogger();
5327
- let startBlock = blockNumber;
5328
- let reorgDetected = false;
5329
- const { blockNumber: latestBlockNumberChain } = await db.blocks.getChain(client.chain.id);
5330
- const stream = streamLogs({
5331
- client,
5332
- contractAddress: client.chain.custom.morpho.address,
5333
- event: consumedEvent,
5334
- blockNumberGte: blockNumber,
5335
- blockNumberLte: latestBlockNumberChain,
5336
- order: "asc",
5337
- options: {
5338
- maxBatchSize,
5339
- blockWindow
5340
- }
5341
- });
5342
- for await (const { logs, blockNumber: lastStreamBlockNumber } of stream) {
5343
- const parsedLogs = parseEventLogs({
5344
- abi: [consumedEvent],
5345
- logs
5346
- });
5347
- const events = [];
5348
- for (const log of parsedLogs) {
5349
- if (log.blockNumber === null || log.logIndex === null || log.transactionHash === null) {
5350
- logger.debug({
5351
- collector,
5352
- chainId: client.chain.id,
5353
- msg: "Skipping log because it is missing required fields"
5354
- });
5355
- continue;
5356
- }
5357
- events.push({
5358
- id: `${log.blockNumber.toString()}-${log.logIndex.toString()}-${client.chain.id}-${log.transactionHash}`,
5359
- chainId: client.chain.id,
5360
- maker: log.args.user,
5361
- group: log.args.group,
5362
- amount: log.args.amount,
5363
- blockNumber: Number(log.blockNumber)
5364
- });
5365
- }
5366
- await db.transaction(async (dbTx) => {
5367
- try {
5368
- await dbTx.consumed.create(events);
5369
- if (events.length > 0) logger.info({
5370
- msg: `Events indexed`,
5371
- collector,
5372
- count: events.length,
5373
- chain_id: client.chain.id,
5374
- block_range: [startBlock, lastStreamBlockNumber]
5375
- });
5376
- } catch (err) {
5377
- logger.error({
5378
- err,
5379
- msg: "Failed to process offer_consumed events"
5380
- });
5381
- }
5382
- blockNumber = lastStreamBlockNumber;
5383
- try {
5384
- await dbTx.blocks.advanceCollector({
5385
- collectorName: collector,
5386
- chainId: client.chain.id,
5387
- blockNumber,
5388
- epoch
5389
- });
5390
- } catch (_) {
5391
- try {
5392
- const ancestor = await dbTx.blocks.getCollector({
5393
- collectorName: collector,
5394
- chainId: client.chain.id
5395
- });
5396
- blockNumber = ancestor.blockNumber;
5397
- const deleted = await dbTx.consumed.delete({
5398
- chainId: client.chain.id,
5399
- blockNumberGte: blockNumber + 1
5400
- });
5401
- logger.info({
5402
- collector,
5403
- chain_id: client.chain.id,
5404
- msg: `Reorg detected, events deleted`,
5405
- count: deleted,
5406
- block_number: blockNumber
5407
- });
5408
- await dbTx.blocks.advanceCollector({
5409
- collectorName: collector,
5410
- chainId: client.chain.id,
5411
- blockNumber,
5412
- epoch: ancestor.epoch
5413
- });
5414
- reorgDetected = true;
5415
- } catch (err) {
5416
- const msg = "Failed to delete consumed events when handling reorg.";
5417
- logger.error({
5418
- collector,
5419
- chainId: client.chain.id,
5420
- msg,
5421
- err
5422
- });
5423
- throw new Error(msg);
5424
- }
5425
- }
5426
- });
5427
- if (reorgDetected) return;
5428
- yield blockNumber;
5429
- startBlock = blockNumber;
5430
- }
5431
- }
5432
-
5433
- //#endregion
5434
- //#region src/indexer/collectors/CollectFunctions/collectOffers.ts
5435
- async function* collectOffersV2(parameters) {
5436
- let { db, collector, client, lastBlockNumber: blockNumber, gatekeeper, options: { maxBatchSize = 1e3, blockWindow } = {} } = parameters;
5437
- const logger = getLogger();
5438
- let startBlock = blockNumber;
5439
- let reorgDetected = false;
5440
- if (client.chain.custom.morpho.address.toLowerCase() === zeroAddress) {
5441
- const msg = "Morpho V2 address is zero, signature verification will fail. Please set the Morpho V2 address in the chain configuration.";
5442
- logger.error({
5443
- msg,
5444
- chain_id: client.chain.id
5445
- });
5446
- throw new Error(msg);
5447
- }
5448
- const signatureDomain = {
5449
- chainId: client.chain.id,
5450
- verifyingContract: client.chain.custom.morpho.address
5451
- };
5452
- const { blockNumber: latestBlockNumberChain } = await db.blocks.getChain(client.chain.id);
5453
- const stream = streamLogs({
5454
- client,
5455
- contractAddress: client.chain.custom.mempool.address,
5456
- event: {
5457
- type: "event",
5458
- name: "Event",
5459
- inputs: [{
5460
- name: "data",
5461
- type: "bytes",
5462
- indexed: false,
5463
- internalType: "bytes"
5464
- }],
5465
- anonymous: false
5466
- },
5467
- blockNumberGte: blockNumber,
5468
- blockNumberLte: latestBlockNumberChain,
5469
- order: "asc",
5470
- options: {
5471
- maxBatchSize,
5472
- blockWindow
5473
- }
5474
- });
5475
- for await (const { logs, blockNumber: lastStreamBlockNumber } of stream) {
5476
- blockNumber = lastStreamBlockNumber;
5477
- const decodedTrees = [];
5478
- for (const log of logs) {
5479
- if (!log) continue;
5480
- const [payload] = decodeAbiParameters([{ type: "bytes" }], log.data);
5481
- try {
5482
- const { tree, signature, signer } = await decode(payload, signatureDomain);
5483
- const signerMismatch = tree.offers.find((offer) => offer.maker.toLowerCase() !== signer.toLowerCase());
5484
- if (signerMismatch) {
5485
- logger.debug({
5486
- msg: "Tree rejected: signer mismatch",
5487
- reason: "signer_mismatch",
5488
- signer,
5489
- maker: signerMismatch.maker,
5490
- chain_id: client.chain.id
5491
- });
5492
- continue;
5493
- }
5494
- decodedTrees.push({
5495
- tree,
5496
- signature,
5497
- signer,
5498
- blockNumber: Number(log.blockNumber)
5499
- });
5500
- } catch (err) {
5501
- const reason = err instanceof DecodeError && err.message.includes("signature") ? "invalid_signature" : "decode_failed";
5502
- logger.debug({
5503
- msg: "Tree decode failed",
5504
- reason,
5505
- chain_id: client.chain.id,
5506
- err: err instanceof Error ? err.message : String(err)
5507
- });
5508
- }
5509
- }
5510
- await db.transaction(async (dbTx) => {
5511
- const { epoch, blockNumber: latestBlockNumber } = await dbTx.blocks.getChain(client.chain.id);
5512
- const treesToInsert = [];
5513
- let totalValidOffers = 0;
5514
- const offersWithBlock = [];
5515
- for (const { tree, signature, blockNumber: treeBlockNumber } of decodedTrees) try {
5516
- const allowedResults = await gatekeeper.isAllowed(tree.offers);
5517
- const hasBlockWindowViolation = treeBlockNumber > latestBlockNumber;
5518
- if (!(allowedResults.issues.length === 0 && allowedResults.valid.length === tree.offers.length) || hasBlockWindowViolation) {
5519
- if (allowedResults.issues.length > 0) {
5520
- const hasMixedMaker = allowedResults.issues.some((i) => i.ruleName === "mixed_maker");
5521
- logger.debug({
5522
- msg: "Tree offers rejected by gatekeeper",
5523
- reason: hasMixedMaker ? "mixed_maker" : "gatekeeper_rejected",
5524
- chain_id: client.chain.id,
5525
- issues_count: allowedResults.issues.length
5526
- });
5527
- } else if (hasBlockWindowViolation) logger.debug({
5528
- msg: "Tree rejected: offers outside block window",
5529
- reason: "block_window",
5530
- chain_id: client.chain.id
5531
- });
5532
- continue;
5533
- }
5534
- treesToInsert.push({
5535
- tree,
5536
- signature
5537
- });
5538
- totalValidOffers += tree.offers.length;
5539
- offersWithBlock.push(...tree.offers.map((offer) => ({
5540
- offer,
5541
- blockNumber: treeBlockNumber
5542
- })));
5543
- } catch (err) {
5544
- const error = err instanceof Error ? err : new Error(String(err));
5545
- logger.error({
5546
- err: error,
5547
- msg: "Gatekeeper validation failed",
5548
- chain_id: client.chain.id
5549
- });
5550
- throw new Error("Gatekeeper validation failed", { cause: error });
5551
- }
5552
- const dependencies = buildOfferDependencies$1(offersWithBlock);
5553
- await dbTx.oracles.upsert(dependencies.oracles);
5554
- await dbTx.obligations.create(dependencies.obligations);
5555
- await dbTx.groups.create(dependencies.groups);
5556
- const insertedHashes = await dbTx.offers.create(dependencies.offerBatches);
5557
- if (treesToInsert.length > 0) await dbTx.trees.create(treesToInsert);
5558
- const insertedOffers = filterInsertedOffers({
5559
- offers: offersWithBlock,
5560
- hashes: insertedHashes
5561
- });
5562
- const { callbacks, positions, lots } = await decodeCallbacks({
5563
- chainId: client.chain.id,
5564
- gatekeeper,
5565
- offers: insertedOffers
5566
- });
5567
- if (positions.length > 0) await dbTx.positions.upsert(positions);
5568
- if (callbacks.length > 0) await dbTx.callbacks.upsert(callbacks);
5569
- if (lots.length > 0) await dbTx.lots.create(lots);
5570
- try {
5571
- await dbTx.blocks.advanceCollector({
5572
- collectorName: collector,
5573
- chainId: client.chain.id,
5574
- blockNumber,
5575
- epoch
5576
- });
5577
- if (totalValidOffers > 0) logger.info({
5578
- msg: `New offers`,
5579
- collector,
5580
- count: totalValidOffers,
5581
- trees_count: treesToInsert.length,
5582
- chain_id: client.chain.id,
5583
- block_range: [startBlock, lastStreamBlockNumber]
5584
- });
5585
- } catch (_) {
5586
- try {
5587
- const ancestor = await dbTx.blocks.getCollector({
5588
- collectorName: collector,
5589
- chainId: client.chain.id
5590
- });
5591
- blockNumber = ancestor.blockNumber;
5592
- const deleted = await dbTx.offers.delete({
5593
- blockNumberGte: blockNumber + 1,
5594
- chainId: client.chain.id
5595
- });
5596
- logger.info({
5597
- collector,
5598
- chain_id: client.chain.id,
5599
- msg: `Reorg detected, offers deleted`,
5600
- count: deleted,
5601
- block_number: blockNumber
5602
- });
5603
- await dbTx.blocks.advanceCollector({
5604
- collectorName: collector,
5605
- chainId: client.chain.id,
5606
- blockNumber,
5607
- epoch: ancestor.epoch
5608
- });
5609
- reorgDetected = true;
5610
- } catch (err) {
5611
- const msg = "Failed to delete offers when handling reorg.";
5612
- logger.error({
5613
- collector,
5614
- chainId: client.chain.id,
5615
- msg,
5616
- err
5617
- });
5618
- throw new Error(msg);
5619
- }
5620
- }
5621
- });
5622
- if (reorgDetected) return;
5623
- yield blockNumber;
5624
- startBlock = blockNumber;
5625
- }
5626
- }
5627
- async function decodeCallbacks(parameters) {
5628
- const { chainId, gatekeeper, offers } = parameters;
5629
- if (offers.length === 0) return {
5630
- callbacks: [],
5631
- positions: [],
5632
- lots: []
5633
- };
5634
- const addresses = offers.filter((entry) => entry.offer.callback.data !== "0x").map((entry) => entry.offer.callback.address);
5635
- if (addresses.length === 0) return {
5636
- callbacks: [],
5637
- positions: [],
5638
- lots: []
5639
- };
5640
- let response;
5641
- try {
5642
- response = await gatekeeper.getCallbackTypes({ callbacks: [{
5643
- chain_id: chainId,
5644
- addresses
5645
- }] });
5646
- } catch (err) {
5647
- const error = err instanceof Error ? err : new Error(String(err));
5648
- throw new Error("Failed to resolve callback types", { cause: error });
5649
- }
5650
- const entry = response.find((item) => item.chain_id === chainId);
5651
- const typeByAddress = /* @__PURE__ */ new Map();
5652
- if (entry) for (const [key, list] of Object.entries(entry)) {
5653
- if (key === "chain_id" || key === "not_supported") continue;
5654
- if (!Array.isArray(list)) continue;
5655
- for (const address of list) typeByAddress.set(address.toLowerCase(), key);
5656
- }
5657
- const callbacks = [];
5658
- const positions = [];
5659
- const lots = [];
5660
- for (const { offer, blockNumber: offerBlockNumber } of offers) {
5661
- if (offer.callback.data === "0x") continue;
5662
- const callbackType = typeByAddress.get(offer.callback.address.toLowerCase());
5663
- if (!callbackType) continue;
5664
- let decoded;
5665
- try {
5666
- decoded = decode$1(callbackType, offer.callback.data);
5667
- } catch (err) {
5668
- const error = err instanceof Error ? err : new Error(String(err));
5669
- throw new Error("Failed to decode callback data", { cause: error });
5670
- }
5671
- if (decoded.length === 0) continue;
5672
- const offerHash = hash(offer);
5673
- const callbackInputs = decoded.map((callback) => ({
5674
- chainId: offer.chainId,
5675
- contract: callback.contract,
5676
- user: offer.maker,
5677
- amount: callback.amount
5678
- }));
5679
- callbacks.push({
5680
- offerHash,
5681
- callbacks: callbackInputs
5682
- });
5683
- for (const callback of decoded) {
5684
- const contract = callback.contract;
5685
- const positionType = callbackType === Type$1.BuyVaultV1Callback ? Type.VAULT_V1 : Type.ERC20;
5686
- const asset = callbackType === Type$1.BuyVaultV1Callback ? void 0 : contract;
5687
- positions.push(from$10({
5688
- chainId: offer.chainId,
5689
- contract,
5690
- user: offer.maker,
5691
- type: positionType,
5692
- asset,
5693
- blockNumber: offerBlockNumber
5694
- }));
5695
- const isLoanPosition = offer.loanToken.toLowerCase() === asset?.toLowerCase();
5696
- lots.push({
5697
- positionChainId: offer.chainId,
5698
- positionContract: contract,
5699
- positionUser: offer.maker,
5700
- group: offer.group,
5701
- size: isLoanPosition ? offer.assets : callback.amount
5702
- });
5703
- }
5704
- }
5705
- return {
5706
- callbacks,
5707
- positions,
5708
- lots
5709
- };
5710
- }
5711
- function buildOfferDependencies$1(offers) {
5712
- const obligationsById = /* @__PURE__ */ new Map();
5713
- const oraclesByKey = /* @__PURE__ */ new Map();
5714
- const groupsByKey = /* @__PURE__ */ new Map();
5715
- const offersByBlock = /* @__PURE__ */ new Map();
5716
- for (const { offer, blockNumber } of offers) {
5717
- const list = offersByBlock.get(blockNumber) ?? [];
5718
- list.push(offer);
5719
- offersByBlock.set(blockNumber, list);
5720
- const obligationId$2 = obligationId(offer);
5721
- if (!obligationsById.get(obligationId$2)) obligationsById.set(obligationId$2, from$13({
5722
- chainId: offer.chainId,
5723
- loanToken: offer.loanToken,
5724
- maturity: offer.maturity,
5725
- collaterals: offer.collaterals
5726
- }));
5727
- for (const collateral of offer.collaterals) {
5728
- const oracleKey = `${offer.chainId}-${collateral.oracle}`.toLowerCase();
5729
- if (!oraclesByKey.has(oracleKey)) oraclesByKey.set(oracleKey, from$11({
5730
- chainId: offer.chainId,
5731
- address: collateral.oracle,
5732
- price: null,
5733
- blockNumber
5734
- }));
5735
- }
5736
- const groupKey = `${offer.chainId}-${offer.maker}-${offer.group}`.toLowerCase();
5737
- if (!groupsByKey.has(groupKey)) groupsByKey.set(groupKey, {
5738
- chainId: offer.chainId,
5739
- maker: offer.maker,
5740
- group: offer.group,
5741
- blockNumber
5742
- });
5743
- }
5744
- return {
5745
- obligations: Array.from(obligationsById.values()),
5746
- oracles: Array.from(oraclesByKey.values()),
5747
- groups: Array.from(groupsByKey.values()),
5748
- offerBatches: Array.from(offersByBlock.entries()).map(([blockNumber, items]) => ({
5749
- blockNumber,
5750
- offers: items
5751
- }))
5752
- };
5753
- }
5754
- function filterInsertedOffers(parameters) {
5755
- if (parameters.hashes.length === 0) return [];
5756
- const inserted = new Set(parameters.hashes.map((hash) => hash.toLowerCase()));
5757
- const seen = /* @__PURE__ */ new Set();
5758
- const filtered = [];
5759
- for (const entry of parameters.offers) {
5760
- const hash$2 = hash(entry.offer).toLowerCase();
5761
- if (!inserted.has(hash$2)) continue;
5762
- if (seen.has(hash$2)) continue;
5763
- seen.add(hash$2);
5764
- filtered.push(entry);
5765
- }
5766
- return filtered;
5767
- }
5768
-
5769
- //#endregion
5770
- //#region src/indexer/collectors/fetchers/fetchOraclePrices.ts
5771
- /**
5772
- * Fetches prices from multiple oracle contracts using multicall.
5773
- *
5774
- * - Executes `price()` on {@link Abi.Oracle} for each {@link Address}.
5775
- * - Requires a client supporting {@link PublicActions.multicall | multicall}.
5776
- *
5777
- * @param parameters - {@link fetchOraclePrices.Parameters} including the list of oracle
5778
- * {@link Address | addresses}, fetch options, and the multicall-enabled client.
5779
- * @returns {@link fetchOraclePrices.ReturnType} mapping {@link Address} to `bigint` price.
5780
- */
5781
- async function fetchOraclePrices(parameters) {
5782
- const { client, oracles, options } = parameters;
5783
- if (oracles.length === 0) return /* @__PURE__ */ new Map();
5784
- const batchSize = Math.max(1, options?.batchSize ?? 5e3);
5785
- const retryAttempts = Math.max(1, options?.retryAttempts ?? 3);
5786
- const retryDelayMs = Math.max(0, options?.retryDelayMs ?? 50);
5787
- const blockNumber = options?.blockNumber ? BigInt(options.blockNumber) : void 0;
5788
- const out = /* @__PURE__ */ new Map();
5789
- for (const oraclesBatch of batch$1(oracles, batchSize)) {
5790
- const priceCalls = [];
5791
- for (const oracle of oraclesBatch) priceCalls.push({
5792
- address: oracle,
5793
- abi: Oracle,
5794
- functionName: "price",
5795
- args: []
5796
- });
5797
- const prices = await batchMulticall({
5798
- client,
5799
- calls: priceCalls,
5800
- batchSize,
5801
- retryAttempts,
5802
- retryDelayMs,
5803
- blockNumber
5804
- });
5805
- for (let i = 0; i < oraclesBatch.length; i++) {
5806
- const oracle = oraclesBatch[i];
5807
- const price = prices[i];
5808
- out.set(oracle, price);
5809
- }
5810
- }
5811
- return out;
5812
- }
5813
-
5814
- //#endregion
5815
- //#region src/indexer/collectors/fetchers/snapshotERC20Positions.ts
5816
- /**
5817
- * Fetches ERC20 balances for positions at a given block number and returns the positions with the updated balances.
5818
- * @notice This method does not mutate positions and returns a new array.
5819
- *
5820
- * @param parameters - {@link snapshotERC20Positions.Parameters}
5821
- * @returns Positions - {@link snapshotERC20Positions.ReturnType}
5822
- */
5823
- async function snapshotERC20Positions(parameters) {
5824
- const { positions: oldPositions, blockNumber, options: { maxBatchSize = 1e3, retryAttempts = 5, retryDelayMs = 500 } = {} } = parameters;
5825
- if (oldPositions.length === 0) return [];
5826
- const calls = [];
5827
- for (const position of oldPositions) calls.push({
5828
- address: position.contract,
5829
- abi: erc20Abi,
5830
- functionName: "balanceOf",
5831
- args: [position.user]
5832
- });
5833
- const balances = await batchMulticall({
5834
- client: parameters.client,
5835
- calls,
5836
- blockNumber: BigInt(blockNumber),
5837
- batchSize: maxBatchSize,
5838
- retryAttempts,
5839
- retryDelayMs
5840
- });
5841
- const positions = [];
5842
- for (let i = 0; i < balances.length; i++) {
5843
- const oldPosition = oldPositions[i];
5844
- if (!oldPosition) continue;
5845
- positions.push({
5846
- ...oldPosition,
5847
- balance: balances[i],
5848
- blockNumber
5849
- });
5850
- }
5851
- return positions;
5852
- }
5853
-
5854
- //#endregion
5855
- //#region src/indexer/collectors/fetchers/snapshotVaultPositions.ts
5856
- /**
5857
- * Fetches vault shares for users at a given block number, converts them to assets and returns the positions with the updated balances.
5858
- * @notice This method does not mutate positions and returns a new array.
5859
- *
5860
- * @param parameters - {@link snapshotVaultPositions.Parameters}
5861
- * @returns Positions - {@link snapshotVaultPositions.ReturnType}
5862
- */
5863
- async function snapshotVaultPositions(parameters) {
5864
- const logger = getLogger();
5865
- const { client, positions: oldPositions, blockNumber, options: { maxBatchSize = 1e3, retryAttempts = 5, retryDelayMs = 500 } = {} } = parameters;
5866
- const calls = [];
5867
- const contracts = /* @__PURE__ */ new Map();
5868
- const positions = structuredClone(oldPositions);
5869
- for (const position of positions) {
5870
- calls.push({
5871
- address: position.contract,
5872
- abi: MetaMorpho,
5873
- functionName: "balanceOf",
5874
- args: [position.user],
5875
- convertToAssets: (shares) => {
5876
- const contract = contracts.get(position.contract.toLowerCase());
5877
- if (!contract) return;
5878
- if (contract.decimalsOffset === void 0 || contract.totalAssets === void 0 || contract.totalSupply === void 0) return;
5879
- try {
5880
- position.balance = convertToAssets({
5881
- shares,
5882
- totalAssets: contract.totalAssets,
5883
- totalSupply: contract.totalSupply,
5884
- decimalsOffset: contract.decimalsOffset
5885
- });
5886
- position.asset = contract.asset;
5887
- position.blockNumber = blockNumber;
5888
- } catch (err) {
5889
- if (err instanceof DenominatorIsZeroError) {
5890
- logger.error({
5891
- msg: "Failed to convert shares to assets",
5892
- chain_id: client.chain.id,
5893
- block_number: blockNumber,
5894
- position_contract: position.contract,
5895
- position_user: position.user,
5896
- shares,
5897
- err
5898
- });
5899
- return;
5900
- }
5901
- throw err;
5902
- }
5903
- }
5904
- });
5905
- if (contracts.has(position.contract.toLowerCase())) continue;
5906
- calls.push({
5907
- address: position.contract,
5908
- abi: MetaMorpho,
5909
- functionName: "DECIMALS_OFFSET",
5910
- args: []
5911
- }, {
5912
- address: position.contract,
5913
- abi: MetaMorpho,
5914
- functionName: "totalAssets",
5915
- args: []
5916
- }, {
5917
- address: position.contract,
5918
- abi: MetaMorpho,
5919
- functionName: "totalSupply",
5920
- args: []
5921
- }, {
5922
- address: position.contract,
5923
- abi: MetaMorpho,
5924
- functionName: "asset",
5925
- args: []
5926
- });
5927
- contracts.set(position.contract.toLowerCase(), {
5928
- decimalsOffset: void 0,
5929
- totalAssets: void 0,
5930
- totalSupply: void 0,
5931
- asset: void 0
5932
- });
5933
- }
5934
- const results = await batchMulticall({
5935
- client,
5936
- calls,
5937
- blockNumber: BigInt(blockNumber),
5938
- batchSize: maxBatchSize,
5939
- retryAttempts,
5940
- retryDelayMs
5941
- });
5942
- const convertToAssetsList = [];
5943
- for (let i = 0; i < results.length; i++) {
5944
- const call = calls[i];
5945
- const value = results[i];
5946
- const contract = contracts.get(call.address.toLowerCase());
5947
- if (!contract) continue;
5948
- switch (call.functionName) {
5949
- case "balanceOf":
5950
- convertToAssetsList.push(() => call.convertToAssets(value));
5951
- break;
5952
- case "DECIMALS_OFFSET":
5953
- contract.decimalsOffset = value;
5954
- break;
5955
- case "totalAssets":
5956
- contract.totalAssets = value;
5957
- break;
5958
- case "totalSupply":
5959
- contract.totalSupply = value;
5960
- break;
5961
- case "asset":
5962
- contract.asset = value.toLowerCase();
5963
- break;
5964
- }
5965
- }
5966
- for (const convertToAssets of convertToAssetsList) convertToAssets();
5967
- return positions;
5968
- }
5969
-
5970
- //#endregion
5971
- //#region src/indexer/collectors/CollectFunctions/collectPositions.ts
5972
- async function* collectPositions(parameters) {
5973
- let { db, collector, client, lastBlockNumber: blockNumber, epoch, options: { maxBatchSize = 1e3, retryAttempts = 5, retryDelayMs = 500, blockWindow } = {} } = parameters;
5974
- const logger = getLogger();
5975
- let startBlock = blockNumber;
5976
- let reorgDetected = false;
5977
- const TransferEvent = {
5978
- type: "event",
5979
- name: "Transfer",
5980
- inputs: [
5981
- {
5982
- name: "from",
5983
- type: "address",
5984
- indexed: true
5985
- },
5986
- {
5987
- name: "to",
5988
- type: "address",
5989
- indexed: true
5990
- },
5991
- {
5992
- name: "value",
5993
- type: "uint256",
5994
- indexed: false
5995
- }
5996
- ]
5997
- };
5998
- const { blockNumber: latestBlockNumberChain } = await db.blocks.getChain(client.chain.id);
5999
- const stream = streamLogs({
6000
- client,
6001
- event: TransferEvent,
6002
- blockNumberGte: blockNumber,
6003
- blockNumberLte: latestBlockNumberChain,
6004
- order: "asc",
6005
- options: {
6006
- maxBatchSize,
6007
- blockWindow
6008
- }
6009
- });
6010
- for await (const { logs, blockNumber: lastStreamBlockNumber } of stream) {
6011
- blockNumber = lastStreamBlockNumber;
6012
- const parsedLogs = parseEventLogs({
6013
- abi: [TransferEvent],
6014
- logs
6015
- });
6016
- const transfers = [];
6017
- for (const log of parsedLogs) {
6018
- if (log.blockNumber === null || log.logIndex === null || log.transactionHash === null) {
6019
- logger.debug({
6020
- collector,
6021
- chainId: client.chain.id,
6022
- msg: "Skipping log because it is missing required fields"
6023
- });
6024
- continue;
6025
- }
6026
- transfers.push(from$8({
6027
- id: `${client.chain.id}-${log.blockNumber.toString()}-${log.transactionHash}-${log.logIndex.toString()}`,
6028
- chainId: client.chain.id,
6029
- contract: log.address,
6030
- from: log.args.from,
6031
- to: log.args.to,
6032
- value: log.args.value,
6033
- blockNumber: Number(log.blockNumber)
6034
- }));
6035
- }
6036
- const { positions } = await db.positions.get({
6037
- chainId: client.chain.id,
6038
- filled: false
6039
- });
6040
- const newPositions = [];
6041
- try {
6042
- newPositions.push(...(await _snapshot({
6043
- positions,
6044
- blockNumber: latestBlockNumberChain,
6045
- client,
6046
- maxBatchSize,
6047
- retryAttempts,
6048
- retryDelayMs
6049
- })).map((p) => ({
6050
- ...p,
6051
- blockNumber: p.blockNumber + 1
6052
- })));
6053
- } catch (err) {
6054
- logger.error({
6055
- msg: "Failed to snapshot new empty positions",
6056
- collector,
6057
- chain_id: client.chain.id,
6058
- block_number: latestBlockNumberChain,
6059
- err
6060
- });
6061
- yield startBlock;
6062
- return;
6063
- }
6064
- try {
6065
- await db.transaction(async (dbTx) => {
6066
- const insertPositions = async () => {
6067
- if (newPositions.length === 0) return;
6068
- try {
6069
- const count = await dbTx.positions.upsert(newPositions);
6070
- logger.info({
6071
- msg: `New positions`,
6072
- collector,
6073
- count,
6074
- chain_id: client.chain.id,
6075
- block_number: latestBlockNumberChain
6076
- });
6077
- } catch (err) {
6078
- throw new InsertPositionsError(err);
6079
- }
6080
- };
6081
- const insertTransfers = async () => {
6082
- if (transfers.length === 0) return;
6083
- try {
6084
- const created = await dbTx.transfers.create(transfers);
6085
- logger.info({
6086
- msg: `New transfers`,
6087
- collector,
6088
- count: created,
6089
- chain_id: client.chain.id,
6090
- block_range: [startBlock, blockNumber]
6091
- });
6092
- } catch (err) {
6093
- throw new InsertTransfersError(err);
6094
- }
6095
- };
6096
- const saveBlockNumber = async () => {
6097
- try {
6098
- await dbTx.blocks.advanceCollector({
6099
- collectorName: collector,
6100
- chainId: client.chain.id,
6101
- blockNumber,
6102
- epoch
6103
- });
6104
- } catch (_) {
6105
- throw new ReorgError(blockNumber);
6106
- }
6107
- };
6108
- await insertPositions();
6109
- await insertTransfers();
6110
- await saveBlockNumber();
6111
- });
6112
- } catch (err) {
6113
- if (err instanceof ReorgError) {
6114
- logger.info({
6115
- msg: "Reorg detected, positions and transfers insertion aborted",
6116
- collector,
6117
- count: newPositions.length,
6118
- chain_id: client.chain.id,
6119
- block_number: blockNumber
6120
- });
6121
- reorgDetected = true;
6122
- }
6123
- if (err instanceof InsertPositionsError) {
6124
- logger.error({
6125
- msg: "Failed to insert positions",
6126
- collector,
6127
- count: newPositions.length,
6128
- chain_id: client.chain.id,
6129
- block_number: latestBlockNumberChain,
6130
- err
6131
- });
6132
- throw err.cause;
6133
- }
6134
- if (err instanceof InsertTransfersError) {
6135
- logger.error({
6136
- msg: "Failed to insert transfers",
6137
- collector,
6138
- count: transfers.length,
6139
- chain_id: client.chain.id,
6140
- block_number: blockNumber,
6141
- err
6142
- });
6143
- throw err.cause;
6144
- }
6145
- }
6146
- if (!reorgDetected) {
6147
- startBlock = blockNumber;
6148
- if (newPositions.length === 0 && transfers.length === 0 && lastStreamBlockNumber !== latestBlockNumberChain) continue;
6149
- yield blockNumber;
6150
- continue;
6151
- }
6152
- await db.transaction(async (dbTx) => {
6153
- try {
6154
- const ancestor = await dbTx.blocks.getCollector({
6155
- collectorName: collector,
6156
- chainId: client.chain.id
6157
- });
6158
- blockNumber = ancestor.blockNumber;
6159
- const emptied = await dbTx.positions.setEmptyAfter({
6160
- chainId: client.chain.id,
6161
- blockNumber: blockNumber + 1
6162
- });
6163
- logger.info({
6164
- msg: "Reorg detected, positions set to empty",
6165
- collector,
6166
- count: emptied,
6167
- chain_id: client.chain.id,
6168
- block_number_gte: blockNumber + 1
6169
- });
6170
- await dbTx.blocks.advanceCollector({
6171
- collectorName: collector,
6172
- chainId: client.chain.id,
6173
- blockNumber,
6174
- epoch: ancestor.epoch
6175
- });
6176
- } catch (err) {
6177
- const msg = "Failed to revert to ancestor block when handling reorg.";
6178
- logger.error({
6179
- collector,
6180
- chainId: client.chain.id,
6181
- msg,
6182
- err
6183
- });
6184
- throw new Error(msg);
6185
- }
6186
- });
6187
- return;
6188
- }
6189
- }
6190
- /**
6191
- * @internal
6192
- *
6193
- * Snapshots positions and returns the new positions.
6194
- * @param parameters - {@link _snapshot.Parameters}
6195
- * @returns The new positions. {@link _snapshot.ReturnType}
6196
- */
6197
- async function _snapshot(parameters) {
6198
- const { positions, blockNumber, client, maxBatchSize, retryAttempts, retryDelayMs } = parameters;
6199
- const vaultV1Positions = [];
6200
- const erc20Positions = [];
6201
- for (const position of positions) switch (position.type) {
6202
- case Type.VAULT_V1:
6203
- vaultV1Positions.push(position);
6204
- break;
6205
- case Type.ERC20:
6206
- erc20Positions.push(position);
6207
- break;
6208
- default: throw new Error("Invalid position type");
6209
- }
6210
- const promises = [snapshotVaultPositions({
6211
- client,
6212
- positions: vaultV1Positions,
6213
- blockNumber,
6214
- options: {
6215
- maxBatchSize,
6216
- retryAttempts,
6217
- retryDelayMs
6218
- }
6219
- }), snapshotERC20Positions({
6220
- client,
6221
- positions: erc20Positions,
6222
- blockNumber,
6223
- options: {
6224
- maxBatchSize,
6225
- retryAttempts,
6226
- retryDelayMs
6227
- }
6228
- })];
6229
- return (await Promise.all(promises)).flat();
6230
- }
6231
- var InsertPositionsError = class extends BaseError {
6232
- name = "InsertPositionsError";
6233
- constructor(err) {
6234
- super("Failed to insert positions", { cause: err });
6235
- }
6236
- };
6237
- var InsertTransfersError = class extends BaseError {
6238
- name = "InsertTransfersError";
6239
- constructor(err) {
6240
- super("Failed to insert transfers", { cause: err });
6241
- }
6242
- };
6243
-
6244
- //#endregion
6245
- //#region src/indexer/collectors/CollectFunctions/collectPrices.ts
6246
- /**
6247
- * Collects oracle prices from on-chain oracles and persists them.
6248
- *
6249
- * - Reads oracle definitions as {@link Oracle.Oracle}.
6250
- * - Uses chain metadata from {@link Chain.Chain}.
6251
- *
6252
- * @param parameters - {@link collectPrices.Parameters} (extends {@link Collector.CollectParameters})
6253
- * with a client supporting {@link PublicActions.multicall | multicall}.
6254
- * @yields Latest processed block number after each successful update.
6255
- */
6256
- async function* collectPrices(parameters) {
6257
- const { db, collector, client, options: { maxBatchSize = 5e3, retryAttempts = 5, retryDelayMs = 500 } = {} } = parameters;
6258
- const logger = getLogger();
6259
- let blockNumber = parameters.lastBlockNumber;
6260
- const [oracles, { blockNumber: latestBlockNumberChain, epoch }] = await Promise.all([db.oracles.get({ chainId: client.chain.id }), db.blocks.getChain(client.chain.id)]);
6261
- const updatedOracles = [];
6262
- try {
6263
- const pricesMap = await fetchOraclePrices({
6264
- client,
6265
- oracles: oracles.map((oracle) => oracle.address),
6266
- options: {
6267
- batchSize: maxBatchSize,
6268
- blockNumber: latestBlockNumberChain,
6269
- retryAttempts,
6270
- retryDelayMs
6271
- }
6272
- });
6273
- for (const oracle of oracles) {
6274
- const price = pricesMap.get(oracle.address);
6275
- if (price !== void 0) updatedOracles.push({
6276
- chainId: client.chain.id,
6277
- address: oracle.address,
6278
- price,
6279
- blockNumber: latestBlockNumberChain
6280
- });
6281
- }
6282
- } catch (err) {
6283
- logger.error({
6284
- msg: "Failed to fetch oracle prices",
6285
- collector,
6286
- chain_id: client.chain.id,
6287
- block_number: latestBlockNumberChain,
6288
- err
6289
- });
6290
- yield blockNumber;
6291
- return;
6292
- }
6293
- let reorgDetected = false;
6294
- try {
6295
- await db.transaction(async (dbTx) => {
6296
- if (updatedOracles.length > 0) {
6297
- await dbTx.oracles.upsert(updatedOracles);
6298
- logger.info({
6299
- msg: "Oracle prices updated",
6300
- collector,
6301
- count: updatedOracles.length,
6302
- chain_id: client.chain.id,
6303
- block_number: latestBlockNumberChain
6304
- });
6305
- }
6306
- try {
6307
- await dbTx.blocks.advanceCollector({
6308
- collectorName: collector,
6309
- chainId: client.chain.id,
6310
- blockNumber: latestBlockNumberChain,
6311
- epoch
6312
- });
6313
- } catch (_) {
6314
- throw new ReorgError(latestBlockNumberChain);
6315
- }
6316
- blockNumber = latestBlockNumberChain;
6317
- });
6318
- } catch (err) {
6319
- if (err instanceof ReorgError) {
6320
- logger.info({
6321
- msg: "Reorg detected, prices update aborted",
6322
- collector,
6323
- count: updatedOracles.length,
6324
- chain_id: client.chain.id,
6325
- block_number: latestBlockNumberChain
6326
- });
6327
- reorgDetected = true;
6328
- } else throw new Error("Failed to collect oracle prices", { cause: err });
6329
- }
6330
- if (!reorgDetected) {
6331
- yield blockNumber;
6332
- return;
6333
- }
6334
- await db.transaction(async (dbTx) => {
6335
- try {
6336
- const ancestor = await dbTx.blocks.getCollector({
6337
- collectorName: collector,
6338
- chainId: client.chain.id
6339
- });
6340
- blockNumber = ancestor.blockNumber;
6341
- await dbTx.blocks.advanceCollector({
6342
- collectorName: collector,
6343
- chainId: client.chain.id,
6344
- blockNumber,
6345
- epoch: ancestor.epoch
6346
- });
6347
- } catch (err) {
6348
- const msg = "Failed to revert to ancestor block when handling reorg.";
6349
- logger.error({
6350
- collector,
6351
- chainId: client.chain.id,
6352
- msg,
6353
- err
6354
- });
6355
- throw new Error(msg);
6356
- }
6357
- });
6358
- yield blockNumber;
6359
- }
6360
-
6361
- //#endregion
6362
- //#region src/indexer/collectors/CollectorBuilder.ts
6363
- function createBuilder(parameters) {
6364
- const { client, db, gatekeeper, options: { maxBlockNumber, blockWindow, interval } = {} } = parameters;
6365
- const createCollector = (name, collect) => create$16({
6366
- name,
6367
- collect,
6368
- client,
6369
- db,
6370
- options: {
6371
- maxBlockNumber,
6372
- interval
6373
- }
6374
- });
6375
- return {
6376
- buildOffersCollector: ({ options: { maxBatchSize = 1e3 } = {} }) => {
6377
- return createCollector("offers", (p) => collectOffersV2({
6378
- ...p,
6379
- gatekeeper,
6380
- collector: "offers",
6381
- client,
6382
- options: {
6383
- maxBatchSize,
6384
- blockWindow
6385
- }
6386
- }));
6387
- },
6388
- buildConsumedEventsCollector: ({ options: { maxBatchSize = 1e3 } = {} } = {}) => {
6389
- return createCollector("consumed_events", (p) => collectConsumedEvents({
6390
- ...p,
6391
- collector: "consumed_events",
6392
- options: {
6393
- maxBatchSize,
6394
- blockWindow
6395
- }
6396
- }));
6397
- },
6398
- buildPricesCollector: ({ options: { maxBatchSize = 5e3, retryAttempts, retryDelayMs } = {} } = {}) => {
6399
- return createCollector("prices", (p) => collectPrices({
6400
- ...p,
6401
- collector: "prices",
6402
- options: {
6403
- maxBatchSize,
6404
- retryAttempts,
6405
- retryDelayMs
6406
- }
6407
- }));
6408
- },
6409
- buildPositionsCollector: ({ options: { maxBatchSize = 1e3, retryAttempts, retryDelayMs } = {} } = {}) => {
6410
- return createCollector("positions", (p) => collectPositions({
6411
- ...p,
6412
- collector: "positions",
6413
- options: {
6414
- maxBatchSize,
6415
- retryAttempts,
6416
- retryDelayMs,
6417
- blockWindow
6418
- }
6419
- }));
6420
- }
6421
- };
6422
- }
6423
-
6424
- //#endregion
6425
- //#region src/indexer/collectors/Collectors.ts
6426
- const from$2 = (parameters) => {
6427
- const { client, db, gatekeeper, maxBatchSize, maxBlockNumber, blockWindow, interval, retryAttempts, retryDelayMs } = parameters;
6428
- const collectorBuilder = createBuilder({
6429
- client,
6430
- db,
6431
- gatekeeper,
6432
- options: {
6433
- maxBlockNumber,
6434
- blockWindow,
6435
- interval
6436
- }
6437
- });
6438
- return {
6439
- offersCollector: collectorBuilder.buildOffersCollector({ options: { maxBatchSize } }),
6440
- consumedEventsCollector: collectorBuilder.buildConsumedEventsCollector({ options: { maxBatchSize } }),
6441
- pricesCollector: collectorBuilder.buildPricesCollector({ options: {
6442
- maxBatchSize,
6443
- retryAttempts,
6444
- retryDelayMs
6445
- } }),
6446
- positionsCollector: collectorBuilder.buildPositionsCollector({ options: {
6447
- maxBatchSize,
6448
- retryAttempts,
6449
- retryDelayMs
6450
- } })
6451
- };
6452
- };
6453
-
6454
- //#endregion
6455
- //#region src/indexer/Indexer.ts
6456
- function from$1(config) {
6457
- const { client, gatekeeper, db, interval = 1e4, maxBatchSize = 1e3, maxBlockNumber, blockWindow, retryAttempts, retryDelayMs } = config;
6458
- const { offersCollector, consumedEventsCollector, positionsCollector, pricesCollector } = from$2({
6459
- client,
6460
- db,
6461
- gatekeeper,
6462
- maxBatchSize,
6463
- maxBlockNumber,
6464
- blockWindow,
6465
- interval,
6466
- retryAttempts,
6467
- retryDelayMs
6468
- });
6469
- return create$18({
6470
- client,
6471
- collectors: [
6472
- offersCollector,
6473
- consumedEventsCollector,
6474
- positionsCollector,
6475
- pricesCollector
6476
- ]
6477
- });
6478
- }
6479
- function create$18(params) {
6480
- const { collectors, client } = params;
6481
- const indexerId = `${client.chain.id.toString()}.indexer`;
6482
- const tracer = getTracer(`router.${indexerId}`);
6483
- const iterators = collectors.map((collector) => collector.collect());
6484
- const next = async () => {
6485
- await startActiveSpan(tracer, `${indexerId}.next`, async () => {
6486
- await Promise.all(iterators.map((iterator) => iterator.next()));
6487
- });
6488
- };
6489
- const _return = async () => {
6490
- await Promise.all(iterators.map(async (iterator) => iterator.return()));
6491
- };
6492
- return {
6493
- start: () => {
6494
- const stops = collectors.map((collector) => start(collector));
6495
- return () => {
6496
- stops.forEach((stop) => {
6497
- stop();
6498
- });
6499
- };
6500
- },
6501
- next,
6502
- return: _return
6503
- };
6504
- }
6505
-
6506
- //#endregion
6507
- //#region src/indexer/collectors/Admin.ts
6508
- function create$17(parameters) {
6509
- const collector = "admin";
6510
- const { client, db, options: { maxBatchSize = 25, maxBlockNumber } = {} } = parameters;
6511
- const maxBlockNumberBI = maxBlockNumber !== void 0 ? BigInt(maxBlockNumber) : void 0;
6512
- let finalizedBlock = null;
6513
- let unfinalizedBlocks = [];
6514
- let initialized = false;
6515
- const logger = getLogger();
6516
- let isMaxBlockNumberReached = false;
6517
- let tick = 0;
6518
- return { syncBlock: async () => {
6519
- if (!initialized) {
6520
- await Promise.all(names.map((collectorName) => db.blocks.init({
6521
- chainId: client.chain.id,
6522
- collectorName
6523
- })));
6524
- initialized = true;
6525
- }
6526
- if (isMaxBlockNumberReached) return true;
6527
- const head = await client.getBlock({
6528
- blockTag: "latest",
6529
- includeTransactions: false
6530
- });
6531
- await db.transaction(async (dbTx) => {
6532
- const { epoch, blockNumber: latestSavedBlockNumber } = await dbTx.blocks.getChain(client.chain.id);
6533
- if (maxBlockNumberBI !== void 0 && head.number >= maxBlockNumberBI) {
6534
- logger.info({
6535
- msg: `Head is greater than max block number`,
6536
- collector,
6537
- chainId: client.chain.id,
6538
- block_number: head.number,
6539
- max_block_number: maxBlockNumber
6540
- });
6541
- await dbTx.blocks.handleReorg({
6542
- chainId: client.chain.id,
6543
- blockNumber: maxBlockNumber,
6544
- epoch: epoch + 1n
6545
- });
6546
- isMaxBlockNumberReached = true;
6547
- return isMaxBlockNumberReached;
6548
- }
6549
- finalizedBlock = await fetchFinalizedBlock({
6550
- tick,
6551
- client,
6552
- logger,
6553
- collector,
6554
- unfinalizedBlocks,
6555
- previousFinalizedBlock: finalizedBlock
6556
- });
6557
- tick++;
6558
- let { block: returnedBlock, didReorgHappened, unfinalizedBlocks: newUnfinalizedBlocks } = await reconcile({
6559
- client,
6560
- block: head,
6561
- unfinalizedBlocks,
6562
- finalizedBlock,
6563
- logger,
6564
- collector,
6565
- maxBatchSize
6566
- });
6567
- unfinalizedBlocks = newUnfinalizedBlocks;
6568
- const blockNumber = Number(returnedBlock.number);
6569
- didReorgHappened = didReorgHappened || blockNumber < latestSavedBlockNumber;
6570
- if (didReorgHappened) await dbTx.blocks.handleReorg({
6571
- chainId: client.chain.id,
6572
- blockNumber,
6573
- epoch: epoch + 1n
6574
- });
6575
- else await dbTx.blocks.advanceChain({
6576
- chainId: client.chain.id,
6577
- blockNumber,
6578
- epoch
6579
- });
6580
- });
6581
- return isMaxBlockNumberReached;
6582
- } };
6583
- }
6584
- const commonAncestor = (block, unfinalizedBlocks) => {
6585
- const parent = unfinalizedBlocks.find((b) => b.hash === block.parentHash);
6586
- if (parent) return parent;
6587
- return null;
6588
- };
6589
- const fetchFinalizedBlock = async (parameters) => {
6590
- let { tick, client, logger, collector, unfinalizedBlocks, previousFinalizedBlock } = parameters;
6591
- let finalizedBlock = previousFinalizedBlock;
6592
- if (tick % 20 === 0 || previousFinalizedBlock === null) {
6593
- finalizedBlock = await client.getBlock({
6594
- blockTag: "finalized",
6595
- includeTransactions: false
6596
- });
6597
- if (finalizedBlock === null || finalizedBlock.number === null) {
6598
- const msg = "Failed to get finalized block";
6599
- logger.fatal({
6600
- collector,
6601
- chainId: client.chain.id,
6602
- msg
6603
- });
6604
- throw new Error(msg);
6605
- }
6606
- unfinalizedBlocks = unfinalizedBlocks.filter((b) => b.number >= finalizedBlock.number);
6607
- }
6608
- if (finalizedBlock.number === null || finalizedBlock.hash === null || finalizedBlock.parentHash === null) {
6609
- const msg = "Failed to get finalized block";
6610
- logger.fatal({
6611
- collector,
6612
- chainId: client.chain.id,
6613
- msg
6614
- });
6615
- throw new Error(msg);
6616
- }
6617
- return {
6618
- hash: finalizedBlock.hash,
6619
- number: finalizedBlock.number,
6620
- parentHash: finalizedBlock.parentHash
6621
- };
6622
- };
6623
- const reconcile = async (parameters) => {
6624
- let { client, block, unfinalizedBlocks, finalizedBlock, logger, collector, maxBatchSize } = parameters;
6625
- const chain = client.chain;
6626
- if (block.hash === null || block.number === null || block.parentHash === null) throw new Error("Failed to get block");
6627
- const latestBlock = unfinalizedBlocks[unfinalizedBlocks.length - 1];
6628
- if (latestBlock === void 0) {
6629
- const newBlock = {
6630
- hash: block.hash,
6631
- number: block.number,
6632
- parentHash: block.parentHash
6633
- };
6634
- unfinalizedBlocks.push(newBlock);
6635
- return {
6636
- block: newBlock,
6637
- didReorgHappened: false,
6638
- unfinalizedBlocks
6639
- };
6640
- }
6641
- if (latestBlock.hash === block.hash) return {
6642
- block: latestBlock,
6643
- didReorgHappened: false,
6644
- unfinalizedBlocks
6645
- };
6646
- if (latestBlock.number >= block.number) {
6647
- const ancestor = commonAncestor(block, unfinalizedBlocks) || finalizedBlock;
6648
- logger.info({
6649
- msg: `Reorg detected, latestBlock.number >= block.number`,
6650
- collector,
6651
- chain_id: chain.id,
6652
- ancestor: ancestor.number,
6653
- latest_block_number: latestBlock.number,
6654
- block_number: block.number
6655
- });
6656
- unfinalizedBlocks = unfinalizedBlocks.filter((b) => b.number <= ancestor.number);
6657
- return {
6658
- block: ancestor,
6659
- didReorgHappened: true,
6660
- unfinalizedBlocks
6661
- };
6662
- }
6663
- if (latestBlock.number + 1n < block.number) {
6664
- logger.debug({
6665
- collector,
6666
- chain_id: chain.id,
6667
- block_range: [latestBlock.number, block.number],
6668
- msg: `Missing blocks`
6669
- });
6670
- const missingBlockNumbers = (() => {
6671
- const missingBlockNumbers = [];
6672
- let start = latestBlock.number + 1n;
6673
- const threshold = latestBlock.number + BigInt(maxBatchSize) > block.number ? block.number : latestBlock.number + BigInt(maxBatchSize);
6674
- while (start < threshold) {
6675
- missingBlockNumbers.push(start);
6676
- start = start + 1n;
6677
- }
6678
- return missingBlockNumbers;
6679
- })();
6680
- const missingBlocks = await Promise.all(missingBlockNumbers.map((blockNumber) => retry(async () => await client.getBlock({
6681
- blockNumber,
6682
- includeTransactions: false
6683
- }))));
6684
- for (const missingBlock of missingBlocks) {
6685
- const { block: returnedBlock, didReorgHappened, unfinalizedBlocks: newUnfinalizedBlocks } = await reconcile({
6686
- client,
6687
- block: missingBlock,
6688
- unfinalizedBlocks,
6689
- finalizedBlock,
6690
- logger,
6691
- collector,
6692
- maxBatchSize
6693
- });
6694
- if (returnedBlock.number !== missingBlock.number) return {
6695
- block: returnedBlock,
6696
- didReorgHappened,
6697
- unfinalizedBlocks: newUnfinalizedBlocks
6698
- };
6699
- }
6700
- return reconcile({
6701
- client,
6702
- block,
6703
- unfinalizedBlocks,
6704
- finalizedBlock,
6705
- logger,
6706
- collector,
6707
- maxBatchSize
6708
- });
6709
- }
6710
- if (block.parentHash !== latestBlock.hash) {
6711
- const ancestor = commonAncestor(block, unfinalizedBlocks) || finalizedBlock;
6712
- logger.info({
6713
- msg: `Reorg detected, block parent hash !== latest block hash`,
6714
- collector,
6715
- chain_id: chain.id,
6716
- ancestor: ancestor.number,
6717
- latest_block_number: latestBlock.number,
6718
- block_number: block.number
6719
- });
6720
- unfinalizedBlocks = unfinalizedBlocks.filter((b) => b.number <= ancestor.number);
6721
- return {
6722
- block: ancestor,
6723
- didReorgHappened: true,
6724
- unfinalizedBlocks
6725
- };
6726
- }
6727
- const newBlock = {
6728
- hash: block.hash,
6729
- number: block.number,
6730
- parentHash: block.parentHash
6731
- };
6732
- unfinalizedBlocks.push(newBlock);
6733
- return {
6734
- block: newBlock,
6735
- didReorgHappened: false,
6736
- unfinalizedBlocks
6737
- };
6738
- };
6739
-
6740
- //#endregion
6741
- //#region src/indexer/collectors/Collector.ts
6742
- const names = [
6743
- "offers",
6744
- "consumed_events",
6745
- "positions",
6746
- "prices"
6747
- ];
6748
- function create$16({ name, collect, client, db, options }) {
6749
- const admin = create$17({
6750
- client,
6751
- db,
6752
- options
6753
- });
6754
- return {
6755
- name,
6756
- chain: client.chain,
6757
- client,
6758
- db,
6759
- interval: options.interval ?? 1e4,
6760
- collect: async function* () {
6761
- const collector = name;
6762
- const chain = client.chain;
6763
- const logger = getLogger();
6764
- const collectorId = `${client.chain.id.toString()}.collector.${collector}`;
6765
- const tracer = getTracer(`router.${collectorId}`);
6766
- logger.info({
6767
- msg: `Collector started`,
6768
- collector,
6769
- chain_id: chain.id
6770
- });
6771
- let iterator = null;
6772
- let lastBlockNumber;
6773
- while (true) try {
6774
- if (iterator === null) iterator = await startActiveSpan(tracer, `${collectorId}.init`, async () => {
6775
- const { collector: collectorBlock } = await db.blocks.init({
6776
- collectorName: name,
6777
- chainId: chain.id
6778
- });
6779
- lastBlockNumber = collectorBlock.blockNumber;
6780
- return collect({
6781
- client,
6782
- collector: name,
6783
- epoch: collectorBlock.epoch,
6784
- lastBlockNumber,
6785
- db
6786
- });
6787
- });
6788
- if (await startActiveSpan(tracer, `${collectorId}.syncBlock`, async (span) => {
6789
- const isMaxBlockNumberReached = await admin.syncBlock();
6790
- span.setAttribute("collector.is_max_block_reached", isMaxBlockNumberReached);
6791
- return isMaxBlockNumberReached;
6792
- }) && options.maxBlockNumber !== void 0 && lastBlockNumber !== void 0 && options.maxBlockNumber === lastBlockNumber) return;
6793
- const { blockNumber, done } = await startActiveSpan(tracer, `${collectorId}.next`, async () => {
6794
- if (iterator === null) throw new Error("Iterator is not initialized");
6795
- const { value: blockNumber, done } = await iterator.next();
6796
- return {
6797
- blockNumber,
6798
- done
6799
- };
6800
- });
6801
- if (done) iterator = null;
6802
- else {
6803
- lastBlockNumber = blockNumber;
6804
- yield blockNumber;
6805
- }
6806
- } catch (err) {
6807
- const isError = err instanceof Error;
6808
- logger.error({
6809
- msg: "Collector error",
6810
- collector,
6811
- chain_id: chain.id,
6812
- error: isError ? err.message : String(err),
6813
- stack: isError ? err.stack : void 0
6814
- });
6815
- }
6816
- }
6817
- };
6818
- }
6819
- /** Start a collector with its own polling cadence based on chain head lag.
6820
- * @param collector - The collector to start.
6821
- * @returns A function to stop the collector.
6822
- */
6823
- function start(collector) {
6824
- let stopped = false;
6825
- const it = collector.collect();
6826
- const logger = getLogger();
6827
- const collectorId = `${collector.chain.id.toString()}.collector.${collector.name}`;
6828
- const tracer = getTracer(`router.${collectorId}`);
6829
- const blocks = collector.db.blocks;
6830
- let initialized = false;
6831
- (async () => {
6832
- while (!stopped) try {
6833
- await startActiveSpan(tracer, `${collectorId}.poll`, async () => {
6834
- if (!initialized) {
6835
- await blocks.init({
6836
- chainId: collector.chain.id,
6837
- collectorName: collector.name
6838
- });
6839
- initialized = true;
6840
- }
6841
- const [block, { blockNumber: collectorBlockNumber }] = await Promise.all([collector.client.getBlock({
6842
- blockTag: "latest",
6843
- includeTransactions: false
6844
- }), blocks.getCollector({
6845
- collectorName: collector.name,
6846
- chainId: collector.chain.id
6847
- })]);
6848
- const delay = Number(block.number) > collectorBlockNumber + 10 ? 250 : collector.interval;
6849
- const waitSpan = tracer.startSpan(`${collectorId}.wait`);
6850
- try {
6851
- await wait(delay);
6852
- } finally {
6853
- waitSpan.end();
6854
- }
6855
- await it.next();
6856
- });
6857
- } catch (err) {
6858
- const error = err instanceof Error ? err : new Error(String(err));
6859
- logger.error({
6860
- msg: "Collector polling error",
6861
- collector: collector.name,
6862
- chain_id: collector.chain.id,
6863
- err: error
6864
- });
6865
- }
6866
- await it.return();
6867
- })();
6868
- return () => {
6869
- stopped = true;
6870
- };
6871
- }
6872
-
6873
- //#endregion
6874
- //#region src/database/drizzle/VERSION.ts
6875
- const VERSION = "router_v1.6";
5151
+ //#region src/database/drizzle/VERSION.ts
5152
+ const VERSION = "router_v1.6";
6876
5153
 
6877
5154
  //#endregion
6878
5155
  //#region src/database/drizzle/schema.ts
@@ -7274,6 +5551,1654 @@ const merklePaths = s.table(EnumTableName.MERKLE_PATHS, {
7274
5551
  createdAt: timestamp("created_at").defaultNow().notNull()
7275
5552
  }, (table) => [index("merkle_paths_tree_root_idx").on(table.treeRoot)]);
7276
5553
 
5554
+ //#endregion
5555
+ //#region src/indexer/collectors/CollectFunctions/collectConsumedEvents.ts
5556
+ const buildGroupKey = (parameters) => {
5557
+ return `${parameters.chainId}-${parameters.maker.toLowerCase()}-${parameters.group.toLowerCase()}`;
5558
+ };
5559
+ async function* collectConsumedEvents(parameters) {
5560
+ let { db, collector, client, lastBlockNumber: blockNumber, epoch, options: { maxBatchSize = 1e3, blockWindow } = {} } = parameters;
5561
+ const logger = getLogger();
5562
+ let startBlock = blockNumber;
5563
+ let reorgDetected = false;
5564
+ const { blockNumber: latestBlockNumberChain } = await db.blocks.getChain(client.chain.id);
5565
+ const stream = streamLogs({
5566
+ client,
5567
+ contractAddress: client.chain.custom.morpho.address,
5568
+ blockNumberGte: blockNumber,
5569
+ blockNumberLte: latestBlockNumberChain,
5570
+ order: "asc",
5571
+ options: {
5572
+ maxBatchSize,
5573
+ blockWindow
5574
+ }
5575
+ });
5576
+ for await (const { logs, blockNumber: lastStreamBlockNumber } of stream) {
5577
+ const parsedLogs = parseEventLogs({
5578
+ abi: [consumedEvent, takeEvent],
5579
+ logs,
5580
+ strict: false
5581
+ });
5582
+ const normalizedLogs = [];
5583
+ const groups$3 = /* @__PURE__ */ new Map();
5584
+ const eventIds = /* @__PURE__ */ new Set();
5585
+ const recordLog = (log) => {
5586
+ normalizedLogs.push(log);
5587
+ eventIds.add(log.id);
5588
+ const groupKey = buildGroupKey({
5589
+ chainId: log.chainId,
5590
+ maker: log.maker,
5591
+ group: log.group
5592
+ });
5593
+ if (!groups$3.has(groupKey)) groups$3.set(groupKey, {
5594
+ chainId: log.chainId,
5595
+ maker: log.maker.toLowerCase(),
5596
+ group: log.group.toLowerCase()
5597
+ });
5598
+ };
5599
+ for (const rawLog of parsedLogs) {
5600
+ if (rawLog.blockNumber === null || rawLog.logIndex === null || rawLog.transactionHash === null) {
5601
+ logger.debug({
5602
+ collector,
5603
+ chainId: client.chain.id,
5604
+ msg: "Skipping log because it is missing required fields"
5605
+ });
5606
+ continue;
5607
+ }
5608
+ if (rawLog.eventName === consumedEvent.name) {
5609
+ const consumeArgs = rawLog.args;
5610
+ if (consumeArgs.user === void 0 || consumeArgs.group === void 0 || consumeArgs.amount === void 0) {
5611
+ logger.debug({
5612
+ collector,
5613
+ chainId: client.chain.id,
5614
+ msg: "Skipping Consume log because it is missing required args"
5615
+ });
5616
+ continue;
5617
+ }
5618
+ recordLog({
5619
+ kind: "consume",
5620
+ id: `${rawLog.blockNumber.toString()}-${rawLog.logIndex.toString()}-${client.chain.id}-${rawLog.transactionHash}`,
5621
+ chainId: client.chain.id,
5622
+ maker: consumeArgs.user,
5623
+ group: consumeArgs.group,
5624
+ amount: consumeArgs.amount,
5625
+ blockNumber: Number(rawLog.blockNumber)
5626
+ });
5627
+ continue;
5628
+ }
5629
+ if (rawLog.eventName === takeEvent.name) {
5630
+ const takeArgs = rawLog.args;
5631
+ if (takeArgs.maker === void 0 || takeArgs.group === void 0 || takeArgs.consumed === void 0) {
5632
+ logger.debug({
5633
+ collector,
5634
+ chainId: client.chain.id,
5635
+ msg: "Skipping Take log because it is missing required args"
5636
+ });
5637
+ continue;
5638
+ }
5639
+ recordLog({
5640
+ kind: "take",
5641
+ id: `${rawLog.blockNumber.toString()}-${rawLog.logIndex.toString()}-${client.chain.id}-${rawLog.transactionHash}`,
5642
+ chainId: client.chain.id,
5643
+ maker: takeArgs.maker,
5644
+ group: takeArgs.group,
5645
+ consumed: takeArgs.consumed,
5646
+ blockNumber: Number(rawLog.blockNumber)
5647
+ });
5648
+ }
5649
+ }
5650
+ await db.transaction(async (dbTx) => {
5651
+ const existingEventIds = /* @__PURE__ */ new Set();
5652
+ if (eventIds.size > 0) {
5653
+ const ids = Array.from(eventIds);
5654
+ for (let index = 0; index < ids.length; index += 500) {
5655
+ const slice = ids.slice(index, index + 500);
5656
+ const { rows } = await dbTx.execute(sql`
5657
+ SELECT event_id
5658
+ FROM ${consumedEvents}
5659
+ WHERE event_id IN (${sql.join(slice.map((id) => sql`${id}`), sql`,`)});
5660
+ `);
5661
+ for (const row of rows) existingEventIds.add(row.event_id);
5662
+ }
5663
+ }
5664
+ const consumedByGroup = /* @__PURE__ */ new Map();
5665
+ if (groups$3.size > 0) {
5666
+ const groupList = Array.from(groups$3.values());
5667
+ for (let index = 0; index < groupList.length; index += 500) {
5668
+ const slice = groupList.slice(index, index + 500);
5669
+ const { rows } = await dbTx.execute(sql`
5670
+ WITH targets(chain_id, maker, "group") AS (
5671
+ VALUES ${sql.join(slice.map((group) => sql`(${group.chainId}::bigint, ${group.maker.toLowerCase()}::varchar(42), ${group.group.toLowerCase()}::varchar(66))`), sql`,`)}
5672
+ )
5673
+ SELECT
5674
+ targets.chain_id,
5675
+ targets.maker,
5676
+ targets."group",
5677
+ COALESCE(g.consumed, 0)::numeric AS consumed
5678
+ FROM targets
5679
+ LEFT JOIN ${groups} g
5680
+ ON g.chain_id = targets.chain_id
5681
+ AND g.maker = targets.maker
5682
+ AND g."group" = targets."group";
5683
+ `);
5684
+ for (const row of rows) {
5685
+ const groupKey = buildGroupKey({
5686
+ chainId: Number(row.chain_id),
5687
+ maker: row.maker,
5688
+ group: row.group
5689
+ });
5690
+ consumedByGroup.set(groupKey, BigInt(row.consumed ?? "0"));
5691
+ }
5692
+ }
5693
+ }
5694
+ const events = [];
5695
+ for (const log of normalizedLogs) {
5696
+ if (existingEventIds.has(log.id)) continue;
5697
+ const groupKey = buildGroupKey({
5698
+ chainId: log.chainId,
5699
+ maker: log.maker,
5700
+ group: log.group
5701
+ });
5702
+ const previousConsumed = consumedByGroup.get(groupKey) ?? 0n;
5703
+ if (log.kind === "consume") {
5704
+ events.push({
5705
+ id: log.id,
5706
+ chainId: log.chainId,
5707
+ maker: log.maker,
5708
+ group: log.group,
5709
+ amount: log.amount,
5710
+ blockNumber: log.blockNumber
5711
+ });
5712
+ consumedByGroup.set(groupKey, previousConsumed + log.amount);
5713
+ continue;
5714
+ }
5715
+ const delta = log.consumed - previousConsumed;
5716
+ if (delta <= 0n) {
5717
+ logger.debug({
5718
+ collector,
5719
+ chainId: client.chain.id,
5720
+ msg: "Skipping Take log because consumed did not increase",
5721
+ previous_consumed: previousConsumed.toString(),
5722
+ consumed: log.consumed.toString()
5723
+ });
5724
+ continue;
5725
+ }
5726
+ events.push({
5727
+ id: log.id,
5728
+ chainId: log.chainId,
5729
+ maker: log.maker,
5730
+ group: log.group,
5731
+ amount: delta,
5732
+ blockNumber: log.blockNumber
5733
+ });
5734
+ consumedByGroup.set(groupKey, log.consumed);
5735
+ }
5736
+ try {
5737
+ await dbTx.consumed.create(events);
5738
+ if (events.length > 0) logger.info({
5739
+ msg: `Events indexed`,
5740
+ collector,
5741
+ count: events.length,
5742
+ chain_id: client.chain.id,
5743
+ block_range: [startBlock, lastStreamBlockNumber]
5744
+ });
5745
+ } catch (err) {
5746
+ logger.error({
5747
+ err,
5748
+ msg: "Failed to process consumed events"
5749
+ });
5750
+ }
5751
+ blockNumber = lastStreamBlockNumber;
5752
+ try {
5753
+ await dbTx.blocks.advanceCollector({
5754
+ collectorName: collector,
5755
+ chainId: client.chain.id,
5756
+ blockNumber,
5757
+ epoch
5758
+ });
5759
+ } catch (_) {
5760
+ try {
5761
+ const ancestor = await dbTx.blocks.getCollector({
5762
+ collectorName: collector,
5763
+ chainId: client.chain.id
5764
+ });
5765
+ blockNumber = ancestor.blockNumber;
5766
+ const deleted = await dbTx.consumed.delete({
5767
+ chainId: client.chain.id,
5768
+ blockNumberGte: blockNumber + 1
5769
+ });
5770
+ logger.info({
5771
+ collector,
5772
+ chain_id: client.chain.id,
5773
+ msg: `Reorg detected, events deleted`,
5774
+ count: deleted,
5775
+ block_number: blockNumber
5776
+ });
5777
+ await dbTx.blocks.advanceCollector({
5778
+ collectorName: collector,
5779
+ chainId: client.chain.id,
5780
+ blockNumber,
5781
+ epoch: ancestor.epoch
5782
+ });
5783
+ reorgDetected = true;
5784
+ } catch (err) {
5785
+ const msg = "Failed to delete consumed events when handling reorg.";
5786
+ logger.error({
5787
+ collector,
5788
+ chainId: client.chain.id,
5789
+ msg,
5790
+ err
5791
+ });
5792
+ throw new Error(msg, { cause: err });
5793
+ }
5794
+ }
5795
+ });
5796
+ if (reorgDetected) return;
5797
+ yield blockNumber;
5798
+ startBlock = blockNumber;
5799
+ }
5800
+ }
5801
+
5802
+ //#endregion
5803
+ //#region src/indexer/collectors/CollectFunctions/collectOffers.ts
5804
+ async function* collectOffersV2(parameters) {
5805
+ let { db, collector, client, lastBlockNumber: blockNumber, gatekeeper, options: { maxBatchSize = 1e3, blockWindow } = {} } = parameters;
5806
+ const logger = getLogger();
5807
+ let startBlock = blockNumber;
5808
+ let reorgDetected = false;
5809
+ if (client.chain.custom.morpho.address.toLowerCase() === zeroAddress) {
5810
+ const msg = "Morpho V2 address is zero, signature verification will fail. Please set the Morpho V2 address in the chain configuration.";
5811
+ logger.error({
5812
+ msg,
5813
+ chain_id: client.chain.id
5814
+ });
5815
+ throw new Error(msg);
5816
+ }
5817
+ const signatureDomain = {
5818
+ chainId: client.chain.id,
5819
+ verifyingContract: client.chain.custom.morpho.address
5820
+ };
5821
+ const { blockNumber: latestBlockNumberChain } = await db.blocks.getChain(client.chain.id);
5822
+ const stream = streamLogs({
5823
+ client,
5824
+ contractAddress: client.chain.custom.mempool.address,
5825
+ event: {
5826
+ type: "event",
5827
+ name: "Event",
5828
+ inputs: [{
5829
+ name: "data",
5830
+ type: "bytes",
5831
+ indexed: false,
5832
+ internalType: "bytes"
5833
+ }],
5834
+ anonymous: false
5835
+ },
5836
+ blockNumberGte: blockNumber,
5837
+ blockNumberLte: latestBlockNumberChain,
5838
+ order: "asc",
5839
+ options: {
5840
+ maxBatchSize,
5841
+ blockWindow
5842
+ }
5843
+ });
5844
+ for await (const { logs, blockNumber: lastStreamBlockNumber } of stream) {
5845
+ blockNumber = lastStreamBlockNumber;
5846
+ const decodedTrees = [];
5847
+ for (const log of logs) {
5848
+ if (!log) continue;
5849
+ const [payload] = decodeAbiParameters([{ type: "bytes" }], log.data);
5850
+ try {
5851
+ const { tree, signature, signer } = await decode(payload, signatureDomain);
5852
+ const signerMismatch = tree.offers.find((offer) => offer.maker.toLowerCase() !== signer.toLowerCase());
5853
+ if (signerMismatch) {
5854
+ logger.debug({
5855
+ msg: "Tree rejected: signer mismatch",
5856
+ reason: "signer_mismatch",
5857
+ signer,
5858
+ maker: signerMismatch.maker,
5859
+ chain_id: client.chain.id
5860
+ });
5861
+ continue;
5862
+ }
5863
+ decodedTrees.push({
5864
+ tree,
5865
+ signature,
5866
+ signer,
5867
+ blockNumber: Number(log.blockNumber)
5868
+ });
5869
+ } catch (err) {
5870
+ const reason = err instanceof DecodeError && err.message.includes("signature") ? "invalid_signature" : "decode_failed";
5871
+ logger.debug({
5872
+ msg: "Tree decode failed",
5873
+ reason,
5874
+ chain_id: client.chain.id,
5875
+ err: err instanceof Error ? err.message : String(err)
5876
+ });
5877
+ }
5878
+ }
5879
+ await db.transaction(async (dbTx) => {
5880
+ const { epoch, blockNumber: latestBlockNumber } = await dbTx.blocks.getChain(client.chain.id);
5881
+ const treesToInsert = [];
5882
+ let totalValidOffers = 0;
5883
+ const offersWithBlock = [];
5884
+ for (const { tree, signature, blockNumber: treeBlockNumber } of decodedTrees) try {
5885
+ const allowedResults = await gatekeeper.isAllowed(tree.offers);
5886
+ const hasBlockWindowViolation = treeBlockNumber > latestBlockNumber;
5887
+ if (!(allowedResults.issues.length === 0 && allowedResults.valid.length === tree.offers.length) || hasBlockWindowViolation) {
5888
+ if (allowedResults.issues.length > 0) {
5889
+ const hasMixedMaker = allowedResults.issues.some((i) => i.ruleName === "mixed_maker");
5890
+ logger.debug({
5891
+ msg: "Tree offers rejected by gatekeeper",
5892
+ reason: hasMixedMaker ? "mixed_maker" : "gatekeeper_rejected",
5893
+ chain_id: client.chain.id,
5894
+ issues_count: allowedResults.issues.length
5895
+ });
5896
+ } else if (hasBlockWindowViolation) logger.debug({
5897
+ msg: "Tree rejected: offers outside block window",
5898
+ reason: "block_window",
5899
+ chain_id: client.chain.id
5900
+ });
5901
+ continue;
5902
+ }
5903
+ treesToInsert.push({
5904
+ tree,
5905
+ signature
5906
+ });
5907
+ totalValidOffers += tree.offers.length;
5908
+ offersWithBlock.push(...tree.offers.map((offer) => ({
5909
+ offer,
5910
+ blockNumber: treeBlockNumber
5911
+ })));
5912
+ } catch (err) {
5913
+ const error = err instanceof Error ? err : new Error(String(err));
5914
+ logger.error({
5915
+ err: error,
5916
+ msg: "Gatekeeper validation failed",
5917
+ chain_id: client.chain.id
5918
+ });
5919
+ throw new Error("Gatekeeper validation failed", { cause: error });
5920
+ }
5921
+ const dependencies = buildOfferDependencies$1(offersWithBlock);
5922
+ await dbTx.oracles.upsert(dependencies.oracles);
5923
+ await dbTx.obligations.create(dependencies.obligations);
5924
+ await dbTx.groups.create(dependencies.groups);
5925
+ const insertedHashes = await dbTx.offers.create(dependencies.offerBatches);
5926
+ if (treesToInsert.length > 0) await dbTx.trees.create(treesToInsert);
5927
+ const insertedOffers = filterInsertedOffers({
5928
+ offers: offersWithBlock,
5929
+ hashes: insertedHashes
5930
+ });
5931
+ const { callbacks, positions, lots } = decodeCallbacks({
5932
+ chainId: client.chain.id,
5933
+ offers: insertedOffers
5934
+ });
5935
+ if (positions.length > 0) await dbTx.positions.upsert(positions);
5936
+ if (callbacks.length > 0) await dbTx.callbacks.upsert(callbacks);
5937
+ if (lots.length > 0) await dbTx.lots.create(lots);
5938
+ try {
5939
+ await dbTx.blocks.advanceCollector({
5940
+ collectorName: collector,
5941
+ chainId: client.chain.id,
5942
+ blockNumber,
5943
+ epoch
5944
+ });
5945
+ if (totalValidOffers > 0) logger.info({
5946
+ msg: `New offers`,
5947
+ collector,
5948
+ count: totalValidOffers,
5949
+ trees_count: treesToInsert.length,
5950
+ chain_id: client.chain.id,
5951
+ block_range: [startBlock, lastStreamBlockNumber]
5952
+ });
5953
+ } catch (_) {
5954
+ try {
5955
+ const ancestor = await dbTx.blocks.getCollector({
5956
+ collectorName: collector,
5957
+ chainId: client.chain.id
5958
+ });
5959
+ blockNumber = ancestor.blockNumber;
5960
+ const deleted = await dbTx.offers.delete({
5961
+ blockNumberGte: blockNumber + 1,
5962
+ chainId: client.chain.id
5963
+ });
5964
+ logger.info({
5965
+ collector,
5966
+ chain_id: client.chain.id,
5967
+ msg: `Reorg detected, offers deleted`,
5968
+ count: deleted,
5969
+ block_number: blockNumber
5970
+ });
5971
+ await dbTx.blocks.advanceCollector({
5972
+ collectorName: collector,
5973
+ chainId: client.chain.id,
5974
+ blockNumber,
5975
+ epoch: ancestor.epoch
5976
+ });
5977
+ reorgDetected = true;
5978
+ } catch (err) {
5979
+ const msg = "Failed to delete offers when handling reorg.";
5980
+ logger.error({
5981
+ collector,
5982
+ chainId: client.chain.id,
5983
+ msg,
5984
+ err
5985
+ });
5986
+ throw new Error(msg);
5987
+ }
5988
+ }
5989
+ });
5990
+ if (reorgDetected) return;
5991
+ yield blockNumber;
5992
+ startBlock = blockNumber;
5993
+ }
5994
+ }
5995
+ function decodeCallbacks(parameters) {
5996
+ const { offers } = parameters;
5997
+ if (offers.length === 0) return {
5998
+ callbacks: [],
5999
+ positions: [],
6000
+ lots: []
6001
+ };
6002
+ const callbacks = [];
6003
+ const positions = [];
6004
+ const lots = [];
6005
+ for (const { offer, blockNumber: offerBlockNumber } of offers) {
6006
+ if (!offer.buy) continue;
6007
+ if (!isEmptyCallback(offer)) continue;
6008
+ const loanToken = offer.loanToken.toLowerCase();
6009
+ positions.push(from$10({
6010
+ chainId: offer.chainId,
6011
+ contract: loanToken,
6012
+ user: offer.maker,
6013
+ type: Type.ERC20,
6014
+ asset: loanToken,
6015
+ blockNumber: offerBlockNumber
6016
+ }));
6017
+ lots.push({
6018
+ positionChainId: offer.chainId,
6019
+ positionContract: loanToken,
6020
+ positionUser: offer.maker,
6021
+ group: offer.group,
6022
+ size: offer.assets
6023
+ });
6024
+ callbacks.push({
6025
+ offerHash: hash(offer),
6026
+ callbacks: [{
6027
+ chainId: offer.chainId,
6028
+ contract: loanToken,
6029
+ user: offer.maker,
6030
+ amount: offer.assets
6031
+ }]
6032
+ });
6033
+ }
6034
+ return {
6035
+ callbacks,
6036
+ positions,
6037
+ lots
6038
+ };
6039
+ }
6040
+ function buildOfferDependencies$1(offers) {
6041
+ const obligationsById = /* @__PURE__ */ new Map();
6042
+ const oraclesByKey = /* @__PURE__ */ new Map();
6043
+ const groupsByKey = /* @__PURE__ */ new Map();
6044
+ const offersByBlock = /* @__PURE__ */ new Map();
6045
+ for (const { offer, blockNumber } of offers) {
6046
+ const list = offersByBlock.get(blockNumber) ?? [];
6047
+ list.push(offer);
6048
+ offersByBlock.set(blockNumber, list);
6049
+ const obligationId$2 = obligationId(offer);
6050
+ if (!obligationsById.get(obligationId$2)) obligationsById.set(obligationId$2, from$13({
6051
+ chainId: offer.chainId,
6052
+ loanToken: offer.loanToken,
6053
+ maturity: offer.maturity,
6054
+ collaterals: offer.collaterals
6055
+ }));
6056
+ for (const collateral of offer.collaterals) {
6057
+ const oracleKey = `${offer.chainId}-${collateral.oracle}`.toLowerCase();
6058
+ if (!oraclesByKey.has(oracleKey)) oraclesByKey.set(oracleKey, from$11({
6059
+ chainId: offer.chainId,
6060
+ address: collateral.oracle,
6061
+ price: null,
6062
+ blockNumber
6063
+ }));
6064
+ }
6065
+ const groupKey = `${offer.chainId}-${offer.maker}-${offer.group}`.toLowerCase();
6066
+ if (!groupsByKey.has(groupKey)) groupsByKey.set(groupKey, {
6067
+ chainId: offer.chainId,
6068
+ maker: offer.maker,
6069
+ group: offer.group,
6070
+ blockNumber
6071
+ });
6072
+ }
6073
+ return {
6074
+ obligations: Array.from(obligationsById.values()),
6075
+ oracles: Array.from(oraclesByKey.values()),
6076
+ groups: Array.from(groupsByKey.values()),
6077
+ offerBatches: Array.from(offersByBlock.entries()).map(([blockNumber, items]) => ({
6078
+ blockNumber,
6079
+ offers: items
6080
+ }))
6081
+ };
6082
+ }
6083
+ function filterInsertedOffers(parameters) {
6084
+ if (parameters.hashes.length === 0) return [];
6085
+ const inserted = new Set(parameters.hashes.map((hash) => hash.toLowerCase()));
6086
+ const seen = /* @__PURE__ */ new Set();
6087
+ const filtered = [];
6088
+ for (const entry of parameters.offers) {
6089
+ const hash$2 = hash(entry.offer).toLowerCase();
6090
+ if (!inserted.has(hash$2)) continue;
6091
+ if (seen.has(hash$2)) continue;
6092
+ seen.add(hash$2);
6093
+ filtered.push(entry);
6094
+ }
6095
+ return filtered;
6096
+ }
6097
+
6098
+ //#endregion
6099
+ //#region src/indexer/collectors/fetchers/fetchOraclePrices.ts
6100
+ /**
6101
+ * Fetches prices from multiple oracle contracts using multicall.
6102
+ *
6103
+ * - Executes `price()` on {@link Abi.Oracle} for each {@link Address}.
6104
+ * - Requires a client supporting {@link PublicActions.multicall | multicall}.
6105
+ *
6106
+ * @param parameters - {@link fetchOraclePrices.Parameters} including the list of oracle
6107
+ * {@link Address | addresses}, fetch options, and the multicall-enabled client.
6108
+ * @returns {@link fetchOraclePrices.ReturnType} mapping {@link Address} to `bigint` price.
6109
+ */
6110
+ async function fetchOraclePrices(parameters) {
6111
+ const { client, oracles, options } = parameters;
6112
+ if (oracles.length === 0) return /* @__PURE__ */ new Map();
6113
+ const batchSize = Math.max(1, options?.batchSize ?? 5e3);
6114
+ const retryAttempts = Math.max(1, options?.retryAttempts ?? 3);
6115
+ const retryDelayMs = Math.max(0, options?.retryDelayMs ?? 50);
6116
+ const blockNumber = options?.blockNumber ? BigInt(options.blockNumber) : void 0;
6117
+ const out = /* @__PURE__ */ new Map();
6118
+ for (const oraclesBatch of batch$1(oracles, batchSize)) {
6119
+ const priceCalls = [];
6120
+ for (const oracle of oraclesBatch) priceCalls.push({
6121
+ address: oracle,
6122
+ abi: Oracle,
6123
+ functionName: "price",
6124
+ args: []
6125
+ });
6126
+ const prices = await batchMulticall({
6127
+ client,
6128
+ calls: priceCalls,
6129
+ batchSize,
6130
+ retryAttempts,
6131
+ retryDelayMs,
6132
+ blockNumber
6133
+ });
6134
+ for (let i = 0; i < oraclesBatch.length; i++) {
6135
+ const oracle = oraclesBatch[i];
6136
+ const price = prices[i];
6137
+ out.set(oracle, price);
6138
+ }
6139
+ }
6140
+ return out;
6141
+ }
6142
+
6143
+ //#endregion
6144
+ //#region src/indexer/collectors/fetchers/snapshotERC20Positions.ts
6145
+ /**
6146
+ * Fetches ERC20 balances for positions at a given block number and returns the positions with the updated balances.
6147
+ * @notice This method does not mutate positions and returns a new array.
6148
+ *
6149
+ * @param parameters - {@link snapshotERC20Positions.Parameters}
6150
+ * @returns Positions - {@link snapshotERC20Positions.ReturnType}
6151
+ */
6152
+ async function snapshotERC20Positions(parameters) {
6153
+ const { positions: oldPositions, blockNumber, options: { maxBatchSize = 1e3, retryAttempts = 5, retryDelayMs = 500 } = {} } = parameters;
6154
+ if (oldPositions.length === 0) return [];
6155
+ const calls = [];
6156
+ for (const position of oldPositions) calls.push({
6157
+ address: position.contract,
6158
+ abi: erc20Abi,
6159
+ functionName: "balanceOf",
6160
+ args: [position.user]
6161
+ });
6162
+ const balances = await batchMulticall({
6163
+ client: parameters.client,
6164
+ calls,
6165
+ blockNumber: BigInt(blockNumber),
6166
+ batchSize: maxBatchSize,
6167
+ retryAttempts,
6168
+ retryDelayMs
6169
+ });
6170
+ const positions = [];
6171
+ for (let i = 0; i < balances.length; i++) {
6172
+ const oldPosition = oldPositions[i];
6173
+ if (!oldPosition) continue;
6174
+ positions.push({
6175
+ ...oldPosition,
6176
+ balance: balances[i],
6177
+ blockNumber
6178
+ });
6179
+ }
6180
+ return positions;
6181
+ }
6182
+
6183
+ //#endregion
6184
+ //#region src/indexer/collectors/fetchers/snapshotVaultPositions.ts
6185
+ /**
6186
+ * Fetches vault shares for users at a given block number, converts them to assets and returns the positions with the updated balances.
6187
+ * @notice This method does not mutate positions and returns a new array.
6188
+ *
6189
+ * @param parameters - {@link snapshotVaultPositions.Parameters}
6190
+ * @returns Positions - {@link snapshotVaultPositions.ReturnType}
6191
+ */
6192
+ async function snapshotVaultPositions(parameters) {
6193
+ const logger = getLogger();
6194
+ const { client, positions: oldPositions, blockNumber, options: { maxBatchSize = 1e3, retryAttempts = 5, retryDelayMs = 500 } = {} } = parameters;
6195
+ const calls = [];
6196
+ const contracts = /* @__PURE__ */ new Map();
6197
+ const positions = structuredClone(oldPositions);
6198
+ for (const position of positions) {
6199
+ calls.push({
6200
+ address: position.contract,
6201
+ abi: MetaMorpho,
6202
+ functionName: "balanceOf",
6203
+ args: [position.user],
6204
+ convertToAssets: (shares) => {
6205
+ const contract = contracts.get(position.contract.toLowerCase());
6206
+ if (!contract) return;
6207
+ if (contract.decimalsOffset === void 0 || contract.totalAssets === void 0 || contract.totalSupply === void 0) return;
6208
+ try {
6209
+ position.balance = convertToAssets({
6210
+ shares,
6211
+ totalAssets: contract.totalAssets,
6212
+ totalSupply: contract.totalSupply,
6213
+ decimalsOffset: contract.decimalsOffset
6214
+ });
6215
+ position.asset = contract.asset;
6216
+ position.blockNumber = blockNumber;
6217
+ } catch (err) {
6218
+ if (err instanceof DenominatorIsZeroError) {
6219
+ logger.error({
6220
+ msg: "Failed to convert shares to assets",
6221
+ chain_id: client.chain.id,
6222
+ block_number: blockNumber,
6223
+ position_contract: position.contract,
6224
+ position_user: position.user,
6225
+ shares,
6226
+ err
6227
+ });
6228
+ return;
6229
+ }
6230
+ throw err;
6231
+ }
6232
+ }
6233
+ });
6234
+ if (contracts.has(position.contract.toLowerCase())) continue;
6235
+ calls.push({
6236
+ address: position.contract,
6237
+ abi: MetaMorpho,
6238
+ functionName: "DECIMALS_OFFSET",
6239
+ args: []
6240
+ }, {
6241
+ address: position.contract,
6242
+ abi: MetaMorpho,
6243
+ functionName: "totalAssets",
6244
+ args: []
6245
+ }, {
6246
+ address: position.contract,
6247
+ abi: MetaMorpho,
6248
+ functionName: "totalSupply",
6249
+ args: []
6250
+ }, {
6251
+ address: position.contract,
6252
+ abi: MetaMorpho,
6253
+ functionName: "asset",
6254
+ args: []
6255
+ });
6256
+ contracts.set(position.contract.toLowerCase(), {
6257
+ decimalsOffset: void 0,
6258
+ totalAssets: void 0,
6259
+ totalSupply: void 0,
6260
+ asset: void 0
6261
+ });
6262
+ }
6263
+ const results = await batchMulticall({
6264
+ client,
6265
+ calls,
6266
+ blockNumber: BigInt(blockNumber),
6267
+ batchSize: maxBatchSize,
6268
+ retryAttempts,
6269
+ retryDelayMs
6270
+ });
6271
+ const convertToAssetsList = [];
6272
+ for (let i = 0; i < results.length; i++) {
6273
+ const call = calls[i];
6274
+ const value = results[i];
6275
+ const contract = contracts.get(call.address.toLowerCase());
6276
+ if (!contract) continue;
6277
+ switch (call.functionName) {
6278
+ case "balanceOf":
6279
+ convertToAssetsList.push(() => call.convertToAssets(value));
6280
+ break;
6281
+ case "DECIMALS_OFFSET":
6282
+ contract.decimalsOffset = value;
6283
+ break;
6284
+ case "totalAssets":
6285
+ contract.totalAssets = value;
6286
+ break;
6287
+ case "totalSupply":
6288
+ contract.totalSupply = value;
6289
+ break;
6290
+ case "asset":
6291
+ contract.asset = value.toLowerCase();
6292
+ break;
6293
+ }
6294
+ }
6295
+ for (const convertToAssets of convertToAssetsList) convertToAssets();
6296
+ return positions;
6297
+ }
6298
+
6299
+ //#endregion
6300
+ //#region src/indexer/collectors/CollectFunctions/collectPositions.ts
6301
+ async function* collectPositions(parameters) {
6302
+ let { db, collector, client, lastBlockNumber: blockNumber, epoch, options: { maxBatchSize = 1e3, retryAttempts = 5, retryDelayMs = 500, blockWindow } = {} } = parameters;
6303
+ const logger = getLogger();
6304
+ let startBlock = blockNumber;
6305
+ let reorgDetected = false;
6306
+ const TransferEvent = {
6307
+ type: "event",
6308
+ name: "Transfer",
6309
+ inputs: [
6310
+ {
6311
+ name: "from",
6312
+ type: "address",
6313
+ indexed: true
6314
+ },
6315
+ {
6316
+ name: "to",
6317
+ type: "address",
6318
+ indexed: true
6319
+ },
6320
+ {
6321
+ name: "value",
6322
+ type: "uint256",
6323
+ indexed: false
6324
+ }
6325
+ ]
6326
+ };
6327
+ const { blockNumber: latestBlockNumberChain } = await db.blocks.getChain(client.chain.id);
6328
+ const stream = streamLogs({
6329
+ client,
6330
+ event: TransferEvent,
6331
+ blockNumberGte: blockNumber,
6332
+ blockNumberLte: latestBlockNumberChain,
6333
+ order: "asc",
6334
+ options: {
6335
+ maxBatchSize,
6336
+ blockWindow
6337
+ }
6338
+ });
6339
+ for await (const { logs, blockNumber: lastStreamBlockNumber } of stream) {
6340
+ blockNumber = lastStreamBlockNumber;
6341
+ const parsedLogs = parseEventLogs({
6342
+ abi: [TransferEvent],
6343
+ logs
6344
+ });
6345
+ const transfers = [];
6346
+ for (const log of parsedLogs) {
6347
+ if (log.blockNumber === null || log.logIndex === null || log.transactionHash === null) {
6348
+ logger.debug({
6349
+ collector,
6350
+ chainId: client.chain.id,
6351
+ msg: "Skipping log because it is missing required fields"
6352
+ });
6353
+ continue;
6354
+ }
6355
+ transfers.push(from$8({
6356
+ id: `${client.chain.id}-${log.blockNumber.toString()}-${log.transactionHash}-${log.logIndex.toString()}`,
6357
+ chainId: client.chain.id,
6358
+ contract: log.address,
6359
+ from: log.args.from,
6360
+ to: log.args.to,
6361
+ value: log.args.value,
6362
+ blockNumber: Number(log.blockNumber)
6363
+ }));
6364
+ }
6365
+ const { positions } = await db.positions.get({
6366
+ chainId: client.chain.id,
6367
+ filled: false
6368
+ });
6369
+ const newPositions = [];
6370
+ try {
6371
+ newPositions.push(...(await _snapshot({
6372
+ positions,
6373
+ blockNumber: latestBlockNumberChain,
6374
+ client,
6375
+ maxBatchSize,
6376
+ retryAttempts,
6377
+ retryDelayMs
6378
+ })).map((p) => ({
6379
+ ...p,
6380
+ blockNumber: p.blockNumber + 1
6381
+ })));
6382
+ } catch (err) {
6383
+ logger.error({
6384
+ msg: "Failed to snapshot new empty positions",
6385
+ collector,
6386
+ chain_id: client.chain.id,
6387
+ block_number: latestBlockNumberChain,
6388
+ err
6389
+ });
6390
+ yield startBlock;
6391
+ return;
6392
+ }
6393
+ try {
6394
+ await db.transaction(async (dbTx) => {
6395
+ const insertPositions = async () => {
6396
+ if (newPositions.length === 0) return;
6397
+ try {
6398
+ const count = await dbTx.positions.upsert(newPositions);
6399
+ logger.info({
6400
+ msg: `New positions`,
6401
+ collector,
6402
+ count,
6403
+ chain_id: client.chain.id,
6404
+ block_number: latestBlockNumberChain
6405
+ });
6406
+ } catch (err) {
6407
+ throw new InsertPositionsError(err);
6408
+ }
6409
+ };
6410
+ const insertTransfers = async () => {
6411
+ if (transfers.length === 0) return;
6412
+ try {
6413
+ const created = await dbTx.transfers.create(transfers);
6414
+ logger.info({
6415
+ msg: `New transfers`,
6416
+ collector,
6417
+ count: created,
6418
+ chain_id: client.chain.id,
6419
+ block_range: [startBlock, blockNumber]
6420
+ });
6421
+ } catch (err) {
6422
+ throw new InsertTransfersError(err);
6423
+ }
6424
+ };
6425
+ const saveBlockNumber = async () => {
6426
+ try {
6427
+ await dbTx.blocks.advanceCollector({
6428
+ collectorName: collector,
6429
+ chainId: client.chain.id,
6430
+ blockNumber,
6431
+ epoch
6432
+ });
6433
+ } catch (_) {
6434
+ throw new ReorgError(blockNumber);
6435
+ }
6436
+ };
6437
+ await insertPositions();
6438
+ await insertTransfers();
6439
+ await saveBlockNumber();
6440
+ });
6441
+ } catch (err) {
6442
+ if (err instanceof ReorgError) {
6443
+ logger.info({
6444
+ msg: "Reorg detected, positions and transfers insertion aborted",
6445
+ collector,
6446
+ count: newPositions.length,
6447
+ chain_id: client.chain.id,
6448
+ block_number: blockNumber
6449
+ });
6450
+ reorgDetected = true;
6451
+ }
6452
+ if (err instanceof InsertPositionsError) {
6453
+ logger.error({
6454
+ msg: "Failed to insert positions",
6455
+ collector,
6456
+ count: newPositions.length,
6457
+ chain_id: client.chain.id,
6458
+ block_number: latestBlockNumberChain,
6459
+ err
6460
+ });
6461
+ throw err.cause;
6462
+ }
6463
+ if (err instanceof InsertTransfersError) {
6464
+ logger.error({
6465
+ msg: "Failed to insert transfers",
6466
+ collector,
6467
+ count: transfers.length,
6468
+ chain_id: client.chain.id,
6469
+ block_number: blockNumber,
6470
+ err
6471
+ });
6472
+ throw err.cause;
6473
+ }
6474
+ }
6475
+ if (!reorgDetected) {
6476
+ startBlock = blockNumber;
6477
+ if (newPositions.length === 0 && transfers.length === 0 && lastStreamBlockNumber !== latestBlockNumberChain) continue;
6478
+ yield blockNumber;
6479
+ continue;
6480
+ }
6481
+ await db.transaction(async (dbTx) => {
6482
+ try {
6483
+ const ancestor = await dbTx.blocks.getCollector({
6484
+ collectorName: collector,
6485
+ chainId: client.chain.id
6486
+ });
6487
+ blockNumber = ancestor.blockNumber;
6488
+ const emptied = await dbTx.positions.setEmptyAfter({
6489
+ chainId: client.chain.id,
6490
+ blockNumber: blockNumber + 1
6491
+ });
6492
+ logger.info({
6493
+ msg: "Reorg detected, positions set to empty",
6494
+ collector,
6495
+ count: emptied,
6496
+ chain_id: client.chain.id,
6497
+ block_number_gte: blockNumber + 1
6498
+ });
6499
+ await dbTx.blocks.advanceCollector({
6500
+ collectorName: collector,
6501
+ chainId: client.chain.id,
6502
+ blockNumber,
6503
+ epoch: ancestor.epoch
6504
+ });
6505
+ } catch (err) {
6506
+ const msg = "Failed to revert to ancestor block when handling reorg.";
6507
+ logger.error({
6508
+ collector,
6509
+ chainId: client.chain.id,
6510
+ msg,
6511
+ err
6512
+ });
6513
+ throw new Error(msg);
6514
+ }
6515
+ });
6516
+ return;
6517
+ }
6518
+ }
6519
+ /**
6520
+ * @internal
6521
+ *
6522
+ * Snapshots positions and returns the new positions.
6523
+ * @param parameters - {@link _snapshot.Parameters}
6524
+ * @returns The new positions. {@link _snapshot.ReturnType}
6525
+ */
6526
+ async function _snapshot(parameters) {
6527
+ const { positions, blockNumber, client, maxBatchSize, retryAttempts, retryDelayMs } = parameters;
6528
+ const vaultV1Positions = [];
6529
+ const erc20Positions = [];
6530
+ for (const position of positions) switch (position.type) {
6531
+ case Type.VAULT_V1:
6532
+ vaultV1Positions.push(position);
6533
+ break;
6534
+ case Type.ERC20:
6535
+ erc20Positions.push(position);
6536
+ break;
6537
+ default: throw new Error("Invalid position type");
6538
+ }
6539
+ const promises = [snapshotVaultPositions({
6540
+ client,
6541
+ positions: vaultV1Positions,
6542
+ blockNumber,
6543
+ options: {
6544
+ maxBatchSize,
6545
+ retryAttempts,
6546
+ retryDelayMs
6547
+ }
6548
+ }), snapshotERC20Positions({
6549
+ client,
6550
+ positions: erc20Positions,
6551
+ blockNumber,
6552
+ options: {
6553
+ maxBatchSize,
6554
+ retryAttempts,
6555
+ retryDelayMs
6556
+ }
6557
+ })];
6558
+ return (await Promise.all(promises)).flat();
6559
+ }
6560
+ var InsertPositionsError = class extends BaseError {
6561
+ name = "InsertPositionsError";
6562
+ constructor(err) {
6563
+ super("Failed to insert positions", { cause: err });
6564
+ }
6565
+ };
6566
+ var InsertTransfersError = class extends BaseError {
6567
+ name = "InsertTransfersError";
6568
+ constructor(err) {
6569
+ super("Failed to insert transfers", { cause: err });
6570
+ }
6571
+ };
6572
+
6573
+ //#endregion
6574
+ //#region src/indexer/collectors/CollectFunctions/collectPrices.ts
6575
+ /**
6576
+ * Collects oracle prices from on-chain oracles and persists them.
6577
+ *
6578
+ * - Reads oracle definitions as {@link Oracle.Oracle}.
6579
+ * - Uses chain metadata from {@link Chain.Chain}.
6580
+ *
6581
+ * @param parameters - {@link collectPrices.Parameters} (extends {@link Collector.CollectParameters})
6582
+ * with a client supporting {@link PublicActions.multicall | multicall}.
6583
+ * @yields Latest processed block number after each successful update.
6584
+ */
6585
+ async function* collectPrices(parameters) {
6586
+ const { db, collector, client, options: { maxBatchSize = 5e3, retryAttempts = 5, retryDelayMs = 500 } = {} } = parameters;
6587
+ const logger = getLogger();
6588
+ let blockNumber = parameters.lastBlockNumber;
6589
+ const [oracles, { blockNumber: latestBlockNumberChain, epoch }] = await Promise.all([db.oracles.get({ chainId: client.chain.id }), db.blocks.getChain(client.chain.id)]);
6590
+ const updatedOracles = [];
6591
+ try {
6592
+ const pricesMap = await fetchOraclePrices({
6593
+ client,
6594
+ oracles: oracles.map((oracle) => oracle.address),
6595
+ options: {
6596
+ batchSize: maxBatchSize,
6597
+ blockNumber: latestBlockNumberChain,
6598
+ retryAttempts,
6599
+ retryDelayMs
6600
+ }
6601
+ });
6602
+ for (const oracle of oracles) {
6603
+ const price = pricesMap.get(oracle.address);
6604
+ if (price !== void 0) updatedOracles.push({
6605
+ chainId: client.chain.id,
6606
+ address: oracle.address,
6607
+ price,
6608
+ blockNumber: latestBlockNumberChain
6609
+ });
6610
+ }
6611
+ } catch (err) {
6612
+ logger.error({
6613
+ msg: "Failed to fetch oracle prices",
6614
+ collector,
6615
+ chain_id: client.chain.id,
6616
+ block_number: latestBlockNumberChain,
6617
+ err
6618
+ });
6619
+ yield blockNumber;
6620
+ return;
6621
+ }
6622
+ let reorgDetected = false;
6623
+ try {
6624
+ await db.transaction(async (dbTx) => {
6625
+ if (updatedOracles.length > 0) {
6626
+ await dbTx.oracles.upsert(updatedOracles);
6627
+ logger.info({
6628
+ msg: "Oracle prices updated",
6629
+ collector,
6630
+ count: updatedOracles.length,
6631
+ chain_id: client.chain.id,
6632
+ block_number: latestBlockNumberChain
6633
+ });
6634
+ }
6635
+ try {
6636
+ await dbTx.blocks.advanceCollector({
6637
+ collectorName: collector,
6638
+ chainId: client.chain.id,
6639
+ blockNumber: latestBlockNumberChain,
6640
+ epoch
6641
+ });
6642
+ } catch (_) {
6643
+ throw new ReorgError(latestBlockNumberChain);
6644
+ }
6645
+ blockNumber = latestBlockNumberChain;
6646
+ });
6647
+ } catch (err) {
6648
+ if (err instanceof ReorgError) {
6649
+ logger.info({
6650
+ msg: "Reorg detected, prices update aborted",
6651
+ collector,
6652
+ count: updatedOracles.length,
6653
+ chain_id: client.chain.id,
6654
+ block_number: latestBlockNumberChain
6655
+ });
6656
+ reorgDetected = true;
6657
+ } else throw new Error("Failed to collect oracle prices", { cause: err });
6658
+ }
6659
+ if (!reorgDetected) {
6660
+ yield blockNumber;
6661
+ return;
6662
+ }
6663
+ await db.transaction(async (dbTx) => {
6664
+ try {
6665
+ const ancestor = await dbTx.blocks.getCollector({
6666
+ collectorName: collector,
6667
+ chainId: client.chain.id
6668
+ });
6669
+ blockNumber = ancestor.blockNumber;
6670
+ await dbTx.blocks.advanceCollector({
6671
+ collectorName: collector,
6672
+ chainId: client.chain.id,
6673
+ blockNumber,
6674
+ epoch: ancestor.epoch
6675
+ });
6676
+ } catch (err) {
6677
+ const msg = "Failed to revert to ancestor block when handling reorg.";
6678
+ logger.error({
6679
+ collector,
6680
+ chainId: client.chain.id,
6681
+ msg,
6682
+ err
6683
+ });
6684
+ throw new Error(msg);
6685
+ }
6686
+ });
6687
+ yield blockNumber;
6688
+ }
6689
+
6690
+ //#endregion
6691
+ //#region src/indexer/collectors/CollectorBuilder.ts
6692
+ function createBuilder(parameters) {
6693
+ const { client, db, gatekeeper, options: { maxBlockNumber, blockWindow, interval } = {} } = parameters;
6694
+ const createCollector = (name, collect) => create$16({
6695
+ name,
6696
+ collect,
6697
+ client,
6698
+ db,
6699
+ options: {
6700
+ maxBlockNumber,
6701
+ interval
6702
+ }
6703
+ });
6704
+ return {
6705
+ buildOffersCollector: ({ options: { maxBatchSize = 1e3 } = {} }) => {
6706
+ return createCollector("offers", (p) => collectOffersV2({
6707
+ ...p,
6708
+ gatekeeper,
6709
+ collector: "offers",
6710
+ client,
6711
+ options: {
6712
+ maxBatchSize,
6713
+ blockWindow
6714
+ }
6715
+ }));
6716
+ },
6717
+ buildConsumedEventsCollector: ({ options: { maxBatchSize = 1e3 } = {} } = {}) => {
6718
+ return createCollector("consumed_events", (p) => collectConsumedEvents({
6719
+ ...p,
6720
+ collector: "consumed_events",
6721
+ options: {
6722
+ maxBatchSize,
6723
+ blockWindow
6724
+ }
6725
+ }));
6726
+ },
6727
+ buildPricesCollector: ({ options: { maxBatchSize = 5e3, retryAttempts, retryDelayMs } = {} } = {}) => {
6728
+ return createCollector("prices", (p) => collectPrices({
6729
+ ...p,
6730
+ collector: "prices",
6731
+ options: {
6732
+ maxBatchSize,
6733
+ retryAttempts,
6734
+ retryDelayMs
6735
+ }
6736
+ }));
6737
+ },
6738
+ buildPositionsCollector: ({ options: { maxBatchSize = 1e3, retryAttempts, retryDelayMs } = {} } = {}) => {
6739
+ return createCollector("positions", (p) => collectPositions({
6740
+ ...p,
6741
+ collector: "positions",
6742
+ options: {
6743
+ maxBatchSize,
6744
+ retryAttempts,
6745
+ retryDelayMs,
6746
+ blockWindow
6747
+ }
6748
+ }));
6749
+ }
6750
+ };
6751
+ }
6752
+
6753
+ //#endregion
6754
+ //#region src/indexer/collectors/Collectors.ts
6755
+ const from$2 = (parameters) => {
6756
+ const { client, db, gatekeeper, maxBatchSize, maxBlockNumber, blockWindow, interval, retryAttempts, retryDelayMs } = parameters;
6757
+ const collectorBuilder = createBuilder({
6758
+ client,
6759
+ db,
6760
+ gatekeeper,
6761
+ options: {
6762
+ maxBlockNumber,
6763
+ blockWindow,
6764
+ interval
6765
+ }
6766
+ });
6767
+ return {
6768
+ offersCollector: collectorBuilder.buildOffersCollector({ options: { maxBatchSize } }),
6769
+ consumedEventsCollector: collectorBuilder.buildConsumedEventsCollector({ options: { maxBatchSize } }),
6770
+ pricesCollector: collectorBuilder.buildPricesCollector({ options: {
6771
+ maxBatchSize,
6772
+ retryAttempts,
6773
+ retryDelayMs
6774
+ } }),
6775
+ positionsCollector: collectorBuilder.buildPositionsCollector({ options: {
6776
+ maxBatchSize,
6777
+ retryAttempts,
6778
+ retryDelayMs
6779
+ } })
6780
+ };
6781
+ };
6782
+
6783
+ //#endregion
6784
+ //#region src/indexer/Indexer.ts
6785
+ function from$1(config) {
6786
+ const { client, gatekeeper, db, interval = 1e4, maxBatchSize = 1e3, maxBlockNumber, blockWindow, retryAttempts, retryDelayMs } = config;
6787
+ const { offersCollector, consumedEventsCollector, positionsCollector, pricesCollector } = from$2({
6788
+ client,
6789
+ db,
6790
+ gatekeeper,
6791
+ maxBatchSize,
6792
+ maxBlockNumber,
6793
+ blockWindow,
6794
+ interval,
6795
+ retryAttempts,
6796
+ retryDelayMs
6797
+ });
6798
+ return create$18({
6799
+ client,
6800
+ collectors: [
6801
+ offersCollector,
6802
+ consumedEventsCollector,
6803
+ positionsCollector,
6804
+ pricesCollector
6805
+ ]
6806
+ });
6807
+ }
6808
+ function create$18(params) {
6809
+ const { collectors, client } = params;
6810
+ const indexerId = `${client.chain.id.toString()}.indexer`;
6811
+ const tracer = getTracer(`router.${indexerId}`);
6812
+ const iterators = collectors.map((collector) => collector.collect());
6813
+ const next = async () => {
6814
+ await startActiveSpan(tracer, `${indexerId}.next`, async () => {
6815
+ await Promise.all(iterators.map((iterator) => iterator.next()));
6816
+ });
6817
+ };
6818
+ const _return = async () => {
6819
+ await Promise.all(iterators.map(async (iterator) => iterator.return()));
6820
+ };
6821
+ return {
6822
+ start: () => {
6823
+ const stops = collectors.map((collector) => start(collector));
6824
+ return () => {
6825
+ stops.forEach((stop) => {
6826
+ stop();
6827
+ });
6828
+ };
6829
+ },
6830
+ next,
6831
+ return: _return
6832
+ };
6833
+ }
6834
+
6835
+ //#endregion
6836
+ //#region src/indexer/collectors/Admin.ts
6837
+ function create$17(parameters) {
6838
+ const collector = "admin";
6839
+ const { client, db, options: { maxBatchSize = 25, maxBlockNumber } = {} } = parameters;
6840
+ const maxBlockNumberBI = maxBlockNumber !== void 0 ? BigInt(maxBlockNumber) : void 0;
6841
+ let finalizedBlock = null;
6842
+ let unfinalizedBlocks = [];
6843
+ let initialized = false;
6844
+ const logger = getLogger();
6845
+ let isMaxBlockNumberReached = false;
6846
+ let tick = 0;
6847
+ return { syncBlock: async () => {
6848
+ if (!initialized) {
6849
+ await Promise.all(names.map((collectorName) => db.blocks.init({
6850
+ chainId: client.chain.id,
6851
+ collectorName
6852
+ })));
6853
+ initialized = true;
6854
+ }
6855
+ if (isMaxBlockNumberReached) return true;
6856
+ const head = await client.getBlock({
6857
+ blockTag: "latest",
6858
+ includeTransactions: false
6859
+ });
6860
+ await db.transaction(async (dbTx) => {
6861
+ const { epoch, blockNumber: latestSavedBlockNumber } = await dbTx.blocks.getChain(client.chain.id);
6862
+ if (maxBlockNumberBI !== void 0 && head.number >= maxBlockNumberBI) {
6863
+ logger.info({
6864
+ msg: `Head is greater than max block number`,
6865
+ collector,
6866
+ chainId: client.chain.id,
6867
+ block_number: head.number,
6868
+ max_block_number: maxBlockNumber
6869
+ });
6870
+ await dbTx.blocks.handleReorg({
6871
+ chainId: client.chain.id,
6872
+ blockNumber: maxBlockNumber,
6873
+ epoch: epoch + 1n
6874
+ });
6875
+ isMaxBlockNumberReached = true;
6876
+ return isMaxBlockNumberReached;
6877
+ }
6878
+ finalizedBlock = await fetchFinalizedBlock({
6879
+ tick,
6880
+ client,
6881
+ logger,
6882
+ collector,
6883
+ unfinalizedBlocks,
6884
+ previousFinalizedBlock: finalizedBlock
6885
+ });
6886
+ tick++;
6887
+ let { block: returnedBlock, didReorgHappened, unfinalizedBlocks: newUnfinalizedBlocks } = await reconcile({
6888
+ client,
6889
+ block: head,
6890
+ unfinalizedBlocks,
6891
+ finalizedBlock,
6892
+ logger,
6893
+ collector,
6894
+ maxBatchSize
6895
+ });
6896
+ unfinalizedBlocks = newUnfinalizedBlocks;
6897
+ const blockNumber = Number(returnedBlock.number);
6898
+ didReorgHappened = didReorgHappened || blockNumber < latestSavedBlockNumber;
6899
+ if (didReorgHappened) await dbTx.blocks.handleReorg({
6900
+ chainId: client.chain.id,
6901
+ blockNumber,
6902
+ epoch: epoch + 1n
6903
+ });
6904
+ else await dbTx.blocks.advanceChain({
6905
+ chainId: client.chain.id,
6906
+ blockNumber,
6907
+ epoch
6908
+ });
6909
+ });
6910
+ return isMaxBlockNumberReached;
6911
+ } };
6912
+ }
6913
+ const commonAncestor = (block, unfinalizedBlocks) => {
6914
+ const parent = unfinalizedBlocks.find((b) => b.hash === block.parentHash);
6915
+ if (parent) return parent;
6916
+ return null;
6917
+ };
6918
+ const fetchFinalizedBlock = async (parameters) => {
6919
+ let { tick, client, logger, collector, unfinalizedBlocks, previousFinalizedBlock } = parameters;
6920
+ let finalizedBlock = previousFinalizedBlock;
6921
+ if (tick % 20 === 0 || previousFinalizedBlock === null) {
6922
+ finalizedBlock = await client.getBlock({
6923
+ blockTag: "finalized",
6924
+ includeTransactions: false
6925
+ });
6926
+ if (finalizedBlock === null || finalizedBlock.number === null) {
6927
+ const msg = "Failed to get finalized block";
6928
+ logger.fatal({
6929
+ collector,
6930
+ chainId: client.chain.id,
6931
+ msg
6932
+ });
6933
+ throw new Error(msg);
6934
+ }
6935
+ unfinalizedBlocks = unfinalizedBlocks.filter((b) => b.number >= finalizedBlock.number);
6936
+ }
6937
+ if (finalizedBlock.number === null || finalizedBlock.hash === null || finalizedBlock.parentHash === null) {
6938
+ const msg = "Failed to get finalized block";
6939
+ logger.fatal({
6940
+ collector,
6941
+ chainId: client.chain.id,
6942
+ msg
6943
+ });
6944
+ throw new Error(msg);
6945
+ }
6946
+ return {
6947
+ hash: finalizedBlock.hash,
6948
+ number: finalizedBlock.number,
6949
+ parentHash: finalizedBlock.parentHash
6950
+ };
6951
+ };
6952
+ const reconcile = async (parameters) => {
6953
+ let { client, block, unfinalizedBlocks, finalizedBlock, logger, collector, maxBatchSize } = parameters;
6954
+ const chain = client.chain;
6955
+ if (block.hash === null || block.number === null || block.parentHash === null) throw new Error("Failed to get block");
6956
+ const latestBlock = unfinalizedBlocks[unfinalizedBlocks.length - 1];
6957
+ if (latestBlock === void 0) {
6958
+ const newBlock = {
6959
+ hash: block.hash,
6960
+ number: block.number,
6961
+ parentHash: block.parentHash
6962
+ };
6963
+ unfinalizedBlocks.push(newBlock);
6964
+ return {
6965
+ block: newBlock,
6966
+ didReorgHappened: false,
6967
+ unfinalizedBlocks
6968
+ };
6969
+ }
6970
+ if (latestBlock.hash === block.hash) return {
6971
+ block: latestBlock,
6972
+ didReorgHappened: false,
6973
+ unfinalizedBlocks
6974
+ };
6975
+ if (latestBlock.number >= block.number) {
6976
+ const ancestor = commonAncestor(block, unfinalizedBlocks) || finalizedBlock;
6977
+ logger.info({
6978
+ msg: `Reorg detected, latestBlock.number >= block.number`,
6979
+ collector,
6980
+ chain_id: chain.id,
6981
+ ancestor: ancestor.number,
6982
+ latest_block_number: latestBlock.number,
6983
+ block_number: block.number
6984
+ });
6985
+ unfinalizedBlocks = unfinalizedBlocks.filter((b) => b.number <= ancestor.number);
6986
+ return {
6987
+ block: ancestor,
6988
+ didReorgHappened: true,
6989
+ unfinalizedBlocks
6990
+ };
6991
+ }
6992
+ if (latestBlock.number + 1n < block.number) {
6993
+ logger.debug({
6994
+ collector,
6995
+ chain_id: chain.id,
6996
+ block_range: [latestBlock.number, block.number],
6997
+ msg: `Missing blocks`
6998
+ });
6999
+ const missingBlockNumbers = (() => {
7000
+ const missingBlockNumbers = [];
7001
+ let start = latestBlock.number + 1n;
7002
+ const threshold = latestBlock.number + BigInt(maxBatchSize) > block.number ? block.number : latestBlock.number + BigInt(maxBatchSize);
7003
+ while (start < threshold) {
7004
+ missingBlockNumbers.push(start);
7005
+ start = start + 1n;
7006
+ }
7007
+ return missingBlockNumbers;
7008
+ })();
7009
+ const missingBlocks = await Promise.all(missingBlockNumbers.map((blockNumber) => retry(async () => await client.getBlock({
7010
+ blockNumber,
7011
+ includeTransactions: false
7012
+ }))));
7013
+ for (const missingBlock of missingBlocks) {
7014
+ const { block: returnedBlock, didReorgHappened, unfinalizedBlocks: newUnfinalizedBlocks } = await reconcile({
7015
+ client,
7016
+ block: missingBlock,
7017
+ unfinalizedBlocks,
7018
+ finalizedBlock,
7019
+ logger,
7020
+ collector,
7021
+ maxBatchSize
7022
+ });
7023
+ if (returnedBlock.number !== missingBlock.number) return {
7024
+ block: returnedBlock,
7025
+ didReorgHappened,
7026
+ unfinalizedBlocks: newUnfinalizedBlocks
7027
+ };
7028
+ }
7029
+ return reconcile({
7030
+ client,
7031
+ block,
7032
+ unfinalizedBlocks,
7033
+ finalizedBlock,
7034
+ logger,
7035
+ collector,
7036
+ maxBatchSize
7037
+ });
7038
+ }
7039
+ if (block.parentHash !== latestBlock.hash) {
7040
+ const ancestor = commonAncestor(block, unfinalizedBlocks) || finalizedBlock;
7041
+ logger.info({
7042
+ msg: `Reorg detected, block parent hash !== latest block hash`,
7043
+ collector,
7044
+ chain_id: chain.id,
7045
+ ancestor: ancestor.number,
7046
+ latest_block_number: latestBlock.number,
7047
+ block_number: block.number
7048
+ });
7049
+ unfinalizedBlocks = unfinalizedBlocks.filter((b) => b.number <= ancestor.number);
7050
+ return {
7051
+ block: ancestor,
7052
+ didReorgHappened: true,
7053
+ unfinalizedBlocks
7054
+ };
7055
+ }
7056
+ const newBlock = {
7057
+ hash: block.hash,
7058
+ number: block.number,
7059
+ parentHash: block.parentHash
7060
+ };
7061
+ unfinalizedBlocks.push(newBlock);
7062
+ return {
7063
+ block: newBlock,
7064
+ didReorgHappened: false,
7065
+ unfinalizedBlocks
7066
+ };
7067
+ };
7068
+
7069
+ //#endregion
7070
+ //#region src/indexer/collectors/Collector.ts
7071
+ const names = [
7072
+ "offers",
7073
+ "consumed_events",
7074
+ "positions",
7075
+ "prices"
7076
+ ];
7077
+ function create$16({ name, collect, client, db, options }) {
7078
+ const admin = create$17({
7079
+ client,
7080
+ db,
7081
+ options
7082
+ });
7083
+ return {
7084
+ name,
7085
+ chain: client.chain,
7086
+ client,
7087
+ db,
7088
+ interval: options.interval ?? 1e4,
7089
+ collect: async function* () {
7090
+ const collector = name;
7091
+ const chain = client.chain;
7092
+ const logger = getLogger();
7093
+ const collectorId = `${client.chain.id.toString()}.collector.${collector}`;
7094
+ const tracer = getTracer(`router.${collectorId}`);
7095
+ logger.info({
7096
+ msg: `Collector started`,
7097
+ collector,
7098
+ chain_id: chain.id
7099
+ });
7100
+ let iterator = null;
7101
+ let lastBlockNumber;
7102
+ while (true) try {
7103
+ if (iterator === null) iterator = await startActiveSpan(tracer, `${collectorId}.init`, async () => {
7104
+ const { collector: collectorBlock } = await db.blocks.init({
7105
+ collectorName: name,
7106
+ chainId: chain.id
7107
+ });
7108
+ lastBlockNumber = collectorBlock.blockNumber;
7109
+ return collect({
7110
+ client,
7111
+ collector: name,
7112
+ epoch: collectorBlock.epoch,
7113
+ lastBlockNumber,
7114
+ db
7115
+ });
7116
+ });
7117
+ if (await startActiveSpan(tracer, `${collectorId}.syncBlock`, async (span) => {
7118
+ const isMaxBlockNumberReached = await admin.syncBlock();
7119
+ span.setAttribute("collector.is_max_block_reached", isMaxBlockNumberReached);
7120
+ return isMaxBlockNumberReached;
7121
+ }) && options.maxBlockNumber !== void 0 && lastBlockNumber !== void 0 && options.maxBlockNumber === lastBlockNumber) return;
7122
+ const { blockNumber, done } = await startActiveSpan(tracer, `${collectorId}.next`, async () => {
7123
+ if (iterator === null) throw new Error("Iterator is not initialized");
7124
+ const { value: blockNumber, done } = await iterator.next();
7125
+ return {
7126
+ blockNumber,
7127
+ done
7128
+ };
7129
+ });
7130
+ if (done) iterator = null;
7131
+ else {
7132
+ lastBlockNumber = blockNumber;
7133
+ yield blockNumber;
7134
+ }
7135
+ } catch (err) {
7136
+ const isError = err instanceof Error;
7137
+ logger.error({
7138
+ msg: "Collector error",
7139
+ collector,
7140
+ chain_id: chain.id,
7141
+ error: isError ? err.message : String(err),
7142
+ stack: isError ? err.stack : void 0
7143
+ });
7144
+ }
7145
+ }
7146
+ };
7147
+ }
7148
+ /** Start a collector with its own polling cadence based on chain head lag.
7149
+ * @param collector - The collector to start.
7150
+ * @returns A function to stop the collector.
7151
+ */
7152
+ function start(collector) {
7153
+ let stopped = false;
7154
+ const it = collector.collect();
7155
+ const logger = getLogger();
7156
+ const collectorId = `${collector.chain.id.toString()}.collector.${collector.name}`;
7157
+ const tracer = getTracer(`router.${collectorId}`);
7158
+ const blocks = collector.db.blocks;
7159
+ let initialized = false;
7160
+ (async () => {
7161
+ while (!stopped) try {
7162
+ await startActiveSpan(tracer, `${collectorId}.poll`, async () => {
7163
+ if (!initialized) {
7164
+ await blocks.init({
7165
+ chainId: collector.chain.id,
7166
+ collectorName: collector.name
7167
+ });
7168
+ initialized = true;
7169
+ }
7170
+ const [block, { blockNumber: collectorBlockNumber }] = await Promise.all([collector.client.getBlock({
7171
+ blockTag: "latest",
7172
+ includeTransactions: false
7173
+ }), blocks.getCollector({
7174
+ collectorName: collector.name,
7175
+ chainId: collector.chain.id
7176
+ })]);
7177
+ const delay = Number(block.number) > collectorBlockNumber + 10 ? 250 : collector.interval;
7178
+ const waitSpan = tracer.startSpan(`${collectorId}.wait`);
7179
+ try {
7180
+ await wait(delay);
7181
+ } finally {
7182
+ waitSpan.end();
7183
+ }
7184
+ await it.next();
7185
+ });
7186
+ } catch (err) {
7187
+ const error = err instanceof Error ? err : new Error(String(err));
7188
+ logger.error({
7189
+ msg: "Collector polling error",
7190
+ collector: collector.name,
7191
+ chain_id: collector.chain.id,
7192
+ err: error
7193
+ });
7194
+ }
7195
+ await it.return();
7196
+ })();
7197
+ return () => {
7198
+ stopped = true;
7199
+ };
7200
+ }
7201
+
7277
7202
  //#endregion
7278
7203
  //#region src/database/domains/Blocks.ts
7279
7204
  /** Postgres implementation. */
@@ -7705,35 +7630,15 @@ async function _getOffers(db, params) {
7705
7630
  AND LOWER(pc.contract) = LOWER(c.position_contract)
7706
7631
  AND LOWER(pc."user") = LOWER(c.position_user)
7707
7632
  ),
7708
- -- Compute contribution per callback in loan terms (with oracle price via LEFT JOIN)
7633
+ -- Compute contribution per callback in loan terms (loan token only collateral positions are not indexed)
7709
7634
  callback_loan_contribution AS (
7710
7635
  SELECT
7711
7636
  cc.*,
7712
7637
  CASE
7713
- -- No lot exists: contribution is 0
7714
7638
  WHEN cc.lot_lower IS NULL THEN 0
7715
- -- Loan token position: use lot_balance directly, apply callback limit
7716
- WHEN LOWER(cc.position_asset) = LOWER(cc.loan_token) THEN
7717
- LEAST(
7718
- cc.lot_balance,
7719
- COALESCE(cc.callback_amount::numeric, cc.lot_balance)
7720
- )
7721
- -- Collateral position: convert to loan using (amount * price / 10^36) * lltv / 10^18
7722
- ELSE
7723
- (
7724
- LEAST(
7725
- cc.lot_balance,
7726
- COALESCE(cc.callback_amount::numeric, cc.lot_balance)
7727
- ) * COALESCE(collat_oracle.price::numeric, 0) / 1e36
7728
- ) * COALESCE(collat_info.lltv::numeric, 0) / 1e18
7639
+ ELSE LEAST(cc.lot_balance, COALESCE(cc.callback_amount::numeric, cc.lot_balance))
7729
7640
  END AS contribution_in_loan
7730
7641
  FROM callback_contributions cc
7731
- LEFT JOIN ${obligationCollateralsV2} collat_info
7732
- ON collat_info.obligation_id = cc.obligation_id
7733
- AND LOWER(collat_info.asset) = LOWER(cc.position_asset)
7734
- LEFT JOIN ${oracles} collat_oracle
7735
- ON collat_oracle.chain_id = collat_info.oracle_chain_id
7736
- AND LOWER(collat_oracle.address) = LOWER(collat_info.oracle_address)
7737
7642
  ),
7738
7643
  -- Aggregate contributions per offer, deduplicating by position using DISTINCT ON
7739
7644
  offer_contributions AS (
@@ -7770,6 +7675,22 @@ async function _getOffers(db, params) {
7770
7675
  GROUP BY hash, obligation_id, assets, price, obligation_units, obligation_shares, maturity, expiry, start, group_group, buy,
7771
7676
  callback_address, callback_data, block_number, group_chain_id, group_maker,
7772
7677
  consumed, chain_id, loan_token, session
7678
+ UNION ALL
7679
+ -- Sell offers without callbacks: collateral positions not indexed, takeable = assets - consumed
7680
+ SELECT
7681
+ p.hash, p.obligation_id, p.assets, p.price,
7682
+ p.obligation_units, p.obligation_shares,
7683
+ p.maturity, p.expiry, p.start, p.group_group,
7684
+ p.buy, p.callback_address, p.callback_data,
7685
+ p.block_number, p.group_chain_id, p.group_maker,
7686
+ p.consumed, p.chain_id, p.loan_token, p.session,
7687
+ 0 AS total_available
7688
+ FROM paged p
7689
+ WHERE p.buy = false
7690
+ AND NOT EXISTS (
7691
+ SELECT 1 FROM ${offersCallbacks} oc2
7692
+ WHERE oc2.offer_hash = p.hash
7693
+ )
7773
7694
  )
7774
7695
  -- Final SELECT with inline takeable computation
7775
7696
  SELECT
@@ -7792,18 +7713,24 @@ async function _getOffers(db, params) {
7792
7713
  oc.block_number,
7793
7714
  oc.session,
7794
7715
  COALESCE(oc.total_available, 0) AS available,
7795
- -- takeable = min(assets - consumed, total_available)
7796
- GREATEST(0, LEAST(
7797
- oc.assets::numeric - oc.consumed::numeric,
7798
- COALESCE(oc.total_available, 0)
7799
- )) AS takeable,
7716
+ -- takeable: sell offers use assets - consumed directly (collateral positions not indexed yet)
7717
+ CASE WHEN oc.buy = false
7718
+ THEN GREATEST(0, oc.assets::numeric - oc.consumed::numeric)
7719
+ ELSE GREATEST(0, LEAST(
7720
+ oc.assets::numeric - oc.consumed::numeric,
7721
+ COALESCE(oc.total_available, 0)
7722
+ ))
7723
+ END AS takeable,
7800
7724
  c.collaterals
7801
7725
  FROM offer_contributions oc
7802
7726
  LEFT JOIN collats c ON c.obligation_id = oc.obligation_id
7803
- WHERE GREATEST(0, LEAST(
7804
- oc.assets::numeric - oc.consumed::numeric,
7805
- COALESCE(oc.total_available, 0)
7806
- )) > 0
7727
+ WHERE CASE WHEN oc.buy = false
7728
+ THEN GREATEST(0, oc.assets::numeric - oc.consumed::numeric)
7729
+ ELSE GREATEST(0, LEAST(
7730
+ oc.assets::numeric - oc.consumed::numeric,
7731
+ COALESCE(oc.total_available, 0)
7732
+ ))
7733
+ END > 0
7807
7734
  ORDER BY
7808
7735
  oc.price::numeric ${priceSortDirection === "asc" ? sql`ASC` : sql`DESC`},
7809
7736
  oc.block_number ASC,
@@ -9682,12 +9609,34 @@ async function getBook(params, db) {
9682
9609
  if (!result.success) return failure(result.error);
9683
9610
  const query = result.data;
9684
9611
  try {
9612
+ logger.debug({
9613
+ service: "api_controller",
9614
+ endpoint: "get_book",
9615
+ msg: "Loading book levels",
9616
+ obligation_id: query.obligation_id,
9617
+ side: query.side,
9618
+ limit: query.limit ?? null,
9619
+ has_cursor: query.cursor != null
9620
+ });
9685
9621
  const { levels, nextCursor } = await db.book.get({
9686
9622
  side: query.side,
9687
9623
  obligationId: query.obligation_id,
9688
9624
  cursor: query.cursor,
9689
9625
  limit: query.limit
9690
9626
  });
9627
+ const firstLevel = levels[0];
9628
+ logger.debug({
9629
+ service: "api_controller",
9630
+ endpoint: "get_book",
9631
+ msg: "Loaded book levels",
9632
+ obligation_id: query.obligation_id,
9633
+ side: query.side,
9634
+ levels_count: levels.length,
9635
+ has_next_cursor: nextCursor != null,
9636
+ first_level_price: firstLevel?.price.toString() ?? null,
9637
+ first_level_assets: firstLevel?.assets.toString() ?? null,
9638
+ first_level_count: firstLevel?.count ?? null
9639
+ });
9691
9640
  return success({
9692
9641
  data: levels.map(from$6),
9693
9642
  cursor: nextCursor
@@ -10177,6 +10126,7 @@ async function getOffersQuery(db, parameters) {
10177
10126
  if (cursor !== null && cursor !== void 0) {
10178
10127
  if (!cursor.startsWith("0x") || cursor.length !== 66) throw new Error("Invalid cursor format");
10179
10128
  }
10129
+ const now = Math.floor((Date.now() - 1) / 1e3);
10180
10130
  const collateralsLateral = db.select({ collaterals: sql`COALESCE(
10181
10131
  jsonb_agg(
10182
10132
  jsonb_build_object(
@@ -10188,29 +10138,57 @@ async function getOffersQuery(db, parameters) {
10188
10138
  '[]'::jsonb
10189
10139
  )`.as("collaterals") }).from(obligationCollateralsV2).innerJoin(oracles, sql`${obligationCollateralsV2.oracleChainId} = ${oracles.chainId}
10190
10140
  AND ${obligationCollateralsV2.oracleAddress} = ${oracles.address}`).where(eq(obligationCollateralsV2.obligationId, offers.obligationId)).as("collaterals_lateral");
10191
- const availableLateral = db.select({ available: sql`COALESCE(SUM(
10192
- CASE
10193
- -- If asset is null, position available is 0
10194
- WHEN ${positions.asset} IS NULL THEN 0
10195
-
10196
- -- Position asset matches loan token: no conversion needed
10197
- WHEN ${positions.asset} = ${obligations.loanToken} THEN
10198
- CASE
10199
- WHEN ${callbacks.amount} IS NULL THEN COALESCE(${positions.balance}, 0)::numeric
10200
- ELSE LEAST(${callbacks.amount}::numeric, COALESCE(${positions.balance}, 0)::numeric)
10201
- END
10202
-
10203
- -- Position asset is collateral: apply oracle price * lltv
10204
- -- Formula: balance * price / 1e36 * lltv / 1e18
10205
- ELSE
10206
- (CASE
10207
- WHEN ${callbacks.amount} IS NULL THEN COALESCE(${positions.balance}, 0)::numeric
10208
- ELSE LEAST(${callbacks.amount}::numeric, COALESCE(${positions.balance}, 0)::numeric)
10209
- END)
10210
- * COALESCE(${oracles.price}, 0)::numeric / 1e36
10211
- * COALESCE(${obligationCollateralsV2.lltv}, 0)::numeric / 1e18
10212
- END
10213
- ), 0)`.as("available") }).from(offersCallbacks).innerJoin(callbacks, eq(offersCallbacks.callbackId, callbacks.id)).innerJoin(positions, and(eq(callbacks.positionChainId, positions.chainId), eq(callbacks.positionContract, positions.contract), eq(callbacks.positionUser, positions.user))).leftJoin(obligationCollateralsV2, and(eq(obligationCollateralsV2.obligationId, offers.obligationId), eq(obligationCollateralsV2.asset, positions.asset))).leftJoin(oracles, and(eq(oracles.chainId, obligationCollateralsV2.oracleChainId), eq(oracles.address, obligationCollateralsV2.oracleAddress))).where(eq(offersCallbacks.offerHash, offers.hash)).as("available_lateral");
10141
+ const lotBalanceExpr = sql`GREATEST(0, LEAST(
10142
+ COALESCE(${positions.balance}, 0)::numeric
10143
+ + COALESCE((
10144
+ SELECT SUM(${offsets.value}::numeric)
10145
+ FROM ${offsets}
10146
+ WHERE ${offsets.chainId} = ${callbacks.positionChainId}
10147
+ AND LOWER(${offsets.contract}) = LOWER(${callbacks.positionContract})
10148
+ AND LOWER(${offsets.user}) = LOWER(${callbacks.positionUser})
10149
+ ), 0)
10150
+ - COALESCE(${lots.lower}::numeric, 0),
10151
+ (COALESCE(${lots.upper}::numeric, 0) - COALESCE(${lots.lower}::numeric, 0))
10152
+ - CASE
10153
+ WHEN ${offers.assets}::numeric > 0
10154
+ THEN COALESCE(${groups.consumed}::numeric, 0)
10155
+ * (COALESCE(${lots.upper}::numeric, 0) - COALESCE(${lots.lower}::numeric, 0))
10156
+ / ${offers.assets}::numeric
10157
+ ELSE 0
10158
+ END
10159
+ ))`;
10160
+ const contributionExpr = sql`CASE
10161
+ WHEN ${positions.asset} IS NULL OR ${lots.lower} IS NULL THEN 0
10162
+ ELSE LEAST(COALESCE(${callbacks.amount}::numeric, ${lotBalanceExpr}), ${lotBalanceExpr})
10163
+ END`;
10164
+ const availableExpr = sql`COALESCE((
10165
+ SELECT SUM(deduped.contribution)
10166
+ FROM (
10167
+ SELECT DISTINCT ON (
10168
+ ${callbacks.positionChainId},
10169
+ LOWER(${callbacks.positionContract}),
10170
+ LOWER(${callbacks.positionUser})
10171
+ )
10172
+ ${contributionExpr} AS contribution
10173
+ FROM ${offersCallbacks}
10174
+ INNER JOIN ${callbacks} ON ${offersCallbacks.callbackId} = ${callbacks.id}
10175
+ LEFT JOIN ${positions}
10176
+ ON ${positions.chainId} = ${callbacks.positionChainId}
10177
+ AND LOWER(${positions.contract}) = LOWER(${callbacks.positionContract})
10178
+ AND LOWER(${positions.user}) = LOWER(${callbacks.positionUser})
10179
+ LEFT JOIN ${lots}
10180
+ ON ${lots.chainId} = ${callbacks.positionChainId}
10181
+ AND LOWER(${lots.contract}) = LOWER(${callbacks.positionContract})
10182
+ AND LOWER(${lots.user}) = LOWER(${callbacks.positionUser})
10183
+ AND LOWER(${lots.group}) = LOWER(${offers.group})
10184
+ WHERE ${offersCallbacks.offerHash} = ${offers.hash}
10185
+ ORDER BY
10186
+ ${callbacks.positionChainId},
10187
+ LOWER(${callbacks.positionContract}),
10188
+ LOWER(${callbacks.positionUser}),
10189
+ ${contributionExpr} DESC
10190
+ ) deduped
10191
+ ), 0)`;
10214
10192
  const rows = (await db.select({
10215
10193
  hash: offers.hash,
10216
10194
  maker: offers.groupMaker,
@@ -10231,18 +10209,25 @@ async function getOffersQuery(db, parameters) {
10231
10209
  callbackData: offers.callbackData,
10232
10210
  collaterals: collateralsLateral.collaterals,
10233
10211
  blockNumber: offers.blockNumber,
10234
- available: sql`COALESCE(${availableLateral.available}::numeric, 0)`.as("available"),
10235
- takeable: sql`FLOOR(GREATEST(
10236
- 0,
10237
- LEAST(
10238
- ${offers.assets}::numeric - ${groups.consumed}::numeric,
10239
- COALESCE(${availableLateral.available}::numeric, 0)
10240
- )
10212
+ available: sql`${availableExpr}::numeric`.as("available"),
10213
+ takeable: sql`FLOOR(GREATEST(0,
10214
+ CASE WHEN ${offers.buy} = false
10215
+ THEN ${offers.assets}::numeric - ${groups.consumed}::numeric
10216
+ ELSE LEAST(
10217
+ ${offers.assets}::numeric - ${groups.consumed}::numeric,
10218
+ ${availableExpr}::numeric
10219
+ )
10220
+ END
10241
10221
  ))`.as("takeable")
10242
- }).from(offers).innerJoin(obligations, eq(offers.obligationId, obligations.obligationId)).innerJoin(groups, and(eq(offers.groupChainId, groups.chainId), eq(offers.groupMaker, groups.maker), eq(offers.group, groups.group))).innerJoinLateral(collateralsLateral, sql`true`).leftJoinLateral(availableLateral, sql`true`).where(and(cursor !== null && cursor !== void 0 ? gt(offers.hash, cursor) : void 0, maker !== void 0 ? eq(offers.groupMaker, maker.toLowerCase()) : void 0, maker === void 0 ? sql`GREATEST(0, LEAST(
10243
- ${offers.assets}::numeric - ${groups.consumed}::numeric,
10244
- COALESCE(${availableLateral.available}::numeric, 0)
10245
- )) > 0` : void 0)).orderBy(asc(offers.hash)).limit(limit)).map((row) => {
10222
+ }).from(offers).innerJoin(obligations, eq(offers.obligationId, obligations.obligationId)).innerJoin(groups, and(eq(offers.groupChainId, groups.chainId), eq(offers.groupMaker, groups.maker), eq(offers.group, groups.group))).innerJoinLateral(collateralsLateral, sql`true`).where(and(cursor !== null && cursor !== void 0 ? gt(offers.hash, cursor) : void 0, maker !== void 0 ? eq(offers.groupMaker, maker.toLowerCase()) : void 0, gte(offers.expiry, now), gte(offers.maturity, now), maker === void 0 ? sql`GREATEST(0,
10223
+ CASE WHEN ${offers.buy} = false
10224
+ THEN ${offers.assets}::numeric - ${groups.consumed}::numeric
10225
+ ELSE LEAST(
10226
+ ${offers.assets}::numeric - ${groups.consumed}::numeric,
10227
+ ${availableExpr}::numeric
10228
+ )
10229
+ END
10230
+ ) > 0` : void 0)).orderBy(asc(offers.hash)).limit(limit)).map((row) => {
10246
10231
  return {
10247
10232
  hash: row.hash,
10248
10233
  maker: row.maker,
@@ -10352,35 +10337,6 @@ async function getUserPositions(queryParameters, db) {
10352
10337
  }
10353
10338
  }
10354
10339
 
10355
- //#endregion
10356
- //#region src/api/Controllers/resolveCallbackTypes.ts
10357
- /**
10358
- * Resolve callback types for a list of callback addresses grouped by chain.
10359
- * @param body - Request body with callback addresses. {@link CallbackTypesRequest}
10360
- * @param chains - Chains to resolve callback types against. {@link Chain.Chain}
10361
- * @returns Callback types grouped by chain. {@link CallbackTypesPayload}
10362
- */
10363
- async function resolveCallbackTypes$1(body, chains) {
10364
- const result = safeParse("callback_types", body, (issue) => issue.message);
10365
- if (!result.success) return failure(result.error);
10366
- const request = result.data;
10367
- const chainIds = new Set(chains.map((chain) => chain.id));
10368
- const unknown = request.callbacks.find((entry) => !chainIds.has(entry.chain_id));
10369
- if (unknown) return failure(new BadRequestError(`Unknown chain id ${unknown.chain_id}`));
10370
- try {
10371
- const data = resolveCallbackTypes$2({
10372
- chains,
10373
- request
10374
- });
10375
- return success({
10376
- data,
10377
- cursor: null
10378
- });
10379
- } catch (err) {
10380
- return failure(err);
10381
- }
10382
- }
10383
-
10384
10340
  //#endregion
10385
10341
  //#region src/api/Api.ts
10386
10342
  function from(config) {
@@ -10450,24 +10406,9 @@ function serve$1(parameters) {
10450
10406
  const { statusCode, body } = await gatekeeper.validate(reqBody);
10451
10407
  return c.json(body, statusCode);
10452
10408
  } catch (err) {
10453
- const failure$1 = failure(err);
10454
- return c.json(failure$1.body, failure$1.statusCode);
10455
- }
10456
- });
10457
- app.post("/v1/callbacks", async (c) => {
10458
- let body;
10459
- try {
10460
- body = await c.req.json();
10461
- } catch (err) {
10462
- const failure$3 = failure(err);
10463
- return c.json(failure$3.body, failure$3.statusCode);
10464
- }
10465
- if (body === null || typeof body !== "object") {
10466
- const failure$2 = failure(new BadRequestError("Request body must be a JSON object"));
10409
+ const failure$2 = failure(err);
10467
10410
  return c.json(failure$2.body, failure$2.statusCode);
10468
10411
  }
10469
- const { statusCode, body: responseBody } = await resolveCallbackTypes$1(body, chainRegistry.list());
10470
- return c.json(responseBody, statusCode);
10471
10412
  });
10472
10413
  app.get("/v1/users/:userAddress/positions", async (c) => {
10473
10414
  const query = c.req.query();
@@ -10499,8 +10440,8 @@ function serve$1(parameters) {
10499
10440
  const { statusCode, body } = await gatekeeper.getConfigRules(c.req.query());
10500
10441
  return c.json(body, statusCode);
10501
10442
  } catch (err) {
10502
- const failure$4 = failure(err);
10503
- return c.json(failure$4.body, failure$4.statusCode);
10443
+ const failure$1 = failure(err);
10444
+ return c.json(failure$1.body, failure$1.statusCode);
10504
10445
  }
10505
10446
  });
10506
10447
  app.get("/docs/openapi", async (c) => c.text(JSON.stringify(await getSwaggerJson()), 200, { "Content-Type": "application/json; charset=utf-8" }));
@@ -10591,23 +10532,11 @@ function createHttpClient(config) {
10591
10532
  issues: []
10592
10533
  };
10593
10534
  };
10594
- const getCallbackTypes = async (requestPayload) => {
10595
- const response = await request("/v1/callbacks", {
10596
- method: "POST",
10597
- headers: { "content-type": "application/json" },
10598
- body: JSON.stringify(requestPayload)
10599
- });
10600
- const json = await response.json();
10601
- if (!response.ok) throw new Error(`Gatekeeper callbacks request failed: ${extractErrorMessage(json) ?? response.statusText}`);
10602
- if (!("data" in json) || !Array.isArray(json.data)) throw new Error("Gatekeeper callbacks response is invalid.");
10603
- return json.data;
10604
- };
10605
10535
  return {
10606
10536
  baseUrl,
10607
10537
  validate,
10608
10538
  getConfigRules,
10609
- isAllowed,
10610
- getCallbackTypes
10539
+ isAllowed
10611
10540
  };
10612
10541
  }
10613
10542
  function mergeHeaders(base, extra) {
@@ -10843,35 +10772,17 @@ function createMockOffers(parameters) {
10843
10772
  lltv
10844
10773
  }));
10845
10774
  if (!chainRegistry.getById(offer.chainId)) throw new Error(`Missing chain config for id ${offer.chainId}`);
10846
- const callbackType = buy ? Type$1.BuyVaultV1Callback : Type$1.SellERC20Callback;
10847
- const callbackAddress = buy ? BUY_CALLBACK_ADDRESS : SELL_CALLBACK_ADDRESS;
10848
- const callbackData = buildMockCallbackData(callbackType, {
10849
- ...offer,
10850
- collaterals
10851
- });
10852
10775
  return from$12({
10853
10776
  ...offer,
10854
10777
  loanToken,
10855
10778
  collaterals,
10856
10779
  callback: {
10857
- address: callbackAddress,
10858
- data: callbackData
10780
+ address: zeroAddress,
10781
+ data: "0x"
10859
10782
  }
10860
10783
  });
10861
10784
  });
10862
10785
  }
10863
- function buildMockCallbackData(callbackType, offer) {
10864
- const assets = offer.collaterals.map((collateral) => collateral.asset);
10865
- const amounts = offer.collaterals.map(() => offer.assets);
10866
- if (callbackType === Type$1.BuyVaultV1Callback) return encodeBuyVaultV1Callback({
10867
- vaults: assets,
10868
- amounts
10869
- });
10870
- return encodeSellERC20Callback({
10871
- collaterals: assets,
10872
- amounts
10873
- });
10874
- }
10875
10786
  function seedMockOracles(offers, price, blockNumber) {
10876
10787
  const oracleMap = /* @__PURE__ */ new Map();
10877
10788
  for (const offer of offers) for (const collateral of offer.collaterals) {
@@ -10886,8 +10797,6 @@ function seedMockOracles(offers, price, blockNumber) {
10886
10797
  }
10887
10798
  return Array.from(oracleMap.values());
10888
10799
  }
10889
- const BUY_CALLBACK_ADDRESS = "0x3333333333333333333333333333333333333333";
10890
- const SELL_CALLBACK_ADDRESS = "0x1111111111111111111111111111111111111111";
10891
10800
  async function seedOffers(parameters) {
10892
10801
  const { db, offers, blockNumber } = parameters;
10893
10802
  if (offers.length === 0) return;
@@ -10904,14 +10813,10 @@ async function seedOffers(parameters) {
10904
10813
  }]);
10905
10814
  }
10906
10815
  async function seedOfferAssociations(parameters) {
10907
- const { db, gatekeeper, offers, blockNumber } = parameters;
10816
+ const { db, offers, blockNumber } = parameters;
10908
10817
  if (offers.length === 0) return;
10909
10818
  const { callbacks, positions, lots } = buildOfferAssociationsFromOffers({
10910
10819
  offers,
10911
- callbackTypes: await resolveCallbackTypes({
10912
- gatekeeper,
10913
- offers
10914
- }),
10915
10820
  blockNumber
10916
10821
  });
10917
10822
  if (positions.length > 0) await db.positions.upsert(positions);
@@ -10954,83 +10859,40 @@ function buildOfferDependencies(parameters) {
10954
10859
  groups: Array.from(groupsByKey.values())
10955
10860
  };
10956
10861
  }
10957
- async function resolveCallbackTypes(parameters) {
10958
- const { gatekeeper, offers } = parameters;
10959
- const addressesByChain = /* @__PURE__ */ new Map();
10960
- for (const offer of offers) {
10961
- if (offer.callback.data === "0x") continue;
10962
- const set = addressesByChain.get(offer.chainId) ?? /* @__PURE__ */ new Set();
10963
- set.add(offer.callback.address.toLowerCase());
10964
- addressesByChain.set(offer.chainId, set);
10965
- }
10966
- if (addressesByChain.size === 0) return /* @__PURE__ */ new Map();
10967
- const request = { callbacks: Array.from(addressesByChain.entries()).map(([chainId, addresses]) => ({
10968
- chain_id: chainId,
10969
- addresses: Array.from(addresses)
10970
- })) };
10971
- const response = await gatekeeper.getCallbackTypes(request);
10972
- const typeByAddress = /* @__PURE__ */ new Map();
10973
- for (const entry of response) {
10974
- const chainId = entry.chain_id;
10975
- for (const [key, list] of Object.entries(entry)) {
10976
- if (key === "chain_id" || key === "not_supported") continue;
10977
- if (!Array.isArray(list)) continue;
10978
- for (const address of list) {
10979
- const mapKey = `${chainId}-${address.toLowerCase()}`;
10980
- typeByAddress.set(mapKey, key);
10981
- }
10982
- }
10983
- }
10984
- return typeByAddress;
10985
- }
10986
10862
  function buildOfferAssociationsFromOffers(parameters) {
10987
- const { offers, callbackTypes, blockNumber } = parameters;
10863
+ const { offers, blockNumber } = parameters;
10988
10864
  const callbacks = [];
10989
10865
  const positions = [];
10990
10866
  const lots = [];
10991
10867
  for (const offer of offers) {
10992
- if (offer.callback.data === "0x") continue;
10993
- const key = `${offer.chainId}-${offer.callback.address.toLowerCase()}`;
10994
- const callbackType = callbackTypes.get(key);
10995
- if (!callbackType) continue;
10996
- let decoded;
10997
- try {
10998
- decoded = decode$1(callbackType, offer.callback.data);
10999
- } catch (err) {
11000
- const error = err instanceof Error ? err : new Error(String(err));
11001
- throw new Error("Failed to decode callback data", { cause: error });
11002
- }
11003
- if (decoded.length === 0) continue;
10868
+ if (!offer.buy) continue;
10869
+ const loanToken = offer.loanToken.toLowerCase();
10870
+ const offerHash = hash(offer);
10871
+ positions.push(from$10({
10872
+ chainId: offer.chainId,
10873
+ contract: loanToken,
10874
+ user: offer.maker,
10875
+ type: Type.ERC20,
10876
+ asset: loanToken,
10877
+ balance: offer.assets,
10878
+ blockNumber
10879
+ }));
11004
10880
  callbacks.push({
11005
- offerHash: hash(offer),
11006
- callbacks: decoded.map((callback) => ({
10881
+ offerHash,
10882
+ callbacks: [{
11007
10883
  chainId: offer.chainId,
11008
- contract: callback.contract,
10884
+ contract: loanToken,
11009
10885
  user: offer.maker,
11010
- amount: callback.amount
11011
- }))
10886
+ amount: offer.assets
10887
+ }]
10888
+ });
10889
+ lots.push({
10890
+ positionChainId: offer.chainId,
10891
+ positionContract: loanToken,
10892
+ positionUser: offer.maker,
10893
+ group: offer.group,
10894
+ size: offer.assets
11012
10895
  });
11013
- for (const callback of decoded) {
11014
- const positionType = callbackType === Type$1.BuyVaultV1Callback ? Type.VAULT_V1 : Type.ERC20;
11015
- const positionAsset = callbackType === Type$1.BuyVaultV1Callback ? offer.loanToken : callback.contract;
11016
- positions.push(from$10({
11017
- chainId: offer.chainId,
11018
- contract: callback.contract,
11019
- user: offer.maker,
11020
- type: positionType,
11021
- balance: callback.amount * 2n,
11022
- asset: positionAsset,
11023
- blockNumber
11024
- }));
11025
- const isLoanPosition = positionAsset.toLowerCase() === offer.loanToken.toLowerCase();
11026
- lots.push({
11027
- positionChainId: offer.chainId,
11028
- positionContract: callback.contract,
11029
- positionUser: offer.maker,
11030
- group: offer.group,
11031
- size: isLoanPosition ? offer.assets : callback.amount
11032
- });
11033
- }
11034
10896
  }
11035
10897
  return {
11036
10898
  callbacks,