@morpho-dev/router 0.7.0 → 0.7.1

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.1";
156
156
  var description = "Router package for Morpho protocol";
157
157
 
158
158
  //#endregion
@@ -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
  };
@@ -2300,7 +2173,7 @@ function random(config) {
2300
2173
  const chain = config?.chains ? config.chains[int(config.chains.length)] : chains$2.ethereum;
2301
2174
  const loanToken = config?.loanTokens ? config.loanTokens[int(config.loanTokens.length)] : address();
2302
2175
  const collateralCandidates = config?.collateralTokens ? config.collateralTokens.filter((a) => a !== loanToken) : [address()];
2303
- const collateralAsset = collateralCandidates[int(collateralCandidates.length)];
2176
+ collateralCandidates[int(collateralCandidates.length)];
2304
2177
  const maturityOption = weightedChoice([["end_of_month", 1], ["end_of_next_month", 1]]);
2305
2178
  const maturity = config?.maturity ?? from$16(maturityOption);
2306
2179
  const lltv = from$15(weightedChoice([
@@ -2327,21 +2200,10 @@ function random(config) {
2327
2200
  const unit = BigInt(10) ** BigInt(loanTokenDecimals);
2328
2201
  const amountBase = BigInt(100 + int(999901));
2329
2202
  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
- })();
2203
+ const emptyCallback = {
2204
+ address: zeroAddress,
2205
+ data: "0x"
2206
+ };
2345
2207
  return from$12({
2346
2208
  maker: config?.maker ?? address(),
2347
2209
  assets: assetsScaled,
@@ -2360,7 +2222,7 @@ function random(config) {
2360
2222
  ...random$1(),
2361
2223
  lltv
2362
2224
  })).sort((a, b) => a.asset.localeCompare(b.asset)),
2363
- callback: config?.callback ?? callbackBySide
2225
+ callback: config?.callback ?? emptyCallback
2364
2226
  });
2365
2227
  }
2366
2228
  const weightedChoice = (pairs) => {
@@ -2514,6 +2376,94 @@ function obligationId(offer) {
2514
2376
  }));
2515
2377
  }
2516
2378
  /**
2379
+ * ABI for the Take event emitted by the Morpho V2 contract.
2380
+ */
2381
+ const takeEvent = {
2382
+ type: "event",
2383
+ name: "Take",
2384
+ inputs: [
2385
+ {
2386
+ name: "caller",
2387
+ type: "address",
2388
+ indexed: false,
2389
+ internalType: "address"
2390
+ },
2391
+ {
2392
+ name: "id",
2393
+ type: "bytes32",
2394
+ indexed: true,
2395
+ internalType: "bytes32"
2396
+ },
2397
+ {
2398
+ name: "maker",
2399
+ type: "address",
2400
+ indexed: true,
2401
+ internalType: "address"
2402
+ },
2403
+ {
2404
+ name: "taker",
2405
+ type: "address",
2406
+ indexed: true,
2407
+ internalType: "address"
2408
+ },
2409
+ {
2410
+ name: "offerIsBuy",
2411
+ type: "bool",
2412
+ indexed: false,
2413
+ internalType: "bool"
2414
+ },
2415
+ {
2416
+ name: "buyerAssets",
2417
+ type: "uint256",
2418
+ indexed: false,
2419
+ internalType: "uint256"
2420
+ },
2421
+ {
2422
+ name: "sellerAssets",
2423
+ type: "uint256",
2424
+ indexed: false,
2425
+ internalType: "uint256"
2426
+ },
2427
+ {
2428
+ name: "obligationUnits",
2429
+ type: "uint256",
2430
+ indexed: false,
2431
+ internalType: "uint256"
2432
+ },
2433
+ {
2434
+ name: "obligationShares",
2435
+ type: "uint256",
2436
+ indexed: false,
2437
+ internalType: "uint256"
2438
+ },
2439
+ {
2440
+ name: "buyerIsLender",
2441
+ type: "bool",
2442
+ indexed: false,
2443
+ internalType: "bool"
2444
+ },
2445
+ {
2446
+ name: "sellerIsBorrower",
2447
+ type: "bool",
2448
+ indexed: false,
2449
+ internalType: "bool"
2450
+ },
2451
+ {
2452
+ name: "group",
2453
+ type: "bytes32",
2454
+ indexed: false,
2455
+ internalType: "bytes32"
2456
+ },
2457
+ {
2458
+ name: "consumed",
2459
+ type: "uint256",
2460
+ indexed: false,
2461
+ internalType: "uint256"
2462
+ }
2463
+ ],
2464
+ anonymous: false
2465
+ };
2466
+ /**
2517
2467
  * ABI for the Consume event emitted by the Obligation contract.
2518
2468
  */
2519
2469
  const consumedEvent = {
@@ -2939,12 +2889,10 @@ const maturity = ({ maturities }) => single("maturity", `Validates that offer ma
2939
2889
  const allowedMaturities = maturities.map((m) => from$16(m));
2940
2890
  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
2891
  });
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
- }
2892
+ 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) => {
2893
+ if (!isEmptyCallback(offer)) return { message: "Non-empty callbacks are not supported." };
2894
+ if (isEmptyCallback(offer) && offer.buy && !callbacks.includes(Type$1.BuyWithEmptyCallback)) return { message: "Buy offers with empty callback not allowed." };
2895
+ if (isEmptyCallback(offer) && !offer.buy && !callbacks.includes(Type$1.SellWithEmptyCallback)) return { message: "Sell offers with empty callback not allowed." };
2948
2896
  });
2949
2897
  /**
2950
2898
  * A validation rule that checks if the offer's tokens are allowed for its chain.
@@ -3009,12 +2957,8 @@ const morphoRules = (chains) => {
3009
2957
  chains$1({ chains }),
3010
2958
  maturity({ maturities: [MaturityType.EndOfMonth, MaturityType.EndOfNextMonth] }),
3011
2959
  callback({
3012
- callbacks: [
3013
- Type$1.BuyWithEmptyCallback,
3014
- Type$1.BuyVaultV1Callback,
3015
- Type$1.SellERC20Callback
3016
- ],
3017
- allowedAddresses: chains.flatMap((c) => getCallbackAddresses(c.name))
2960
+ callbacks: [Type$1.BuyWithEmptyCallback, Type$1.SellWithEmptyCallback],
2961
+ allowedAddresses: []
3018
2962
  }),
3019
2963
  token({ assetsByChainId }),
3020
2964
  oracle({ oraclesByChainId })
@@ -3031,25 +2975,13 @@ const morphoRules = (chains) => {
3031
2975
  function buildConfigRules(chains) {
3032
2976
  const rules = [];
3033
2977
  for (const chain of chains) {
3034
- const config = configs[chain.name];
3035
- const maturities = config.maturities ?? [];
2978
+ const maturities = configs[chain.name].maturities ?? [];
3036
2979
  for (const maturityName of maturities) rules.push({
3037
2980
  type: "maturity",
3038
2981
  chain_id: chain.id,
3039
2982
  name: maturityName,
3040
2983
  timestamp: from$16(maturityName)
3041
2984
  });
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
2985
  const loanTokens = assets[chain.id.toString()] ?? [];
3054
2986
  for (const address of loanTokens) rules.push({
3055
2987
  type: "loan_token",
@@ -3367,8 +3299,8 @@ const offerExample = {
3367
3299
  price: "2750000000000000000",
3368
3300
  group: "0x000000000000000000000000000000000000000000000000000000000008b8f4",
3369
3301
  session: "0x0000000000000000000000000000000000000000000000000000000000000000",
3370
- callback: "0x1111111111111111111111111111111111111111",
3371
- callback_data: "0x00000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000000100000000000000000000000034cf890db685fc536e05652fb41f02090c3fb751000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000108e644e3ab01184155270aa92a00000000000"
3302
+ callback: "0x0000000000000000000000000000000000000000",
3303
+ callback_data: "0x"
3372
3304
  },
3373
3305
  offer_hash: "0xac4bd8318ec914f89f8af913f162230575b0ac0696a19256bc12138c5cfe1427",
3374
3306
  obligation_id: "0x25690ae1aee324a005be565f3bcdd16dbf8daf7969b26c181c8b8f467dad9abc",
@@ -3420,25 +3352,10 @@ const validateOfferExample = {
3420
3352
  lltv: "860000000000000000"
3421
3353
  }],
3422
3354
  callback: {
3423
- address: "0x1111111111111111111111111111111111111111",
3424
- data: "0x00000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000000100000000000000000000000034cf890db685fc536e05652fb41f02090c3fb751000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000108e644e3ab01184155270aa92a00000000000"
3355
+ address: "0x0000000000000000000000000000000000000000",
3356
+ data: "0x"
3425
3357
  }
3426
3358
  };
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
3359
  const routerStatusExample = {
3443
3360
  status: "live",
3444
3361
  initialized: true,
@@ -3507,55 +3424,6 @@ __decorate([ApiProperty({
3507
3424
  type: "string",
3508
3425
  example: validateOfferExample.callback.data
3509
3426
  })], 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
3427
  var AskResponse = class {};
3560
3428
  __decorate([ApiProperty({
3561
3429
  type: "string",
@@ -4073,7 +3941,7 @@ __decorate([
4073
3941
  methods: ["post"],
4074
3942
  path: "/v1/validate",
4075
3943
  summary: "Validate offers",
4076
- description: "Validates offers against router validation rules. Returns unsigned payload + root on success, or issues only on validation failure."
3944
+ 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
3945
  }),
4078
3946
  ApiBody({ type: ValidateOffersRequest }),
4079
3947
  ApiResponse({
@@ -4092,28 +3960,6 @@ ValidateController = __decorate([ApiTags("Make"), ApiResponse({
4092
3960
  description: "Bad Request",
4093
3961
  type: BadRequestResponse
4094
3962
  })], 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
3963
  let OffersController = class OffersController {
4118
3964
  async getOffers() {}
4119
3965
  };
@@ -4263,12 +4109,6 @@ const configRulesMaturityExample = {
4263
4109
  name: "end_of_next_month",
4264
4110
  timestamp: 1730415600
4265
4111
  };
4266
- const configRulesCallbackExample = {
4267
- type: "callback",
4268
- chain_id: 1,
4269
- address: "0x1111111111111111111111111111111111111111",
4270
- callback_type: "sell_erc20_callback"
4271
- };
4272
4112
  const configRulesLoanTokenExample = {
4273
4113
  type: "loan_token",
4274
4114
  chain_id: 1,
@@ -4282,7 +4122,6 @@ const configRulesOracleExample = {
4282
4122
  const configRulesChecksumExample = "f1d2d2f924e986ac86fdf7b36c94bcdf";
4283
4123
  const configRulesPayloadExample = [
4284
4124
  configRulesMaturityExample,
4285
- configRulesCallbackExample,
4286
4125
  configRulesLoanTokenExample,
4287
4126
  configRulesOracleExample
4288
4127
  ];
@@ -4347,14 +4186,9 @@ __decorate([ApiProperty({
4347
4186
  })], ConfigRulesRuleResponse.prototype, "timestamp", void 0);
4348
4187
  __decorate([ApiProperty({
4349
4188
  type: "string",
4350
- example: configRulesCallbackExample.address,
4189
+ example: configRulesLoanTokenExample.address,
4351
4190
  required: false
4352
4191
  })], 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
4192
  var ConfigRulesSuccessResponse = class {};
4359
4193
  __decorate([ApiProperty({ type: () => ConfigRulesMeta })], ConfigRulesSuccessResponse.prototype, "meta", void 0);
4360
4194
  __decorate([ApiProperty({
@@ -4415,7 +4249,7 @@ __decorate([
4415
4249
  methods: ["get"],
4416
4250
  path: "/v1/config/rules",
4417
4251
  summary: "Get config rules",
4418
- description: "Returns configured rules for supported chains."
4252
+ description: "Returns configured rules (maturities, loan tokens, oracles) for supported chains."
4419
4253
  }),
4420
4254
  ApiQuery({
4421
4255
  name: "cursor",
@@ -4594,8 +4428,7 @@ const OpenApi = async () => {
4594
4428
  ObligationsController,
4595
4429
  HealthController,
4596
4430
  UsersController,
4597
- ValidateController,
4598
- CallbacksController
4431
+ ValidateController
4599
4432
  ],
4600
4433
  document: {
4601
4434
  openapi: "3.1.0",
@@ -4851,16 +4684,6 @@ const GetBookParams = z$2.object({
4851
4684
  })
4852
4685
  });
4853
4686
  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
4687
  const GetUserPositionsParams = z$2.object({
4865
4688
  ...PaginationQueryParams.shape,
4866
4689
  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 +4702,6 @@ const schemas = {
4879
4702
  get_obligation: GetObligationParams,
4880
4703
  get_book: GetBookParams,
4881
4704
  validate_offers: ValidateOffersBody,
4882
- callback_types: CallbackTypesBody,
4883
4705
  get_user_positions: GetUserPositionsParams
4884
4706
  };
4885
4707
  function safeParse(action, query, error) {
@@ -5135,35 +4957,6 @@ async function validateOffers(body, gatekeeper) {
5135
4957
  }
5136
4958
  }
5137
4959
 
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
4960
  //#endregion
5168
4961
  //#region src/gatekeeper/Service.ts
5169
4962
  /**
@@ -5171,10 +4964,6 @@ function resolveCallbackTypes$2(parameters) {
5171
4964
  * @param parameters - App parameters including the {@link Gatekeeper} instance.
5172
4965
  * @returns Hono app exposing gatekeeper endpoints.
5173
4966
  */
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
4967
  function createApp(parameters) {
5179
4968
  const { gatekeeper, chainRegistry } = parameters;
5180
4969
  const app = new Hono();
@@ -5183,12 +4972,12 @@ function createApp(parameters) {
5183
4972
  try {
5184
4973
  body = await c.req.json();
5185
4974
  } catch (err) {
5186
- const failure$5 = failure(err);
5187
- return c.json(failure$5.body, failure$5.statusCode);
4975
+ const failure$4 = failure(err);
4976
+ return c.json(failure$4.body, failure$4.statusCode);
5188
4977
  }
5189
4978
  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);
4979
+ const failure$3 = failure(new BadRequestError("Request body must be a JSON object"));
4980
+ return c.json(failure$3.body, failure$3.statusCode);
5192
4981
  }
5193
4982
  const { statusCode, body: payload } = await validateOffers(body, gatekeeper);
5194
4983
  return c.json(payload, statusCode);
@@ -5197,34 +4986,6 @@ function createApp(parameters) {
5197
4986
  const { statusCode, body } = await getConfigRules(c.req.query(), chainRegistry.list());
5198
4987
  return c.json(body, statusCode);
5199
4988
  });
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
4989
  return app;
5229
4990
  }
5230
4991
  /**
@@ -5320,1559 +5081,8 @@ function now() {
5320
5081
  }
5321
5082
 
5322
5083
  //#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";
5084
+ //#region src/database/drizzle/VERSION.ts
5085
+ const VERSION = "router_v1.6";
6876
5086
 
6877
5087
  //#endregion
6878
5088
  //#region src/database/drizzle/schema.ts
@@ -7274,6 +5484,1654 @@ const merklePaths = s.table(EnumTableName.MERKLE_PATHS, {
7274
5484
  createdAt: timestamp("created_at").defaultNow().notNull()
7275
5485
  }, (table) => [index("merkle_paths_tree_root_idx").on(table.treeRoot)]);
7276
5486
 
5487
+ //#endregion
5488
+ //#region src/indexer/collectors/CollectFunctions/collectConsumedEvents.ts
5489
+ const buildGroupKey = (parameters) => {
5490
+ return `${parameters.chainId}-${parameters.maker.toLowerCase()}-${parameters.group.toLowerCase()}`;
5491
+ };
5492
+ async function* collectConsumedEvents(parameters) {
5493
+ let { db, collector, client, lastBlockNumber: blockNumber, epoch, options: { maxBatchSize = 1e3, blockWindow } = {} } = parameters;
5494
+ const logger = getLogger();
5495
+ let startBlock = blockNumber;
5496
+ let reorgDetected = false;
5497
+ const { blockNumber: latestBlockNumberChain } = await db.blocks.getChain(client.chain.id);
5498
+ const stream = streamLogs({
5499
+ client,
5500
+ contractAddress: client.chain.custom.morpho.address,
5501
+ blockNumberGte: blockNumber,
5502
+ blockNumberLte: latestBlockNumberChain,
5503
+ order: "asc",
5504
+ options: {
5505
+ maxBatchSize,
5506
+ blockWindow
5507
+ }
5508
+ });
5509
+ for await (const { logs, blockNumber: lastStreamBlockNumber } of stream) {
5510
+ const parsedLogs = parseEventLogs({
5511
+ abi: [consumedEvent, takeEvent],
5512
+ logs,
5513
+ strict: false
5514
+ });
5515
+ const normalizedLogs = [];
5516
+ const groups$3 = /* @__PURE__ */ new Map();
5517
+ const eventIds = /* @__PURE__ */ new Set();
5518
+ const recordLog = (log) => {
5519
+ normalizedLogs.push(log);
5520
+ eventIds.add(log.id);
5521
+ const groupKey = buildGroupKey({
5522
+ chainId: log.chainId,
5523
+ maker: log.maker,
5524
+ group: log.group
5525
+ });
5526
+ if (!groups$3.has(groupKey)) groups$3.set(groupKey, {
5527
+ chainId: log.chainId,
5528
+ maker: log.maker.toLowerCase(),
5529
+ group: log.group.toLowerCase()
5530
+ });
5531
+ };
5532
+ for (const rawLog of parsedLogs) {
5533
+ if (rawLog.blockNumber === null || rawLog.logIndex === null || rawLog.transactionHash === null) {
5534
+ logger.debug({
5535
+ collector,
5536
+ chainId: client.chain.id,
5537
+ msg: "Skipping log because it is missing required fields"
5538
+ });
5539
+ continue;
5540
+ }
5541
+ if (rawLog.eventName === consumedEvent.name) {
5542
+ const consumeArgs = rawLog.args;
5543
+ if (consumeArgs.user === void 0 || consumeArgs.group === void 0 || consumeArgs.amount === void 0) {
5544
+ logger.debug({
5545
+ collector,
5546
+ chainId: client.chain.id,
5547
+ msg: "Skipping Consume log because it is missing required args"
5548
+ });
5549
+ continue;
5550
+ }
5551
+ recordLog({
5552
+ kind: "consume",
5553
+ id: `${rawLog.blockNumber.toString()}-${rawLog.logIndex.toString()}-${client.chain.id}-${rawLog.transactionHash}`,
5554
+ chainId: client.chain.id,
5555
+ maker: consumeArgs.user,
5556
+ group: consumeArgs.group,
5557
+ amount: consumeArgs.amount,
5558
+ blockNumber: Number(rawLog.blockNumber)
5559
+ });
5560
+ continue;
5561
+ }
5562
+ if (rawLog.eventName === takeEvent.name) {
5563
+ const takeArgs = rawLog.args;
5564
+ if (takeArgs.maker === void 0 || takeArgs.group === void 0 || takeArgs.consumed === void 0) {
5565
+ logger.debug({
5566
+ collector,
5567
+ chainId: client.chain.id,
5568
+ msg: "Skipping Take log because it is missing required args"
5569
+ });
5570
+ continue;
5571
+ }
5572
+ recordLog({
5573
+ kind: "take",
5574
+ id: `${rawLog.blockNumber.toString()}-${rawLog.logIndex.toString()}-${client.chain.id}-${rawLog.transactionHash}`,
5575
+ chainId: client.chain.id,
5576
+ maker: takeArgs.maker,
5577
+ group: takeArgs.group,
5578
+ consumed: takeArgs.consumed,
5579
+ blockNumber: Number(rawLog.blockNumber)
5580
+ });
5581
+ }
5582
+ }
5583
+ await db.transaction(async (dbTx) => {
5584
+ const existingEventIds = /* @__PURE__ */ new Set();
5585
+ if (eventIds.size > 0) {
5586
+ const ids = Array.from(eventIds);
5587
+ for (let index = 0; index < ids.length; index += 500) {
5588
+ const slice = ids.slice(index, index + 500);
5589
+ const { rows } = await dbTx.execute(sql`
5590
+ SELECT event_id
5591
+ FROM ${consumedEvents}
5592
+ WHERE event_id IN (${sql.join(slice.map((id) => sql`${id}`), sql`,`)});
5593
+ `);
5594
+ for (const row of rows) existingEventIds.add(row.event_id);
5595
+ }
5596
+ }
5597
+ const consumedByGroup = /* @__PURE__ */ new Map();
5598
+ if (groups$3.size > 0) {
5599
+ const groupList = Array.from(groups$3.values());
5600
+ for (let index = 0; index < groupList.length; index += 500) {
5601
+ const slice = groupList.slice(index, index + 500);
5602
+ const { rows } = await dbTx.execute(sql`
5603
+ WITH targets(chain_id, maker, "group") AS (
5604
+ VALUES ${sql.join(slice.map((group) => sql`(${group.chainId}::bigint, ${group.maker.toLowerCase()}::varchar(42), ${group.group.toLowerCase()}::varchar(66))`), sql`,`)}
5605
+ )
5606
+ SELECT
5607
+ targets.chain_id,
5608
+ targets.maker,
5609
+ targets."group",
5610
+ COALESCE(g.consumed, 0)::numeric AS consumed
5611
+ FROM targets
5612
+ LEFT JOIN ${groups} g
5613
+ ON g.chain_id = targets.chain_id
5614
+ AND g.maker = targets.maker
5615
+ AND g."group" = targets."group";
5616
+ `);
5617
+ for (const row of rows) {
5618
+ const groupKey = buildGroupKey({
5619
+ chainId: Number(row.chain_id),
5620
+ maker: row.maker,
5621
+ group: row.group
5622
+ });
5623
+ consumedByGroup.set(groupKey, BigInt(row.consumed ?? "0"));
5624
+ }
5625
+ }
5626
+ }
5627
+ const events = [];
5628
+ for (const log of normalizedLogs) {
5629
+ if (existingEventIds.has(log.id)) continue;
5630
+ const groupKey = buildGroupKey({
5631
+ chainId: log.chainId,
5632
+ maker: log.maker,
5633
+ group: log.group
5634
+ });
5635
+ const previousConsumed = consumedByGroup.get(groupKey) ?? 0n;
5636
+ if (log.kind === "consume") {
5637
+ events.push({
5638
+ id: log.id,
5639
+ chainId: log.chainId,
5640
+ maker: log.maker,
5641
+ group: log.group,
5642
+ amount: log.amount,
5643
+ blockNumber: log.blockNumber
5644
+ });
5645
+ consumedByGroup.set(groupKey, previousConsumed + log.amount);
5646
+ continue;
5647
+ }
5648
+ const delta = log.consumed - previousConsumed;
5649
+ if (delta <= 0n) {
5650
+ logger.debug({
5651
+ collector,
5652
+ chainId: client.chain.id,
5653
+ msg: "Skipping Take log because consumed did not increase",
5654
+ previous_consumed: previousConsumed.toString(),
5655
+ consumed: log.consumed.toString()
5656
+ });
5657
+ continue;
5658
+ }
5659
+ events.push({
5660
+ id: log.id,
5661
+ chainId: log.chainId,
5662
+ maker: log.maker,
5663
+ group: log.group,
5664
+ amount: delta,
5665
+ blockNumber: log.blockNumber
5666
+ });
5667
+ consumedByGroup.set(groupKey, log.consumed);
5668
+ }
5669
+ try {
5670
+ await dbTx.consumed.create(events);
5671
+ if (events.length > 0) logger.info({
5672
+ msg: `Events indexed`,
5673
+ collector,
5674
+ count: events.length,
5675
+ chain_id: client.chain.id,
5676
+ block_range: [startBlock, lastStreamBlockNumber]
5677
+ });
5678
+ } catch (err) {
5679
+ logger.error({
5680
+ err,
5681
+ msg: "Failed to process consumed events"
5682
+ });
5683
+ }
5684
+ blockNumber = lastStreamBlockNumber;
5685
+ try {
5686
+ await dbTx.blocks.advanceCollector({
5687
+ collectorName: collector,
5688
+ chainId: client.chain.id,
5689
+ blockNumber,
5690
+ epoch
5691
+ });
5692
+ } catch (_) {
5693
+ try {
5694
+ const ancestor = await dbTx.blocks.getCollector({
5695
+ collectorName: collector,
5696
+ chainId: client.chain.id
5697
+ });
5698
+ blockNumber = ancestor.blockNumber;
5699
+ const deleted = await dbTx.consumed.delete({
5700
+ chainId: client.chain.id,
5701
+ blockNumberGte: blockNumber + 1
5702
+ });
5703
+ logger.info({
5704
+ collector,
5705
+ chain_id: client.chain.id,
5706
+ msg: `Reorg detected, events deleted`,
5707
+ count: deleted,
5708
+ block_number: blockNumber
5709
+ });
5710
+ await dbTx.blocks.advanceCollector({
5711
+ collectorName: collector,
5712
+ chainId: client.chain.id,
5713
+ blockNumber,
5714
+ epoch: ancestor.epoch
5715
+ });
5716
+ reorgDetected = true;
5717
+ } catch (err) {
5718
+ const msg = "Failed to delete consumed events when handling reorg.";
5719
+ logger.error({
5720
+ collector,
5721
+ chainId: client.chain.id,
5722
+ msg,
5723
+ err
5724
+ });
5725
+ throw new Error(msg, { cause: err });
5726
+ }
5727
+ }
5728
+ });
5729
+ if (reorgDetected) return;
5730
+ yield blockNumber;
5731
+ startBlock = blockNumber;
5732
+ }
5733
+ }
5734
+
5735
+ //#endregion
5736
+ //#region src/indexer/collectors/CollectFunctions/collectOffers.ts
5737
+ async function* collectOffersV2(parameters) {
5738
+ let { db, collector, client, lastBlockNumber: blockNumber, gatekeeper, options: { maxBatchSize = 1e3, blockWindow } = {} } = parameters;
5739
+ const logger = getLogger();
5740
+ let startBlock = blockNumber;
5741
+ let reorgDetected = false;
5742
+ if (client.chain.custom.morpho.address.toLowerCase() === zeroAddress) {
5743
+ const msg = "Morpho V2 address is zero, signature verification will fail. Please set the Morpho V2 address in the chain configuration.";
5744
+ logger.error({
5745
+ msg,
5746
+ chain_id: client.chain.id
5747
+ });
5748
+ throw new Error(msg);
5749
+ }
5750
+ const signatureDomain = {
5751
+ chainId: client.chain.id,
5752
+ verifyingContract: client.chain.custom.morpho.address
5753
+ };
5754
+ const { blockNumber: latestBlockNumberChain } = await db.blocks.getChain(client.chain.id);
5755
+ const stream = streamLogs({
5756
+ client,
5757
+ contractAddress: client.chain.custom.mempool.address,
5758
+ event: {
5759
+ type: "event",
5760
+ name: "Event",
5761
+ inputs: [{
5762
+ name: "data",
5763
+ type: "bytes",
5764
+ indexed: false,
5765
+ internalType: "bytes"
5766
+ }],
5767
+ anonymous: false
5768
+ },
5769
+ blockNumberGte: blockNumber,
5770
+ blockNumberLte: latestBlockNumberChain,
5771
+ order: "asc",
5772
+ options: {
5773
+ maxBatchSize,
5774
+ blockWindow
5775
+ }
5776
+ });
5777
+ for await (const { logs, blockNumber: lastStreamBlockNumber } of stream) {
5778
+ blockNumber = lastStreamBlockNumber;
5779
+ const decodedTrees = [];
5780
+ for (const log of logs) {
5781
+ if (!log) continue;
5782
+ const [payload] = decodeAbiParameters([{ type: "bytes" }], log.data);
5783
+ try {
5784
+ const { tree, signature, signer } = await decode(payload, signatureDomain);
5785
+ const signerMismatch = tree.offers.find((offer) => offer.maker.toLowerCase() !== signer.toLowerCase());
5786
+ if (signerMismatch) {
5787
+ logger.debug({
5788
+ msg: "Tree rejected: signer mismatch",
5789
+ reason: "signer_mismatch",
5790
+ signer,
5791
+ maker: signerMismatch.maker,
5792
+ chain_id: client.chain.id
5793
+ });
5794
+ continue;
5795
+ }
5796
+ decodedTrees.push({
5797
+ tree,
5798
+ signature,
5799
+ signer,
5800
+ blockNumber: Number(log.blockNumber)
5801
+ });
5802
+ } catch (err) {
5803
+ const reason = err instanceof DecodeError && err.message.includes("signature") ? "invalid_signature" : "decode_failed";
5804
+ logger.debug({
5805
+ msg: "Tree decode failed",
5806
+ reason,
5807
+ chain_id: client.chain.id,
5808
+ err: err instanceof Error ? err.message : String(err)
5809
+ });
5810
+ }
5811
+ }
5812
+ await db.transaction(async (dbTx) => {
5813
+ const { epoch, blockNumber: latestBlockNumber } = await dbTx.blocks.getChain(client.chain.id);
5814
+ const treesToInsert = [];
5815
+ let totalValidOffers = 0;
5816
+ const offersWithBlock = [];
5817
+ for (const { tree, signature, blockNumber: treeBlockNumber } of decodedTrees) try {
5818
+ const allowedResults = await gatekeeper.isAllowed(tree.offers);
5819
+ const hasBlockWindowViolation = treeBlockNumber > latestBlockNumber;
5820
+ if (!(allowedResults.issues.length === 0 && allowedResults.valid.length === tree.offers.length) || hasBlockWindowViolation) {
5821
+ if (allowedResults.issues.length > 0) {
5822
+ const hasMixedMaker = allowedResults.issues.some((i) => i.ruleName === "mixed_maker");
5823
+ logger.debug({
5824
+ msg: "Tree offers rejected by gatekeeper",
5825
+ reason: hasMixedMaker ? "mixed_maker" : "gatekeeper_rejected",
5826
+ chain_id: client.chain.id,
5827
+ issues_count: allowedResults.issues.length
5828
+ });
5829
+ } else if (hasBlockWindowViolation) logger.debug({
5830
+ msg: "Tree rejected: offers outside block window",
5831
+ reason: "block_window",
5832
+ chain_id: client.chain.id
5833
+ });
5834
+ continue;
5835
+ }
5836
+ treesToInsert.push({
5837
+ tree,
5838
+ signature
5839
+ });
5840
+ totalValidOffers += tree.offers.length;
5841
+ offersWithBlock.push(...tree.offers.map((offer) => ({
5842
+ offer,
5843
+ blockNumber: treeBlockNumber
5844
+ })));
5845
+ } catch (err) {
5846
+ const error = err instanceof Error ? err : new Error(String(err));
5847
+ logger.error({
5848
+ err: error,
5849
+ msg: "Gatekeeper validation failed",
5850
+ chain_id: client.chain.id
5851
+ });
5852
+ throw new Error("Gatekeeper validation failed", { cause: error });
5853
+ }
5854
+ const dependencies = buildOfferDependencies$1(offersWithBlock);
5855
+ await dbTx.oracles.upsert(dependencies.oracles);
5856
+ await dbTx.obligations.create(dependencies.obligations);
5857
+ await dbTx.groups.create(dependencies.groups);
5858
+ const insertedHashes = await dbTx.offers.create(dependencies.offerBatches);
5859
+ if (treesToInsert.length > 0) await dbTx.trees.create(treesToInsert);
5860
+ const insertedOffers = filterInsertedOffers({
5861
+ offers: offersWithBlock,
5862
+ hashes: insertedHashes
5863
+ });
5864
+ const { callbacks, positions, lots } = decodeCallbacks({
5865
+ chainId: client.chain.id,
5866
+ offers: insertedOffers
5867
+ });
5868
+ if (positions.length > 0) await dbTx.positions.upsert(positions);
5869
+ if (callbacks.length > 0) await dbTx.callbacks.upsert(callbacks);
5870
+ if (lots.length > 0) await dbTx.lots.create(lots);
5871
+ try {
5872
+ await dbTx.blocks.advanceCollector({
5873
+ collectorName: collector,
5874
+ chainId: client.chain.id,
5875
+ blockNumber,
5876
+ epoch
5877
+ });
5878
+ if (totalValidOffers > 0) logger.info({
5879
+ msg: `New offers`,
5880
+ collector,
5881
+ count: totalValidOffers,
5882
+ trees_count: treesToInsert.length,
5883
+ chain_id: client.chain.id,
5884
+ block_range: [startBlock, lastStreamBlockNumber]
5885
+ });
5886
+ } catch (_) {
5887
+ try {
5888
+ const ancestor = await dbTx.blocks.getCollector({
5889
+ collectorName: collector,
5890
+ chainId: client.chain.id
5891
+ });
5892
+ blockNumber = ancestor.blockNumber;
5893
+ const deleted = await dbTx.offers.delete({
5894
+ blockNumberGte: blockNumber + 1,
5895
+ chainId: client.chain.id
5896
+ });
5897
+ logger.info({
5898
+ collector,
5899
+ chain_id: client.chain.id,
5900
+ msg: `Reorg detected, offers deleted`,
5901
+ count: deleted,
5902
+ block_number: blockNumber
5903
+ });
5904
+ await dbTx.blocks.advanceCollector({
5905
+ collectorName: collector,
5906
+ chainId: client.chain.id,
5907
+ blockNumber,
5908
+ epoch: ancestor.epoch
5909
+ });
5910
+ reorgDetected = true;
5911
+ } catch (err) {
5912
+ const msg = "Failed to delete offers when handling reorg.";
5913
+ logger.error({
5914
+ collector,
5915
+ chainId: client.chain.id,
5916
+ msg,
5917
+ err
5918
+ });
5919
+ throw new Error(msg);
5920
+ }
5921
+ }
5922
+ });
5923
+ if (reorgDetected) return;
5924
+ yield blockNumber;
5925
+ startBlock = blockNumber;
5926
+ }
5927
+ }
5928
+ function decodeCallbacks(parameters) {
5929
+ const { offers } = parameters;
5930
+ if (offers.length === 0) return {
5931
+ callbacks: [],
5932
+ positions: [],
5933
+ lots: []
5934
+ };
5935
+ const callbacks = [];
5936
+ const positions = [];
5937
+ const lots = [];
5938
+ for (const { offer, blockNumber: offerBlockNumber } of offers) {
5939
+ if (!offer.buy) continue;
5940
+ if (!isEmptyCallback(offer)) continue;
5941
+ const loanToken = offer.loanToken.toLowerCase();
5942
+ positions.push(from$10({
5943
+ chainId: offer.chainId,
5944
+ contract: loanToken,
5945
+ user: offer.maker,
5946
+ type: Type.ERC20,
5947
+ asset: loanToken,
5948
+ blockNumber: offerBlockNumber
5949
+ }));
5950
+ lots.push({
5951
+ positionChainId: offer.chainId,
5952
+ positionContract: loanToken,
5953
+ positionUser: offer.maker,
5954
+ group: offer.group,
5955
+ size: offer.assets
5956
+ });
5957
+ callbacks.push({
5958
+ offerHash: hash(offer),
5959
+ callbacks: [{
5960
+ chainId: offer.chainId,
5961
+ contract: loanToken,
5962
+ user: offer.maker,
5963
+ amount: offer.assets
5964
+ }]
5965
+ });
5966
+ }
5967
+ return {
5968
+ callbacks,
5969
+ positions,
5970
+ lots
5971
+ };
5972
+ }
5973
+ function buildOfferDependencies$1(offers) {
5974
+ const obligationsById = /* @__PURE__ */ new Map();
5975
+ const oraclesByKey = /* @__PURE__ */ new Map();
5976
+ const groupsByKey = /* @__PURE__ */ new Map();
5977
+ const offersByBlock = /* @__PURE__ */ new Map();
5978
+ for (const { offer, blockNumber } of offers) {
5979
+ const list = offersByBlock.get(blockNumber) ?? [];
5980
+ list.push(offer);
5981
+ offersByBlock.set(blockNumber, list);
5982
+ const obligationId$2 = obligationId(offer);
5983
+ if (!obligationsById.get(obligationId$2)) obligationsById.set(obligationId$2, from$13({
5984
+ chainId: offer.chainId,
5985
+ loanToken: offer.loanToken,
5986
+ maturity: offer.maturity,
5987
+ collaterals: offer.collaterals
5988
+ }));
5989
+ for (const collateral of offer.collaterals) {
5990
+ const oracleKey = `${offer.chainId}-${collateral.oracle}`.toLowerCase();
5991
+ if (!oraclesByKey.has(oracleKey)) oraclesByKey.set(oracleKey, from$11({
5992
+ chainId: offer.chainId,
5993
+ address: collateral.oracle,
5994
+ price: null,
5995
+ blockNumber
5996
+ }));
5997
+ }
5998
+ const groupKey = `${offer.chainId}-${offer.maker}-${offer.group}`.toLowerCase();
5999
+ if (!groupsByKey.has(groupKey)) groupsByKey.set(groupKey, {
6000
+ chainId: offer.chainId,
6001
+ maker: offer.maker,
6002
+ group: offer.group,
6003
+ blockNumber
6004
+ });
6005
+ }
6006
+ return {
6007
+ obligations: Array.from(obligationsById.values()),
6008
+ oracles: Array.from(oraclesByKey.values()),
6009
+ groups: Array.from(groupsByKey.values()),
6010
+ offerBatches: Array.from(offersByBlock.entries()).map(([blockNumber, items]) => ({
6011
+ blockNumber,
6012
+ offers: items
6013
+ }))
6014
+ };
6015
+ }
6016
+ function filterInsertedOffers(parameters) {
6017
+ if (parameters.hashes.length === 0) return [];
6018
+ const inserted = new Set(parameters.hashes.map((hash) => hash.toLowerCase()));
6019
+ const seen = /* @__PURE__ */ new Set();
6020
+ const filtered = [];
6021
+ for (const entry of parameters.offers) {
6022
+ const hash$2 = hash(entry.offer).toLowerCase();
6023
+ if (!inserted.has(hash$2)) continue;
6024
+ if (seen.has(hash$2)) continue;
6025
+ seen.add(hash$2);
6026
+ filtered.push(entry);
6027
+ }
6028
+ return filtered;
6029
+ }
6030
+
6031
+ //#endregion
6032
+ //#region src/indexer/collectors/fetchers/fetchOraclePrices.ts
6033
+ /**
6034
+ * Fetches prices from multiple oracle contracts using multicall.
6035
+ *
6036
+ * - Executes `price()` on {@link Abi.Oracle} for each {@link Address}.
6037
+ * - Requires a client supporting {@link PublicActions.multicall | multicall}.
6038
+ *
6039
+ * @param parameters - {@link fetchOraclePrices.Parameters} including the list of oracle
6040
+ * {@link Address | addresses}, fetch options, and the multicall-enabled client.
6041
+ * @returns {@link fetchOraclePrices.ReturnType} mapping {@link Address} to `bigint` price.
6042
+ */
6043
+ async function fetchOraclePrices(parameters) {
6044
+ const { client, oracles, options } = parameters;
6045
+ if (oracles.length === 0) return /* @__PURE__ */ new Map();
6046
+ const batchSize = Math.max(1, options?.batchSize ?? 5e3);
6047
+ const retryAttempts = Math.max(1, options?.retryAttempts ?? 3);
6048
+ const retryDelayMs = Math.max(0, options?.retryDelayMs ?? 50);
6049
+ const blockNumber = options?.blockNumber ? BigInt(options.blockNumber) : void 0;
6050
+ const out = /* @__PURE__ */ new Map();
6051
+ for (const oraclesBatch of batch$1(oracles, batchSize)) {
6052
+ const priceCalls = [];
6053
+ for (const oracle of oraclesBatch) priceCalls.push({
6054
+ address: oracle,
6055
+ abi: Oracle,
6056
+ functionName: "price",
6057
+ args: []
6058
+ });
6059
+ const prices = await batchMulticall({
6060
+ client,
6061
+ calls: priceCalls,
6062
+ batchSize,
6063
+ retryAttempts,
6064
+ retryDelayMs,
6065
+ blockNumber
6066
+ });
6067
+ for (let i = 0; i < oraclesBatch.length; i++) {
6068
+ const oracle = oraclesBatch[i];
6069
+ const price = prices[i];
6070
+ out.set(oracle, price);
6071
+ }
6072
+ }
6073
+ return out;
6074
+ }
6075
+
6076
+ //#endregion
6077
+ //#region src/indexer/collectors/fetchers/snapshotERC20Positions.ts
6078
+ /**
6079
+ * Fetches ERC20 balances for positions at a given block number and returns the positions with the updated balances.
6080
+ * @notice This method does not mutate positions and returns a new array.
6081
+ *
6082
+ * @param parameters - {@link snapshotERC20Positions.Parameters}
6083
+ * @returns Positions - {@link snapshotERC20Positions.ReturnType}
6084
+ */
6085
+ async function snapshotERC20Positions(parameters) {
6086
+ const { positions: oldPositions, blockNumber, options: { maxBatchSize = 1e3, retryAttempts = 5, retryDelayMs = 500 } = {} } = parameters;
6087
+ if (oldPositions.length === 0) return [];
6088
+ const calls = [];
6089
+ for (const position of oldPositions) calls.push({
6090
+ address: position.contract,
6091
+ abi: erc20Abi,
6092
+ functionName: "balanceOf",
6093
+ args: [position.user]
6094
+ });
6095
+ const balances = await batchMulticall({
6096
+ client: parameters.client,
6097
+ calls,
6098
+ blockNumber: BigInt(blockNumber),
6099
+ batchSize: maxBatchSize,
6100
+ retryAttempts,
6101
+ retryDelayMs
6102
+ });
6103
+ const positions = [];
6104
+ for (let i = 0; i < balances.length; i++) {
6105
+ const oldPosition = oldPositions[i];
6106
+ if (!oldPosition) continue;
6107
+ positions.push({
6108
+ ...oldPosition,
6109
+ balance: balances[i],
6110
+ blockNumber
6111
+ });
6112
+ }
6113
+ return positions;
6114
+ }
6115
+
6116
+ //#endregion
6117
+ //#region src/indexer/collectors/fetchers/snapshotVaultPositions.ts
6118
+ /**
6119
+ * Fetches vault shares for users at a given block number, converts them to assets and returns the positions with the updated balances.
6120
+ * @notice This method does not mutate positions and returns a new array.
6121
+ *
6122
+ * @param parameters - {@link snapshotVaultPositions.Parameters}
6123
+ * @returns Positions - {@link snapshotVaultPositions.ReturnType}
6124
+ */
6125
+ async function snapshotVaultPositions(parameters) {
6126
+ const logger = getLogger();
6127
+ const { client, positions: oldPositions, blockNumber, options: { maxBatchSize = 1e3, retryAttempts = 5, retryDelayMs = 500 } = {} } = parameters;
6128
+ const calls = [];
6129
+ const contracts = /* @__PURE__ */ new Map();
6130
+ const positions = structuredClone(oldPositions);
6131
+ for (const position of positions) {
6132
+ calls.push({
6133
+ address: position.contract,
6134
+ abi: MetaMorpho,
6135
+ functionName: "balanceOf",
6136
+ args: [position.user],
6137
+ convertToAssets: (shares) => {
6138
+ const contract = contracts.get(position.contract.toLowerCase());
6139
+ if (!contract) return;
6140
+ if (contract.decimalsOffset === void 0 || contract.totalAssets === void 0 || contract.totalSupply === void 0) return;
6141
+ try {
6142
+ position.balance = convertToAssets({
6143
+ shares,
6144
+ totalAssets: contract.totalAssets,
6145
+ totalSupply: contract.totalSupply,
6146
+ decimalsOffset: contract.decimalsOffset
6147
+ });
6148
+ position.asset = contract.asset;
6149
+ position.blockNumber = blockNumber;
6150
+ } catch (err) {
6151
+ if (err instanceof DenominatorIsZeroError) {
6152
+ logger.error({
6153
+ msg: "Failed to convert shares to assets",
6154
+ chain_id: client.chain.id,
6155
+ block_number: blockNumber,
6156
+ position_contract: position.contract,
6157
+ position_user: position.user,
6158
+ shares,
6159
+ err
6160
+ });
6161
+ return;
6162
+ }
6163
+ throw err;
6164
+ }
6165
+ }
6166
+ });
6167
+ if (contracts.has(position.contract.toLowerCase())) continue;
6168
+ calls.push({
6169
+ address: position.contract,
6170
+ abi: MetaMorpho,
6171
+ functionName: "DECIMALS_OFFSET",
6172
+ args: []
6173
+ }, {
6174
+ address: position.contract,
6175
+ abi: MetaMorpho,
6176
+ functionName: "totalAssets",
6177
+ args: []
6178
+ }, {
6179
+ address: position.contract,
6180
+ abi: MetaMorpho,
6181
+ functionName: "totalSupply",
6182
+ args: []
6183
+ }, {
6184
+ address: position.contract,
6185
+ abi: MetaMorpho,
6186
+ functionName: "asset",
6187
+ args: []
6188
+ });
6189
+ contracts.set(position.contract.toLowerCase(), {
6190
+ decimalsOffset: void 0,
6191
+ totalAssets: void 0,
6192
+ totalSupply: void 0,
6193
+ asset: void 0
6194
+ });
6195
+ }
6196
+ const results = await batchMulticall({
6197
+ client,
6198
+ calls,
6199
+ blockNumber: BigInt(blockNumber),
6200
+ batchSize: maxBatchSize,
6201
+ retryAttempts,
6202
+ retryDelayMs
6203
+ });
6204
+ const convertToAssetsList = [];
6205
+ for (let i = 0; i < results.length; i++) {
6206
+ const call = calls[i];
6207
+ const value = results[i];
6208
+ const contract = contracts.get(call.address.toLowerCase());
6209
+ if (!contract) continue;
6210
+ switch (call.functionName) {
6211
+ case "balanceOf":
6212
+ convertToAssetsList.push(() => call.convertToAssets(value));
6213
+ break;
6214
+ case "DECIMALS_OFFSET":
6215
+ contract.decimalsOffset = value;
6216
+ break;
6217
+ case "totalAssets":
6218
+ contract.totalAssets = value;
6219
+ break;
6220
+ case "totalSupply":
6221
+ contract.totalSupply = value;
6222
+ break;
6223
+ case "asset":
6224
+ contract.asset = value.toLowerCase();
6225
+ break;
6226
+ }
6227
+ }
6228
+ for (const convertToAssets of convertToAssetsList) convertToAssets();
6229
+ return positions;
6230
+ }
6231
+
6232
+ //#endregion
6233
+ //#region src/indexer/collectors/CollectFunctions/collectPositions.ts
6234
+ async function* collectPositions(parameters) {
6235
+ let { db, collector, client, lastBlockNumber: blockNumber, epoch, options: { maxBatchSize = 1e3, retryAttempts = 5, retryDelayMs = 500, blockWindow } = {} } = parameters;
6236
+ const logger = getLogger();
6237
+ let startBlock = blockNumber;
6238
+ let reorgDetected = false;
6239
+ const TransferEvent = {
6240
+ type: "event",
6241
+ name: "Transfer",
6242
+ inputs: [
6243
+ {
6244
+ name: "from",
6245
+ type: "address",
6246
+ indexed: true
6247
+ },
6248
+ {
6249
+ name: "to",
6250
+ type: "address",
6251
+ indexed: true
6252
+ },
6253
+ {
6254
+ name: "value",
6255
+ type: "uint256",
6256
+ indexed: false
6257
+ }
6258
+ ]
6259
+ };
6260
+ const { blockNumber: latestBlockNumberChain } = await db.blocks.getChain(client.chain.id);
6261
+ const stream = streamLogs({
6262
+ client,
6263
+ event: TransferEvent,
6264
+ blockNumberGte: blockNumber,
6265
+ blockNumberLte: latestBlockNumberChain,
6266
+ order: "asc",
6267
+ options: {
6268
+ maxBatchSize,
6269
+ blockWindow
6270
+ }
6271
+ });
6272
+ for await (const { logs, blockNumber: lastStreamBlockNumber } of stream) {
6273
+ blockNumber = lastStreamBlockNumber;
6274
+ const parsedLogs = parseEventLogs({
6275
+ abi: [TransferEvent],
6276
+ logs
6277
+ });
6278
+ const transfers = [];
6279
+ for (const log of parsedLogs) {
6280
+ if (log.blockNumber === null || log.logIndex === null || log.transactionHash === null) {
6281
+ logger.debug({
6282
+ collector,
6283
+ chainId: client.chain.id,
6284
+ msg: "Skipping log because it is missing required fields"
6285
+ });
6286
+ continue;
6287
+ }
6288
+ transfers.push(from$8({
6289
+ id: `${client.chain.id}-${log.blockNumber.toString()}-${log.transactionHash}-${log.logIndex.toString()}`,
6290
+ chainId: client.chain.id,
6291
+ contract: log.address,
6292
+ from: log.args.from,
6293
+ to: log.args.to,
6294
+ value: log.args.value,
6295
+ blockNumber: Number(log.blockNumber)
6296
+ }));
6297
+ }
6298
+ const { positions } = await db.positions.get({
6299
+ chainId: client.chain.id,
6300
+ filled: false
6301
+ });
6302
+ const newPositions = [];
6303
+ try {
6304
+ newPositions.push(...(await _snapshot({
6305
+ positions,
6306
+ blockNumber: latestBlockNumberChain,
6307
+ client,
6308
+ maxBatchSize,
6309
+ retryAttempts,
6310
+ retryDelayMs
6311
+ })).map((p) => ({
6312
+ ...p,
6313
+ blockNumber: p.blockNumber + 1
6314
+ })));
6315
+ } catch (err) {
6316
+ logger.error({
6317
+ msg: "Failed to snapshot new empty positions",
6318
+ collector,
6319
+ chain_id: client.chain.id,
6320
+ block_number: latestBlockNumberChain,
6321
+ err
6322
+ });
6323
+ yield startBlock;
6324
+ return;
6325
+ }
6326
+ try {
6327
+ await db.transaction(async (dbTx) => {
6328
+ const insertPositions = async () => {
6329
+ if (newPositions.length === 0) return;
6330
+ try {
6331
+ const count = await dbTx.positions.upsert(newPositions);
6332
+ logger.info({
6333
+ msg: `New positions`,
6334
+ collector,
6335
+ count,
6336
+ chain_id: client.chain.id,
6337
+ block_number: latestBlockNumberChain
6338
+ });
6339
+ } catch (err) {
6340
+ throw new InsertPositionsError(err);
6341
+ }
6342
+ };
6343
+ const insertTransfers = async () => {
6344
+ if (transfers.length === 0) return;
6345
+ try {
6346
+ const created = await dbTx.transfers.create(transfers);
6347
+ logger.info({
6348
+ msg: `New transfers`,
6349
+ collector,
6350
+ count: created,
6351
+ chain_id: client.chain.id,
6352
+ block_range: [startBlock, blockNumber]
6353
+ });
6354
+ } catch (err) {
6355
+ throw new InsertTransfersError(err);
6356
+ }
6357
+ };
6358
+ const saveBlockNumber = async () => {
6359
+ try {
6360
+ await dbTx.blocks.advanceCollector({
6361
+ collectorName: collector,
6362
+ chainId: client.chain.id,
6363
+ blockNumber,
6364
+ epoch
6365
+ });
6366
+ } catch (_) {
6367
+ throw new ReorgError(blockNumber);
6368
+ }
6369
+ };
6370
+ await insertPositions();
6371
+ await insertTransfers();
6372
+ await saveBlockNumber();
6373
+ });
6374
+ } catch (err) {
6375
+ if (err instanceof ReorgError) {
6376
+ logger.info({
6377
+ msg: "Reorg detected, positions and transfers insertion aborted",
6378
+ collector,
6379
+ count: newPositions.length,
6380
+ chain_id: client.chain.id,
6381
+ block_number: blockNumber
6382
+ });
6383
+ reorgDetected = true;
6384
+ }
6385
+ if (err instanceof InsertPositionsError) {
6386
+ logger.error({
6387
+ msg: "Failed to insert positions",
6388
+ collector,
6389
+ count: newPositions.length,
6390
+ chain_id: client.chain.id,
6391
+ block_number: latestBlockNumberChain,
6392
+ err
6393
+ });
6394
+ throw err.cause;
6395
+ }
6396
+ if (err instanceof InsertTransfersError) {
6397
+ logger.error({
6398
+ msg: "Failed to insert transfers",
6399
+ collector,
6400
+ count: transfers.length,
6401
+ chain_id: client.chain.id,
6402
+ block_number: blockNumber,
6403
+ err
6404
+ });
6405
+ throw err.cause;
6406
+ }
6407
+ }
6408
+ if (!reorgDetected) {
6409
+ startBlock = blockNumber;
6410
+ if (newPositions.length === 0 && transfers.length === 0 && lastStreamBlockNumber !== latestBlockNumberChain) continue;
6411
+ yield blockNumber;
6412
+ continue;
6413
+ }
6414
+ await db.transaction(async (dbTx) => {
6415
+ try {
6416
+ const ancestor = await dbTx.blocks.getCollector({
6417
+ collectorName: collector,
6418
+ chainId: client.chain.id
6419
+ });
6420
+ blockNumber = ancestor.blockNumber;
6421
+ const emptied = await dbTx.positions.setEmptyAfter({
6422
+ chainId: client.chain.id,
6423
+ blockNumber: blockNumber + 1
6424
+ });
6425
+ logger.info({
6426
+ msg: "Reorg detected, positions set to empty",
6427
+ collector,
6428
+ count: emptied,
6429
+ chain_id: client.chain.id,
6430
+ block_number_gte: blockNumber + 1
6431
+ });
6432
+ await dbTx.blocks.advanceCollector({
6433
+ collectorName: collector,
6434
+ chainId: client.chain.id,
6435
+ blockNumber,
6436
+ epoch: ancestor.epoch
6437
+ });
6438
+ } catch (err) {
6439
+ const msg = "Failed to revert to ancestor block when handling reorg.";
6440
+ logger.error({
6441
+ collector,
6442
+ chainId: client.chain.id,
6443
+ msg,
6444
+ err
6445
+ });
6446
+ throw new Error(msg);
6447
+ }
6448
+ });
6449
+ return;
6450
+ }
6451
+ }
6452
+ /**
6453
+ * @internal
6454
+ *
6455
+ * Snapshots positions and returns the new positions.
6456
+ * @param parameters - {@link _snapshot.Parameters}
6457
+ * @returns The new positions. {@link _snapshot.ReturnType}
6458
+ */
6459
+ async function _snapshot(parameters) {
6460
+ const { positions, blockNumber, client, maxBatchSize, retryAttempts, retryDelayMs } = parameters;
6461
+ const vaultV1Positions = [];
6462
+ const erc20Positions = [];
6463
+ for (const position of positions) switch (position.type) {
6464
+ case Type.VAULT_V1:
6465
+ vaultV1Positions.push(position);
6466
+ break;
6467
+ case Type.ERC20:
6468
+ erc20Positions.push(position);
6469
+ break;
6470
+ default: throw new Error("Invalid position type");
6471
+ }
6472
+ const promises = [snapshotVaultPositions({
6473
+ client,
6474
+ positions: vaultV1Positions,
6475
+ blockNumber,
6476
+ options: {
6477
+ maxBatchSize,
6478
+ retryAttempts,
6479
+ retryDelayMs
6480
+ }
6481
+ }), snapshotERC20Positions({
6482
+ client,
6483
+ positions: erc20Positions,
6484
+ blockNumber,
6485
+ options: {
6486
+ maxBatchSize,
6487
+ retryAttempts,
6488
+ retryDelayMs
6489
+ }
6490
+ })];
6491
+ return (await Promise.all(promises)).flat();
6492
+ }
6493
+ var InsertPositionsError = class extends BaseError {
6494
+ name = "InsertPositionsError";
6495
+ constructor(err) {
6496
+ super("Failed to insert positions", { cause: err });
6497
+ }
6498
+ };
6499
+ var InsertTransfersError = class extends BaseError {
6500
+ name = "InsertTransfersError";
6501
+ constructor(err) {
6502
+ super("Failed to insert transfers", { cause: err });
6503
+ }
6504
+ };
6505
+
6506
+ //#endregion
6507
+ //#region src/indexer/collectors/CollectFunctions/collectPrices.ts
6508
+ /**
6509
+ * Collects oracle prices from on-chain oracles and persists them.
6510
+ *
6511
+ * - Reads oracle definitions as {@link Oracle.Oracle}.
6512
+ * - Uses chain metadata from {@link Chain.Chain}.
6513
+ *
6514
+ * @param parameters - {@link collectPrices.Parameters} (extends {@link Collector.CollectParameters})
6515
+ * with a client supporting {@link PublicActions.multicall | multicall}.
6516
+ * @yields Latest processed block number after each successful update.
6517
+ */
6518
+ async function* collectPrices(parameters) {
6519
+ const { db, collector, client, options: { maxBatchSize = 5e3, retryAttempts = 5, retryDelayMs = 500 } = {} } = parameters;
6520
+ const logger = getLogger();
6521
+ let blockNumber = parameters.lastBlockNumber;
6522
+ const [oracles, { blockNumber: latestBlockNumberChain, epoch }] = await Promise.all([db.oracles.get({ chainId: client.chain.id }), db.blocks.getChain(client.chain.id)]);
6523
+ const updatedOracles = [];
6524
+ try {
6525
+ const pricesMap = await fetchOraclePrices({
6526
+ client,
6527
+ oracles: oracles.map((oracle) => oracle.address),
6528
+ options: {
6529
+ batchSize: maxBatchSize,
6530
+ blockNumber: latestBlockNumberChain,
6531
+ retryAttempts,
6532
+ retryDelayMs
6533
+ }
6534
+ });
6535
+ for (const oracle of oracles) {
6536
+ const price = pricesMap.get(oracle.address);
6537
+ if (price !== void 0) updatedOracles.push({
6538
+ chainId: client.chain.id,
6539
+ address: oracle.address,
6540
+ price,
6541
+ blockNumber: latestBlockNumberChain
6542
+ });
6543
+ }
6544
+ } catch (err) {
6545
+ logger.error({
6546
+ msg: "Failed to fetch oracle prices",
6547
+ collector,
6548
+ chain_id: client.chain.id,
6549
+ block_number: latestBlockNumberChain,
6550
+ err
6551
+ });
6552
+ yield blockNumber;
6553
+ return;
6554
+ }
6555
+ let reorgDetected = false;
6556
+ try {
6557
+ await db.transaction(async (dbTx) => {
6558
+ if (updatedOracles.length > 0) {
6559
+ await dbTx.oracles.upsert(updatedOracles);
6560
+ logger.info({
6561
+ msg: "Oracle prices updated",
6562
+ collector,
6563
+ count: updatedOracles.length,
6564
+ chain_id: client.chain.id,
6565
+ block_number: latestBlockNumberChain
6566
+ });
6567
+ }
6568
+ try {
6569
+ await dbTx.blocks.advanceCollector({
6570
+ collectorName: collector,
6571
+ chainId: client.chain.id,
6572
+ blockNumber: latestBlockNumberChain,
6573
+ epoch
6574
+ });
6575
+ } catch (_) {
6576
+ throw new ReorgError(latestBlockNumberChain);
6577
+ }
6578
+ blockNumber = latestBlockNumberChain;
6579
+ });
6580
+ } catch (err) {
6581
+ if (err instanceof ReorgError) {
6582
+ logger.info({
6583
+ msg: "Reorg detected, prices update aborted",
6584
+ collector,
6585
+ count: updatedOracles.length,
6586
+ chain_id: client.chain.id,
6587
+ block_number: latestBlockNumberChain
6588
+ });
6589
+ reorgDetected = true;
6590
+ } else throw new Error("Failed to collect oracle prices", { cause: err });
6591
+ }
6592
+ if (!reorgDetected) {
6593
+ yield blockNumber;
6594
+ return;
6595
+ }
6596
+ await db.transaction(async (dbTx) => {
6597
+ try {
6598
+ const ancestor = await dbTx.blocks.getCollector({
6599
+ collectorName: collector,
6600
+ chainId: client.chain.id
6601
+ });
6602
+ blockNumber = ancestor.blockNumber;
6603
+ await dbTx.blocks.advanceCollector({
6604
+ collectorName: collector,
6605
+ chainId: client.chain.id,
6606
+ blockNumber,
6607
+ epoch: ancestor.epoch
6608
+ });
6609
+ } catch (err) {
6610
+ const msg = "Failed to revert to ancestor block when handling reorg.";
6611
+ logger.error({
6612
+ collector,
6613
+ chainId: client.chain.id,
6614
+ msg,
6615
+ err
6616
+ });
6617
+ throw new Error(msg);
6618
+ }
6619
+ });
6620
+ yield blockNumber;
6621
+ }
6622
+
6623
+ //#endregion
6624
+ //#region src/indexer/collectors/CollectorBuilder.ts
6625
+ function createBuilder(parameters) {
6626
+ const { client, db, gatekeeper, options: { maxBlockNumber, blockWindow, interval } = {} } = parameters;
6627
+ const createCollector = (name, collect) => create$16({
6628
+ name,
6629
+ collect,
6630
+ client,
6631
+ db,
6632
+ options: {
6633
+ maxBlockNumber,
6634
+ interval
6635
+ }
6636
+ });
6637
+ return {
6638
+ buildOffersCollector: ({ options: { maxBatchSize = 1e3 } = {} }) => {
6639
+ return createCollector("offers", (p) => collectOffersV2({
6640
+ ...p,
6641
+ gatekeeper,
6642
+ collector: "offers",
6643
+ client,
6644
+ options: {
6645
+ maxBatchSize,
6646
+ blockWindow
6647
+ }
6648
+ }));
6649
+ },
6650
+ buildConsumedEventsCollector: ({ options: { maxBatchSize = 1e3 } = {} } = {}) => {
6651
+ return createCollector("consumed_events", (p) => collectConsumedEvents({
6652
+ ...p,
6653
+ collector: "consumed_events",
6654
+ options: {
6655
+ maxBatchSize,
6656
+ blockWindow
6657
+ }
6658
+ }));
6659
+ },
6660
+ buildPricesCollector: ({ options: { maxBatchSize = 5e3, retryAttempts, retryDelayMs } = {} } = {}) => {
6661
+ return createCollector("prices", (p) => collectPrices({
6662
+ ...p,
6663
+ collector: "prices",
6664
+ options: {
6665
+ maxBatchSize,
6666
+ retryAttempts,
6667
+ retryDelayMs
6668
+ }
6669
+ }));
6670
+ },
6671
+ buildPositionsCollector: ({ options: { maxBatchSize = 1e3, retryAttempts, retryDelayMs } = {} } = {}) => {
6672
+ return createCollector("positions", (p) => collectPositions({
6673
+ ...p,
6674
+ collector: "positions",
6675
+ options: {
6676
+ maxBatchSize,
6677
+ retryAttempts,
6678
+ retryDelayMs,
6679
+ blockWindow
6680
+ }
6681
+ }));
6682
+ }
6683
+ };
6684
+ }
6685
+
6686
+ //#endregion
6687
+ //#region src/indexer/collectors/Collectors.ts
6688
+ const from$2 = (parameters) => {
6689
+ const { client, db, gatekeeper, maxBatchSize, maxBlockNumber, blockWindow, interval, retryAttempts, retryDelayMs } = parameters;
6690
+ const collectorBuilder = createBuilder({
6691
+ client,
6692
+ db,
6693
+ gatekeeper,
6694
+ options: {
6695
+ maxBlockNumber,
6696
+ blockWindow,
6697
+ interval
6698
+ }
6699
+ });
6700
+ return {
6701
+ offersCollector: collectorBuilder.buildOffersCollector({ options: { maxBatchSize } }),
6702
+ consumedEventsCollector: collectorBuilder.buildConsumedEventsCollector({ options: { maxBatchSize } }),
6703
+ pricesCollector: collectorBuilder.buildPricesCollector({ options: {
6704
+ maxBatchSize,
6705
+ retryAttempts,
6706
+ retryDelayMs
6707
+ } }),
6708
+ positionsCollector: collectorBuilder.buildPositionsCollector({ options: {
6709
+ maxBatchSize,
6710
+ retryAttempts,
6711
+ retryDelayMs
6712
+ } })
6713
+ };
6714
+ };
6715
+
6716
+ //#endregion
6717
+ //#region src/indexer/Indexer.ts
6718
+ function from$1(config) {
6719
+ const { client, gatekeeper, db, interval = 1e4, maxBatchSize = 1e3, maxBlockNumber, blockWindow, retryAttempts, retryDelayMs } = config;
6720
+ const { offersCollector, consumedEventsCollector, positionsCollector, pricesCollector } = from$2({
6721
+ client,
6722
+ db,
6723
+ gatekeeper,
6724
+ maxBatchSize,
6725
+ maxBlockNumber,
6726
+ blockWindow,
6727
+ interval,
6728
+ retryAttempts,
6729
+ retryDelayMs
6730
+ });
6731
+ return create$18({
6732
+ client,
6733
+ collectors: [
6734
+ offersCollector,
6735
+ consumedEventsCollector,
6736
+ positionsCollector,
6737
+ pricesCollector
6738
+ ]
6739
+ });
6740
+ }
6741
+ function create$18(params) {
6742
+ const { collectors, client } = params;
6743
+ const indexerId = `${client.chain.id.toString()}.indexer`;
6744
+ const tracer = getTracer(`router.${indexerId}`);
6745
+ const iterators = collectors.map((collector) => collector.collect());
6746
+ const next = async () => {
6747
+ await startActiveSpan(tracer, `${indexerId}.next`, async () => {
6748
+ await Promise.all(iterators.map((iterator) => iterator.next()));
6749
+ });
6750
+ };
6751
+ const _return = async () => {
6752
+ await Promise.all(iterators.map(async (iterator) => iterator.return()));
6753
+ };
6754
+ return {
6755
+ start: () => {
6756
+ const stops = collectors.map((collector) => start(collector));
6757
+ return () => {
6758
+ stops.forEach((stop) => {
6759
+ stop();
6760
+ });
6761
+ };
6762
+ },
6763
+ next,
6764
+ return: _return
6765
+ };
6766
+ }
6767
+
6768
+ //#endregion
6769
+ //#region src/indexer/collectors/Admin.ts
6770
+ function create$17(parameters) {
6771
+ const collector = "admin";
6772
+ const { client, db, options: { maxBatchSize = 25, maxBlockNumber } = {} } = parameters;
6773
+ const maxBlockNumberBI = maxBlockNumber !== void 0 ? BigInt(maxBlockNumber) : void 0;
6774
+ let finalizedBlock = null;
6775
+ let unfinalizedBlocks = [];
6776
+ let initialized = false;
6777
+ const logger = getLogger();
6778
+ let isMaxBlockNumberReached = false;
6779
+ let tick = 0;
6780
+ return { syncBlock: async () => {
6781
+ if (!initialized) {
6782
+ await Promise.all(names.map((collectorName) => db.blocks.init({
6783
+ chainId: client.chain.id,
6784
+ collectorName
6785
+ })));
6786
+ initialized = true;
6787
+ }
6788
+ if (isMaxBlockNumberReached) return true;
6789
+ const head = await client.getBlock({
6790
+ blockTag: "latest",
6791
+ includeTransactions: false
6792
+ });
6793
+ await db.transaction(async (dbTx) => {
6794
+ const { epoch, blockNumber: latestSavedBlockNumber } = await dbTx.blocks.getChain(client.chain.id);
6795
+ if (maxBlockNumberBI !== void 0 && head.number >= maxBlockNumberBI) {
6796
+ logger.info({
6797
+ msg: `Head is greater than max block number`,
6798
+ collector,
6799
+ chainId: client.chain.id,
6800
+ block_number: head.number,
6801
+ max_block_number: maxBlockNumber
6802
+ });
6803
+ await dbTx.blocks.handleReorg({
6804
+ chainId: client.chain.id,
6805
+ blockNumber: maxBlockNumber,
6806
+ epoch: epoch + 1n
6807
+ });
6808
+ isMaxBlockNumberReached = true;
6809
+ return isMaxBlockNumberReached;
6810
+ }
6811
+ finalizedBlock = await fetchFinalizedBlock({
6812
+ tick,
6813
+ client,
6814
+ logger,
6815
+ collector,
6816
+ unfinalizedBlocks,
6817
+ previousFinalizedBlock: finalizedBlock
6818
+ });
6819
+ tick++;
6820
+ let { block: returnedBlock, didReorgHappened, unfinalizedBlocks: newUnfinalizedBlocks } = await reconcile({
6821
+ client,
6822
+ block: head,
6823
+ unfinalizedBlocks,
6824
+ finalizedBlock,
6825
+ logger,
6826
+ collector,
6827
+ maxBatchSize
6828
+ });
6829
+ unfinalizedBlocks = newUnfinalizedBlocks;
6830
+ const blockNumber = Number(returnedBlock.number);
6831
+ didReorgHappened = didReorgHappened || blockNumber < latestSavedBlockNumber;
6832
+ if (didReorgHappened) await dbTx.blocks.handleReorg({
6833
+ chainId: client.chain.id,
6834
+ blockNumber,
6835
+ epoch: epoch + 1n
6836
+ });
6837
+ else await dbTx.blocks.advanceChain({
6838
+ chainId: client.chain.id,
6839
+ blockNumber,
6840
+ epoch
6841
+ });
6842
+ });
6843
+ return isMaxBlockNumberReached;
6844
+ } };
6845
+ }
6846
+ const commonAncestor = (block, unfinalizedBlocks) => {
6847
+ const parent = unfinalizedBlocks.find((b) => b.hash === block.parentHash);
6848
+ if (parent) return parent;
6849
+ return null;
6850
+ };
6851
+ const fetchFinalizedBlock = async (parameters) => {
6852
+ let { tick, client, logger, collector, unfinalizedBlocks, previousFinalizedBlock } = parameters;
6853
+ let finalizedBlock = previousFinalizedBlock;
6854
+ if (tick % 20 === 0 || previousFinalizedBlock === null) {
6855
+ finalizedBlock = await client.getBlock({
6856
+ blockTag: "finalized",
6857
+ includeTransactions: false
6858
+ });
6859
+ if (finalizedBlock === null || finalizedBlock.number === null) {
6860
+ const msg = "Failed to get finalized block";
6861
+ logger.fatal({
6862
+ collector,
6863
+ chainId: client.chain.id,
6864
+ msg
6865
+ });
6866
+ throw new Error(msg);
6867
+ }
6868
+ unfinalizedBlocks = unfinalizedBlocks.filter((b) => b.number >= finalizedBlock.number);
6869
+ }
6870
+ if (finalizedBlock.number === null || finalizedBlock.hash === null || finalizedBlock.parentHash === null) {
6871
+ const msg = "Failed to get finalized block";
6872
+ logger.fatal({
6873
+ collector,
6874
+ chainId: client.chain.id,
6875
+ msg
6876
+ });
6877
+ throw new Error(msg);
6878
+ }
6879
+ return {
6880
+ hash: finalizedBlock.hash,
6881
+ number: finalizedBlock.number,
6882
+ parentHash: finalizedBlock.parentHash
6883
+ };
6884
+ };
6885
+ const reconcile = async (parameters) => {
6886
+ let { client, block, unfinalizedBlocks, finalizedBlock, logger, collector, maxBatchSize } = parameters;
6887
+ const chain = client.chain;
6888
+ if (block.hash === null || block.number === null || block.parentHash === null) throw new Error("Failed to get block");
6889
+ const latestBlock = unfinalizedBlocks[unfinalizedBlocks.length - 1];
6890
+ if (latestBlock === void 0) {
6891
+ const newBlock = {
6892
+ hash: block.hash,
6893
+ number: block.number,
6894
+ parentHash: block.parentHash
6895
+ };
6896
+ unfinalizedBlocks.push(newBlock);
6897
+ return {
6898
+ block: newBlock,
6899
+ didReorgHappened: false,
6900
+ unfinalizedBlocks
6901
+ };
6902
+ }
6903
+ if (latestBlock.hash === block.hash) return {
6904
+ block: latestBlock,
6905
+ didReorgHappened: false,
6906
+ unfinalizedBlocks
6907
+ };
6908
+ if (latestBlock.number >= block.number) {
6909
+ const ancestor = commonAncestor(block, unfinalizedBlocks) || finalizedBlock;
6910
+ logger.info({
6911
+ msg: `Reorg detected, latestBlock.number >= block.number`,
6912
+ collector,
6913
+ chain_id: chain.id,
6914
+ ancestor: ancestor.number,
6915
+ latest_block_number: latestBlock.number,
6916
+ block_number: block.number
6917
+ });
6918
+ unfinalizedBlocks = unfinalizedBlocks.filter((b) => b.number <= ancestor.number);
6919
+ return {
6920
+ block: ancestor,
6921
+ didReorgHappened: true,
6922
+ unfinalizedBlocks
6923
+ };
6924
+ }
6925
+ if (latestBlock.number + 1n < block.number) {
6926
+ logger.debug({
6927
+ collector,
6928
+ chain_id: chain.id,
6929
+ block_range: [latestBlock.number, block.number],
6930
+ msg: `Missing blocks`
6931
+ });
6932
+ const missingBlockNumbers = (() => {
6933
+ const missingBlockNumbers = [];
6934
+ let start = latestBlock.number + 1n;
6935
+ const threshold = latestBlock.number + BigInt(maxBatchSize) > block.number ? block.number : latestBlock.number + BigInt(maxBatchSize);
6936
+ while (start < threshold) {
6937
+ missingBlockNumbers.push(start);
6938
+ start = start + 1n;
6939
+ }
6940
+ return missingBlockNumbers;
6941
+ })();
6942
+ const missingBlocks = await Promise.all(missingBlockNumbers.map((blockNumber) => retry(async () => await client.getBlock({
6943
+ blockNumber,
6944
+ includeTransactions: false
6945
+ }))));
6946
+ for (const missingBlock of missingBlocks) {
6947
+ const { block: returnedBlock, didReorgHappened, unfinalizedBlocks: newUnfinalizedBlocks } = await reconcile({
6948
+ client,
6949
+ block: missingBlock,
6950
+ unfinalizedBlocks,
6951
+ finalizedBlock,
6952
+ logger,
6953
+ collector,
6954
+ maxBatchSize
6955
+ });
6956
+ if (returnedBlock.number !== missingBlock.number) return {
6957
+ block: returnedBlock,
6958
+ didReorgHappened,
6959
+ unfinalizedBlocks: newUnfinalizedBlocks
6960
+ };
6961
+ }
6962
+ return reconcile({
6963
+ client,
6964
+ block,
6965
+ unfinalizedBlocks,
6966
+ finalizedBlock,
6967
+ logger,
6968
+ collector,
6969
+ maxBatchSize
6970
+ });
6971
+ }
6972
+ if (block.parentHash !== latestBlock.hash) {
6973
+ const ancestor = commonAncestor(block, unfinalizedBlocks) || finalizedBlock;
6974
+ logger.info({
6975
+ msg: `Reorg detected, block parent hash !== latest block hash`,
6976
+ collector,
6977
+ chain_id: chain.id,
6978
+ ancestor: ancestor.number,
6979
+ latest_block_number: latestBlock.number,
6980
+ block_number: block.number
6981
+ });
6982
+ unfinalizedBlocks = unfinalizedBlocks.filter((b) => b.number <= ancestor.number);
6983
+ return {
6984
+ block: ancestor,
6985
+ didReorgHappened: true,
6986
+ unfinalizedBlocks
6987
+ };
6988
+ }
6989
+ const newBlock = {
6990
+ hash: block.hash,
6991
+ number: block.number,
6992
+ parentHash: block.parentHash
6993
+ };
6994
+ unfinalizedBlocks.push(newBlock);
6995
+ return {
6996
+ block: newBlock,
6997
+ didReorgHappened: false,
6998
+ unfinalizedBlocks
6999
+ };
7000
+ };
7001
+
7002
+ //#endregion
7003
+ //#region src/indexer/collectors/Collector.ts
7004
+ const names = [
7005
+ "offers",
7006
+ "consumed_events",
7007
+ "positions",
7008
+ "prices"
7009
+ ];
7010
+ function create$16({ name, collect, client, db, options }) {
7011
+ const admin = create$17({
7012
+ client,
7013
+ db,
7014
+ options
7015
+ });
7016
+ return {
7017
+ name,
7018
+ chain: client.chain,
7019
+ client,
7020
+ db,
7021
+ interval: options.interval ?? 1e4,
7022
+ collect: async function* () {
7023
+ const collector = name;
7024
+ const chain = client.chain;
7025
+ const logger = getLogger();
7026
+ const collectorId = `${client.chain.id.toString()}.collector.${collector}`;
7027
+ const tracer = getTracer(`router.${collectorId}`);
7028
+ logger.info({
7029
+ msg: `Collector started`,
7030
+ collector,
7031
+ chain_id: chain.id
7032
+ });
7033
+ let iterator = null;
7034
+ let lastBlockNumber;
7035
+ while (true) try {
7036
+ if (iterator === null) iterator = await startActiveSpan(tracer, `${collectorId}.init`, async () => {
7037
+ const { collector: collectorBlock } = await db.blocks.init({
7038
+ collectorName: name,
7039
+ chainId: chain.id
7040
+ });
7041
+ lastBlockNumber = collectorBlock.blockNumber;
7042
+ return collect({
7043
+ client,
7044
+ collector: name,
7045
+ epoch: collectorBlock.epoch,
7046
+ lastBlockNumber,
7047
+ db
7048
+ });
7049
+ });
7050
+ if (await startActiveSpan(tracer, `${collectorId}.syncBlock`, async (span) => {
7051
+ const isMaxBlockNumberReached = await admin.syncBlock();
7052
+ span.setAttribute("collector.is_max_block_reached", isMaxBlockNumberReached);
7053
+ return isMaxBlockNumberReached;
7054
+ }) && options.maxBlockNumber !== void 0 && lastBlockNumber !== void 0 && options.maxBlockNumber === lastBlockNumber) return;
7055
+ const { blockNumber, done } = await startActiveSpan(tracer, `${collectorId}.next`, async () => {
7056
+ if (iterator === null) throw new Error("Iterator is not initialized");
7057
+ const { value: blockNumber, done } = await iterator.next();
7058
+ return {
7059
+ blockNumber,
7060
+ done
7061
+ };
7062
+ });
7063
+ if (done) iterator = null;
7064
+ else {
7065
+ lastBlockNumber = blockNumber;
7066
+ yield blockNumber;
7067
+ }
7068
+ } catch (err) {
7069
+ const isError = err instanceof Error;
7070
+ logger.error({
7071
+ msg: "Collector error",
7072
+ collector,
7073
+ chain_id: chain.id,
7074
+ error: isError ? err.message : String(err),
7075
+ stack: isError ? err.stack : void 0
7076
+ });
7077
+ }
7078
+ }
7079
+ };
7080
+ }
7081
+ /** Start a collector with its own polling cadence based on chain head lag.
7082
+ * @param collector - The collector to start.
7083
+ * @returns A function to stop the collector.
7084
+ */
7085
+ function start(collector) {
7086
+ let stopped = false;
7087
+ const it = collector.collect();
7088
+ const logger = getLogger();
7089
+ const collectorId = `${collector.chain.id.toString()}.collector.${collector.name}`;
7090
+ const tracer = getTracer(`router.${collectorId}`);
7091
+ const blocks = collector.db.blocks;
7092
+ let initialized = false;
7093
+ (async () => {
7094
+ while (!stopped) try {
7095
+ await startActiveSpan(tracer, `${collectorId}.poll`, async () => {
7096
+ if (!initialized) {
7097
+ await blocks.init({
7098
+ chainId: collector.chain.id,
7099
+ collectorName: collector.name
7100
+ });
7101
+ initialized = true;
7102
+ }
7103
+ const [block, { blockNumber: collectorBlockNumber }] = await Promise.all([collector.client.getBlock({
7104
+ blockTag: "latest",
7105
+ includeTransactions: false
7106
+ }), blocks.getCollector({
7107
+ collectorName: collector.name,
7108
+ chainId: collector.chain.id
7109
+ })]);
7110
+ const delay = Number(block.number) > collectorBlockNumber + 10 ? 250 : collector.interval;
7111
+ const waitSpan = tracer.startSpan(`${collectorId}.wait`);
7112
+ try {
7113
+ await wait(delay);
7114
+ } finally {
7115
+ waitSpan.end();
7116
+ }
7117
+ await it.next();
7118
+ });
7119
+ } catch (err) {
7120
+ const error = err instanceof Error ? err : new Error(String(err));
7121
+ logger.error({
7122
+ msg: "Collector polling error",
7123
+ collector: collector.name,
7124
+ chain_id: collector.chain.id,
7125
+ err: error
7126
+ });
7127
+ }
7128
+ await it.return();
7129
+ })();
7130
+ return () => {
7131
+ stopped = true;
7132
+ };
7133
+ }
7134
+
7277
7135
  //#endregion
7278
7136
  //#region src/database/domains/Blocks.ts
7279
7137
  /** Postgres implementation. */
@@ -7705,35 +7563,15 @@ async function _getOffers(db, params) {
7705
7563
  AND LOWER(pc.contract) = LOWER(c.position_contract)
7706
7564
  AND LOWER(pc."user") = LOWER(c.position_user)
7707
7565
  ),
7708
- -- Compute contribution per callback in loan terms (with oracle price via LEFT JOIN)
7566
+ -- Compute contribution per callback in loan terms (loan token only collateral positions are not indexed)
7709
7567
  callback_loan_contribution AS (
7710
7568
  SELECT
7711
7569
  cc.*,
7712
7570
  CASE
7713
- -- No lot exists: contribution is 0
7714
7571
  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
7572
+ ELSE LEAST(cc.lot_balance, COALESCE(cc.callback_amount::numeric, cc.lot_balance))
7729
7573
  END AS contribution_in_loan
7730
7574
  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
7575
  ),
7738
7576
  -- Aggregate contributions per offer, deduplicating by position using DISTINCT ON
7739
7577
  offer_contributions AS (
@@ -7770,6 +7608,22 @@ async function _getOffers(db, params) {
7770
7608
  GROUP BY hash, obligation_id, assets, price, obligation_units, obligation_shares, maturity, expiry, start, group_group, buy,
7771
7609
  callback_address, callback_data, block_number, group_chain_id, group_maker,
7772
7610
  consumed, chain_id, loan_token, session
7611
+ UNION ALL
7612
+ -- Sell offers without callbacks: collateral positions not indexed, takeable = assets - consumed
7613
+ SELECT
7614
+ p.hash, p.obligation_id, p.assets, p.price,
7615
+ p.obligation_units, p.obligation_shares,
7616
+ p.maturity, p.expiry, p.start, p.group_group,
7617
+ p.buy, p.callback_address, p.callback_data,
7618
+ p.block_number, p.group_chain_id, p.group_maker,
7619
+ p.consumed, p.chain_id, p.loan_token, p.session,
7620
+ 0 AS total_available
7621
+ FROM paged p
7622
+ WHERE p.buy = false
7623
+ AND NOT EXISTS (
7624
+ SELECT 1 FROM ${offersCallbacks} oc2
7625
+ WHERE oc2.offer_hash = p.hash
7626
+ )
7773
7627
  )
7774
7628
  -- Final SELECT with inline takeable computation
7775
7629
  SELECT
@@ -7792,18 +7646,24 @@ async function _getOffers(db, params) {
7792
7646
  oc.block_number,
7793
7647
  oc.session,
7794
7648
  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,
7649
+ -- takeable: sell offers use assets - consumed directly (collateral positions not indexed yet)
7650
+ CASE WHEN oc.buy = false
7651
+ THEN GREATEST(0, oc.assets::numeric - oc.consumed::numeric)
7652
+ ELSE GREATEST(0, LEAST(
7653
+ oc.assets::numeric - oc.consumed::numeric,
7654
+ COALESCE(oc.total_available, 0)
7655
+ ))
7656
+ END AS takeable,
7800
7657
  c.collaterals
7801
7658
  FROM offer_contributions oc
7802
7659
  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
7660
+ WHERE CASE WHEN oc.buy = false
7661
+ THEN GREATEST(0, oc.assets::numeric - oc.consumed::numeric)
7662
+ ELSE GREATEST(0, LEAST(
7663
+ oc.assets::numeric - oc.consumed::numeric,
7664
+ COALESCE(oc.total_available, 0)
7665
+ ))
7666
+ END > 0
7807
7667
  ORDER BY
7808
7668
  oc.price::numeric ${priceSortDirection === "asc" ? sql`ASC` : sql`DESC`},
7809
7669
  oc.block_number ASC,
@@ -10177,6 +10037,7 @@ async function getOffersQuery(db, parameters) {
10177
10037
  if (cursor !== null && cursor !== void 0) {
10178
10038
  if (!cursor.startsWith("0x") || cursor.length !== 66) throw new Error("Invalid cursor format");
10179
10039
  }
10040
+ const now = Math.floor((Date.now() - 1) / 1e3);
10180
10041
  const collateralsLateral = db.select({ collaterals: sql`COALESCE(
10181
10042
  jsonb_agg(
10182
10043
  jsonb_build_object(
@@ -10190,27 +10051,14 @@ async function getOffersQuery(db, parameters) {
10190
10051
  AND ${obligationCollateralsV2.oracleAddress} = ${oracles.address}`).where(eq(obligationCollateralsV2.obligationId, offers.obligationId)).as("collaterals_lateral");
10191
10052
  const availableLateral = db.select({ available: sql`COALESCE(SUM(
10192
10053
  CASE
10193
- -- If asset is null, position available is 0
10194
10054
  WHEN ${positions.asset} IS NULL THEN 0
10195
-
10196
- -- Position asset matches loan token: no conversion needed
10197
- WHEN ${positions.asset} = ${obligations.loanToken} THEN
10055
+ ELSE
10198
10056
  CASE
10199
10057
  WHEN ${callbacks.amount} IS NULL THEN COALESCE(${positions.balance}, 0)::numeric
10200
10058
  ELSE LEAST(${callbacks.amount}::numeric, COALESCE(${positions.balance}, 0)::numeric)
10201
10059
  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
10060
  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");
10061
+ ), 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))).where(eq(offersCallbacks.offerHash, offers.hash)).as("available_lateral");
10214
10062
  const rows = (await db.select({
10215
10063
  hash: offers.hash,
10216
10064
  maker: offers.groupMaker,
@@ -10232,17 +10080,24 @@ async function getOffersQuery(db, parameters) {
10232
10080
  collaterals: collateralsLateral.collaterals,
10233
10081
  blockNumber: offers.blockNumber,
10234
10082
  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
- )
10083
+ takeable: sql`FLOOR(GREATEST(0,
10084
+ CASE WHEN ${offers.buy} = false
10085
+ THEN ${offers.assets}::numeric - ${groups.consumed}::numeric
10086
+ ELSE LEAST(
10087
+ ${offers.assets}::numeric - ${groups.consumed}::numeric,
10088
+ COALESCE(${availableLateral.available}::numeric, 0)
10089
+ )
10090
+ END
10241
10091
  ))`.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) => {
10092
+ }).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, gte(offers.expiry, now), gte(offers.maturity, now), maker === void 0 ? sql`GREATEST(0,
10093
+ CASE WHEN ${offers.buy} = false
10094
+ THEN ${offers.assets}::numeric - ${groups.consumed}::numeric
10095
+ ELSE LEAST(
10096
+ ${offers.assets}::numeric - ${groups.consumed}::numeric,
10097
+ COALESCE(${availableLateral.available}::numeric, 0)
10098
+ )
10099
+ END
10100
+ ) > 0` : void 0)).orderBy(asc(offers.hash)).limit(limit)).map((row) => {
10246
10101
  return {
10247
10102
  hash: row.hash,
10248
10103
  maker: row.maker,
@@ -10352,35 +10207,6 @@ async function getUserPositions(queryParameters, db) {
10352
10207
  }
10353
10208
  }
10354
10209
 
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
10210
  //#endregion
10385
10211
  //#region src/api/Api.ts
10386
10212
  function from(config) {
@@ -10450,24 +10276,9 @@ function serve$1(parameters) {
10450
10276
  const { statusCode, body } = await gatekeeper.validate(reqBody);
10451
10277
  return c.json(body, statusCode);
10452
10278
  } 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"));
10279
+ const failure$2 = failure(err);
10467
10280
  return c.json(failure$2.body, failure$2.statusCode);
10468
10281
  }
10469
- const { statusCode, body: responseBody } = await resolveCallbackTypes$1(body, chainRegistry.list());
10470
- return c.json(responseBody, statusCode);
10471
10282
  });
10472
10283
  app.get("/v1/users/:userAddress/positions", async (c) => {
10473
10284
  const query = c.req.query();
@@ -10499,8 +10310,8 @@ function serve$1(parameters) {
10499
10310
  const { statusCode, body } = await gatekeeper.getConfigRules(c.req.query());
10500
10311
  return c.json(body, statusCode);
10501
10312
  } catch (err) {
10502
- const failure$4 = failure(err);
10503
- return c.json(failure$4.body, failure$4.statusCode);
10313
+ const failure$1 = failure(err);
10314
+ return c.json(failure$1.body, failure$1.statusCode);
10504
10315
  }
10505
10316
  });
10506
10317
  app.get("/docs/openapi", async (c) => c.text(JSON.stringify(await getSwaggerJson()), 200, { "Content-Type": "application/json; charset=utf-8" }));
@@ -10591,23 +10402,11 @@ function createHttpClient(config) {
10591
10402
  issues: []
10592
10403
  };
10593
10404
  };
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
10405
  return {
10606
10406
  baseUrl,
10607
10407
  validate,
10608
10408
  getConfigRules,
10609
- isAllowed,
10610
- getCallbackTypes
10409
+ isAllowed
10611
10410
  };
10612
10411
  }
10613
10412
  function mergeHeaders(base, extra) {
@@ -10843,35 +10642,17 @@ function createMockOffers(parameters) {
10843
10642
  lltv
10844
10643
  }));
10845
10644
  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
10645
  return from$12({
10853
10646
  ...offer,
10854
10647
  loanToken,
10855
10648
  collaterals,
10856
10649
  callback: {
10857
- address: callbackAddress,
10858
- data: callbackData
10650
+ address: zeroAddress,
10651
+ data: "0x"
10859
10652
  }
10860
10653
  });
10861
10654
  });
10862
10655
  }
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
10656
  function seedMockOracles(offers, price, blockNumber) {
10876
10657
  const oracleMap = /* @__PURE__ */ new Map();
10877
10658
  for (const offer of offers) for (const collateral of offer.collaterals) {
@@ -10886,8 +10667,6 @@ function seedMockOracles(offers, price, blockNumber) {
10886
10667
  }
10887
10668
  return Array.from(oracleMap.values());
10888
10669
  }
10889
- const BUY_CALLBACK_ADDRESS = "0x3333333333333333333333333333333333333333";
10890
- const SELL_CALLBACK_ADDRESS = "0x1111111111111111111111111111111111111111";
10891
10670
  async function seedOffers(parameters) {
10892
10671
  const { db, offers, blockNumber } = parameters;
10893
10672
  if (offers.length === 0) return;
@@ -10904,14 +10683,10 @@ async function seedOffers(parameters) {
10904
10683
  }]);
10905
10684
  }
10906
10685
  async function seedOfferAssociations(parameters) {
10907
- const { db, gatekeeper, offers, blockNumber } = parameters;
10686
+ const { db, offers, blockNumber } = parameters;
10908
10687
  if (offers.length === 0) return;
10909
10688
  const { callbacks, positions, lots } = buildOfferAssociationsFromOffers({
10910
10689
  offers,
10911
- callbackTypes: await resolveCallbackTypes({
10912
- gatekeeper,
10913
- offers
10914
- }),
10915
10690
  blockNumber
10916
10691
  });
10917
10692
  if (positions.length > 0) await db.positions.upsert(positions);
@@ -10954,83 +10729,40 @@ function buildOfferDependencies(parameters) {
10954
10729
  groups: Array.from(groupsByKey.values())
10955
10730
  };
10956
10731
  }
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
10732
  function buildOfferAssociationsFromOffers(parameters) {
10987
- const { offers, callbackTypes, blockNumber } = parameters;
10733
+ const { offers, blockNumber } = parameters;
10988
10734
  const callbacks = [];
10989
10735
  const positions = [];
10990
10736
  const lots = [];
10991
10737
  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;
10738
+ if (!offer.buy) continue;
10739
+ const loanToken = offer.loanToken.toLowerCase();
10740
+ const offerHash = hash(offer);
10741
+ positions.push(from$10({
10742
+ chainId: offer.chainId,
10743
+ contract: loanToken,
10744
+ user: offer.maker,
10745
+ type: Type.ERC20,
10746
+ asset: loanToken,
10747
+ balance: offer.assets,
10748
+ blockNumber
10749
+ }));
11004
10750
  callbacks.push({
11005
- offerHash: hash(offer),
11006
- callbacks: decoded.map((callback) => ({
10751
+ offerHash,
10752
+ callbacks: [{
11007
10753
  chainId: offer.chainId,
11008
- contract: callback.contract,
10754
+ contract: loanToken,
11009
10755
  user: offer.maker,
11010
- amount: callback.amount
11011
- }))
10756
+ amount: offer.assets
10757
+ }]
10758
+ });
10759
+ lots.push({
10760
+ positionChainId: offer.chainId,
10761
+ positionContract: loanToken,
10762
+ positionUser: offer.maker,
10763
+ group: offer.group,
10764
+ size: offer.assets
11012
10765
  });
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
10766
  }
11035
10767
  return {
11036
10768
  callbacks,