@morpho-dev/router 0.9.0 → 0.10.0

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.
@@ -844,7 +844,7 @@ const reconcile = async (parameters) => {
844
844
  //#region src/indexer/collectors/Collector.ts
845
845
  const names = [
846
846
  "offers",
847
- "consumed_events",
847
+ "morpho_v2",
848
848
  "positions",
849
849
  "prices"
850
850
  ];
@@ -1221,7 +1221,7 @@ var Chain_exports = /* @__PURE__ */ __exportAll({
1221
1221
  MissingBlockNumberError: () => MissingBlockNumberError,
1222
1222
  chainIds: () => chainIds,
1223
1223
  chainNames: () => chainNames,
1224
- chains: () => chains$2,
1224
+ chains: () => chains$1,
1225
1225
  getChain: () => getChain,
1226
1226
  getWhitelistedChains: () => getWhitelistedChains,
1227
1227
  streamLogs: () => streamLogs
@@ -1238,17 +1238,17 @@ const chainNameLookup = new Map(Object.entries(ChainId).map(([key, value]) => [v
1238
1238
  function getChain(chainId) {
1239
1239
  const chainName = chainNameLookup.get(chainId);
1240
1240
  if (!chainName) return void 0;
1241
- return chains$2[chainName];
1241
+ return chains$1[chainName];
1242
1242
  }
1243
1243
  const getWhitelistedChains = () => {
1244
1244
  return [
1245
- chains$2.ethereum,
1246
- chains$2.base,
1247
- chains$2["ethereum-virtual-testnet"],
1248
- chains$2.anvil
1245
+ chains$1.ethereum,
1246
+ chains$1.base,
1247
+ chains$1["ethereum-virtual-testnet"],
1248
+ chains$1.anvil
1249
1249
  ];
1250
1250
  };
1251
- const chains$2 = {
1251
+ const chains$1 = {
1252
1252
  ethereum: {
1253
1253
  ...viem_chains.mainnet,
1254
1254
  id: ChainId.ETHEREUM,
@@ -1285,16 +1285,16 @@ const chains$2 = {
1285
1285
  name: "base",
1286
1286
  custom: {
1287
1287
  morpho: {
1288
- address: "0x0000000000000000000000000000000000000000",
1289
- blockCreated: 0
1288
+ address: "0x3F067BC9D8898F6ec02D6480c3fF1026E512BcBF",
1289
+ blockCreated: 41799989
1290
1290
  },
1291
1291
  morphoBlue: {
1292
1292
  address: "0xBBBBBbbBBb9cC5e90e3b3Af64bdAF62C37EEFFCb",
1293
1293
  blockCreated: 0
1294
1294
  },
1295
1295
  mempool: {
1296
- address: "0x0000000000000000000000000000000000000000",
1297
- blockCreated: 35449942
1296
+ address: "0xd25c7512EA5035bef4F18c708C0862E1B6151765",
1297
+ blockCreated: 41800226
1298
1298
  },
1299
1299
  vaults: { factories: {
1300
1300
  v1_0: {
@@ -1618,9 +1618,24 @@ const LLTVSchema = zod.bigint({ coerce: true }).refine((lltv) => {
1618
1618
  var Collateral_exports = /* @__PURE__ */ __exportAll({
1619
1619
  CollateralSchema: () => CollateralSchema,
1620
1620
  CollateralsSchema: () => CollateralsSchema,
1621
+ abi: () => abi$1,
1621
1622
  from: () => from$17,
1622
1623
  random: () => random$3
1623
1624
  });
1625
+ const abi$1 = [
1626
+ {
1627
+ type: "address",
1628
+ name: "token"
1629
+ },
1630
+ {
1631
+ type: "uint256",
1632
+ name: "lltv"
1633
+ },
1634
+ {
1635
+ type: "address",
1636
+ name: "oracle"
1637
+ }
1638
+ ];
1624
1639
  const CollateralSchema = zod.object({
1625
1640
  asset: zod.string().transform(transformAddress),
1626
1641
  oracle: zod.string().transform(transformAddress),
@@ -1725,80 +1740,6 @@ var DenominatorIsZeroError = class extends BaseError {
1725
1740
  }
1726
1741
  };
1727
1742
 
1728
- //#endregion
1729
- //#region src/core/Liquidity.ts
1730
- var Liquidity_exports = /* @__PURE__ */ __exportAll({
1731
- calculateMaxDebt: () => calculateMaxDebt,
1732
- generateAllowancePoolId: () => generateAllowancePoolId,
1733
- generateBalancePoolId: () => generateBalancePoolId,
1734
- generateDebtPoolId: () => generateDebtPoolId,
1735
- generateMarketLiquidityPoolId: () => generateMarketLiquidityPoolId,
1736
- generateObligationCollateralPoolId: () => generateObligationCollateralPoolId,
1737
- generateUserVaultPositionPoolId: () => generateUserVaultPositionPoolId,
1738
- generateVaultPositionPoolId: () => generateVaultPositionPoolId
1739
- });
1740
- /**
1741
- * Calculate maximum debt capacity from collateral amount.
1742
- * @param amount - Collateral amount
1743
- * @param oraclePrice - Oracle price (scaled to 36 decimals)
1744
- * @param lltv - Loan-to-value ratio (scaled to 18 decimals)
1745
- * @returns Maximum debt capacity
1746
- */
1747
- function calculateMaxDebt(amount, oraclePrice, lltv) {
1748
- return amount * oraclePrice / 10n ** 36n * lltv / 10n ** 18n;
1749
- }
1750
- /**
1751
- * Generate pool ID for balance pools.
1752
- */
1753
- function generateBalancePoolId(parameters) {
1754
- const { user, chainId, token } = parameters;
1755
- return `${user}-${chainId.toString()}-${token}-balance`.toLowerCase();
1756
- }
1757
- /**
1758
- * Generate pool ID for allowance pools.
1759
- */
1760
- function generateAllowancePoolId(parameters) {
1761
- const { user, chainId, token } = parameters;
1762
- return `${user}-${chainId.toString()}-${token}-allowance`.toLowerCase();
1763
- }
1764
- /**
1765
- * Generate pool ID for obligation collateral pools.
1766
- * Obligation collateral pools represent collateral already deposited in the obligation.
1767
- * These pools are shared across all offers with the same obligation.
1768
- */
1769
- function generateObligationCollateralPoolId(parameters) {
1770
- const { user, chainId, obligationId, token } = parameters;
1771
- return `${user}-${chainId.toString()}-${obligationId}-${token}-obligation-collateral`.toLowerCase();
1772
- }
1773
- /**
1774
- * Generate pool ID for debt pools.
1775
- */
1776
- function generateDebtPoolId(parameters) {
1777
- const { user, chainId, obligationId } = parameters;
1778
- return `${user}-${chainId.toString()}-${obligationId}-debt`.toLowerCase();
1779
- }
1780
- /**
1781
- * Generate pool ID for user position in a vault.
1782
- */
1783
- function generateUserVaultPositionPoolId(parameters) {
1784
- const { user, chainId, vault } = parameters;
1785
- return `${user}-${chainId.toString()}-${vault}-user-vault-position`.toLowerCase();
1786
- }
1787
- /**
1788
- * Generate pool ID for vault position in a market.
1789
- */
1790
- function generateVaultPositionPoolId(parameters) {
1791
- const { vault, chainId, marketId } = parameters;
1792
- return `${vault}-${chainId.toString()}-${marketId}-vault-position`.toLowerCase();
1793
- }
1794
- /**
1795
- * Generate pool ID for market total liquidity.
1796
- */
1797
- function generateMarketLiquidityPoolId(parameters) {
1798
- const { chainId, marketId } = parameters;
1799
- return `${chainId.toString()}-${marketId}-market-liquidity`.toLowerCase();
1800
- }
1801
-
1802
1743
  //#endregion
1803
1744
  //#region src/core/Maturity.ts
1804
1745
  var Maturity_exports = /* @__PURE__ */ __exportAll({
@@ -1931,18 +1872,38 @@ var Obligation_exports = /* @__PURE__ */ __exportAll({
1931
1872
  CollateralsAreNotSortedError: () => CollateralsAreNotSortedError,
1932
1873
  InvalidObligationError: () => InvalidObligationError,
1933
1874
  ObligationSchema: () => ObligationSchema,
1875
+ abi: () => abi,
1934
1876
  from: () => from$15,
1935
1877
  fromOffer: () => fromOffer$1,
1936
1878
  fromSnakeCase: () => fromSnakeCase$2,
1937
- id: () => id,
1938
- random: () => random$2
1879
+ key: () => key,
1880
+ random: () => random$2,
1881
+ tupleAbi: () => tupleAbi
1939
1882
  });
1940
1883
  const ObligationSchema = zod.object({
1941
- chainId: zod.number().min(0).max(Number.MAX_SAFE_INTEGER),
1942
1884
  loanToken: zod.string().transform(transformAddress),
1943
1885
  collaterals: CollateralsSchema,
1944
1886
  maturity: MaturitySchema
1945
1887
  });
1888
+ const abi = [
1889
+ {
1890
+ type: "address",
1891
+ name: "loanToken"
1892
+ },
1893
+ {
1894
+ type: "tuple[]",
1895
+ name: "collaterals",
1896
+ components: abi$1
1897
+ },
1898
+ {
1899
+ type: "uint256",
1900
+ name: "maturity"
1901
+ }
1902
+ ];
1903
+ const tupleAbi = [{
1904
+ type: "tuple",
1905
+ components: abi
1906
+ }];
1946
1907
  /**
1947
1908
  * Creates an obligation from the given parameters.
1948
1909
  * @constructor
@@ -1953,7 +1914,6 @@ const ObligationSchema = zod.object({
1953
1914
  * @example
1954
1915
  * ```ts
1955
1916
  * const obligation = Obligation.from({
1956
- * chainId: 1,
1957
1917
  * loanToken: privateKeyToAccount(generatePrivateKey()).address,
1958
1918
  * collaterals: [
1959
1919
  * Collateral.from({
@@ -1973,7 +1933,6 @@ function from$15(parameters) {
1973
1933
  maturity: from$16(parameters.maturity)
1974
1934
  });
1975
1935
  return {
1976
- chainId: parsedObligation.chainId,
1977
1936
  loanToken: parsedObligation.loanToken.toLowerCase(),
1978
1937
  collaterals: parsedObligation.collaterals.sort((a, b) => a.asset.localeCompare(b.asset)),
1979
1938
  maturity: parsedObligation.maturity
@@ -1992,49 +1951,27 @@ function fromSnakeCase$2(input) {
1992
1951
  return from$15(fromSnakeCase$3(input));
1993
1952
  }
1994
1953
  /**
1995
- * Calculates the obligation id based on the smart contract's Obligation struct.
1996
- * The id is computed as keccak256(abi.encode(chainId, loanToken, collaterals, maturity)).
1954
+ * Calculates a canonical key for an obligation payload.
1955
+ * The key is computed as keccak256(abi.encode(loanToken, collaterals, maturity)).
1997
1956
  * @throws If the collaterals are not sorted alphabetically by address. {@link CollateralsAreNotSortedError}
1998
- * @param parameters - {@link id.Parameters}
1999
- * @returns The obligation id as a 32-byte hex string. {@link id.ReturnType}
1957
+ * @param parameters - {@link key.Parameters}
1958
+ * @returns The obligation key as a 32-byte hex string. {@link key.ReturnType}
2000
1959
  *
2001
1960
  * @example
2002
1961
  * ```ts
2003
1962
  * const obligation = Obligation.random();
2004
- * const id = Obligation.id(obligation);
2005
- * console.log(id); // 0x1234567890123456789012345678901234567890123456789012345678901234
1963
+ * const key = Obligation.key(obligation);
1964
+ * console.log(key); // 0x1234567890123456789012345678901234567890123456789012345678901234
2006
1965
  * ```
2007
1966
  */
2008
- function id(parameters) {
1967
+ function key(parameters) {
2009
1968
  let lastAsset = "";
2010
1969
  for (const collateral of parameters.collaterals) {
2011
1970
  const newAsset = collateral.asset.toLowerCase();
2012
1971
  if (newAsset.localeCompare(lastAsset) < 0) throw new CollateralsAreNotSortedError();
2013
1972
  lastAsset = newAsset;
2014
1973
  }
2015
- return (0, viem.keccak256)((0, viem.encodeAbiParameters)([
2016
- { type: "uint256" },
2017
- { type: "address" },
2018
- {
2019
- type: "tuple[]",
2020
- components: [
2021
- {
2022
- type: "address",
2023
- name: "token"
2024
- },
2025
- {
2026
- type: "uint256",
2027
- name: "lltv"
2028
- },
2029
- {
2030
- type: "address",
2031
- name: "oracle"
2032
- }
2033
- ]
2034
- },
2035
- { type: "uint256" }
2036
- ], [
2037
- BigInt(parameters.chainId),
1974
+ return (0, viem.keccak256)((0, viem.encodeAbiParameters)(abi, [
2038
1975
  parameters.loanToken.toLowerCase(),
2039
1976
  parameters.collaterals.map((c) => ({
2040
1977
  token: c.asset.toLowerCase(),
@@ -2055,7 +1992,6 @@ function id(parameters) {
2055
1992
  */
2056
1993
  function random$2() {
2057
1994
  return from$15({
2058
- chainId: 1,
2059
1995
  loanToken: address(),
2060
1996
  collaterals: [random$3()],
2061
1997
  maturity: from$16("end_of_next_quarter")
@@ -2070,7 +2006,6 @@ function random$2() {
2070
2006
  */
2071
2007
  function fromOffer$1(offer) {
2072
2008
  return from$15({
2073
- chainId: offer.chainId,
2074
2009
  loanToken: offer.loanToken,
2075
2010
  collaterals: offer.collaterals,
2076
2011
  maturity: offer.maturity
@@ -2089,6 +2024,121 @@ var CollateralsAreNotSortedError = class extends BaseError {
2089
2024
  }
2090
2025
  };
2091
2026
 
2027
+ //#endregion
2028
+ //#region src/core/Id.ts
2029
+ var Id_exports = /* @__PURE__ */ __exportAll({
2030
+ creationCode: () => creationCode,
2031
+ toId: () => toId
2032
+ });
2033
+ const CREATION_CODE_PREFIX = "0x603f380380603f5f395ff3";
2034
+ /**
2035
+ * Builds the same creation code as `IdLib.creationCode` in Solidity.
2036
+ *
2037
+ * Layout: `prefix (11 bytes) + chainId (32 bytes) + morphoV2 (20 bytes) + abi.encode(obligation)`.
2038
+ *
2039
+ * @param parameters - {@link creationCode.Parameters}
2040
+ * @returns The CREATE2 init code bytes. {@link creationCode.ReturnType}
2041
+ */
2042
+ function creationCode(parameters) {
2043
+ const encodedObligation = (0, viem.encodeAbiParameters)(tupleAbi, [{
2044
+ loanToken: parameters.obligation.loanToken.toLowerCase(),
2045
+ collaterals: parameters.obligation.collaterals.map((collateral) => ({
2046
+ token: collateral.asset.toLowerCase(),
2047
+ lltv: collateral.lltv,
2048
+ oracle: collateral.oracle.toLowerCase()
2049
+ })),
2050
+ maturity: BigInt(parameters.obligation.maturity)
2051
+ }]);
2052
+ return (0, viem.concatHex)([
2053
+ CREATION_CODE_PREFIX,
2054
+ (0, viem.numberToHex)(BigInt(parameters.chainId), { size: 32 }),
2055
+ parameters.morphoV2.toLowerCase(),
2056
+ encodedObligation
2057
+ ]);
2058
+ }
2059
+ /**
2060
+ * Computes the same id as `IdLib.toId` in Solidity.
2061
+ * @param parameters - {@link toId.Parameters}
2062
+ * @returns The obligation id. {@link toId.ReturnType}
2063
+ */
2064
+ function toId(parameters) {
2065
+ return (0, viem.keccak256)(creationCode(parameters));
2066
+ }
2067
+
2068
+ //#endregion
2069
+ //#region src/core/Liquidity.ts
2070
+ var Liquidity_exports = /* @__PURE__ */ __exportAll({
2071
+ calculateMaxDebt: () => calculateMaxDebt,
2072
+ generateAllowancePoolId: () => generateAllowancePoolId,
2073
+ generateBalancePoolId: () => generateBalancePoolId,
2074
+ generateDebtPoolId: () => generateDebtPoolId,
2075
+ generateMarketLiquidityPoolId: () => generateMarketLiquidityPoolId,
2076
+ generateObligationCollateralPoolId: () => generateObligationCollateralPoolId,
2077
+ generateUserVaultPositionPoolId: () => generateUserVaultPositionPoolId,
2078
+ generateVaultPositionPoolId: () => generateVaultPositionPoolId
2079
+ });
2080
+ /**
2081
+ * Calculate maximum debt capacity from collateral amount.
2082
+ * @param amount - Collateral amount
2083
+ * @param oraclePrice - Oracle price (scaled to 36 decimals)
2084
+ * @param lltv - Loan-to-value ratio (scaled to 18 decimals)
2085
+ * @returns Maximum debt capacity
2086
+ */
2087
+ function calculateMaxDebt(amount, oraclePrice, lltv) {
2088
+ return amount * oraclePrice / 10n ** 36n * lltv / 10n ** 18n;
2089
+ }
2090
+ /**
2091
+ * Generate pool ID for balance pools.
2092
+ */
2093
+ function generateBalancePoolId(parameters) {
2094
+ const { user, chainId, token } = parameters;
2095
+ return `${user}-${chainId.toString()}-${token}-balance`.toLowerCase();
2096
+ }
2097
+ /**
2098
+ * Generate pool ID for allowance pools.
2099
+ */
2100
+ function generateAllowancePoolId(parameters) {
2101
+ const { user, chainId, token } = parameters;
2102
+ return `${user}-${chainId.toString()}-${token}-allowance`.toLowerCase();
2103
+ }
2104
+ /**
2105
+ * Generate pool ID for obligation collateral pools.
2106
+ * Obligation collateral pools represent collateral already deposited in the obligation.
2107
+ * These pools are shared across all offers with the same obligation.
2108
+ */
2109
+ function generateObligationCollateralPoolId(parameters) {
2110
+ const { user, chainId, obligationId, token } = parameters;
2111
+ return `${user}-${chainId.toString()}-${obligationId}-${token}-obligation-collateral`.toLowerCase();
2112
+ }
2113
+ /**
2114
+ * Generate pool ID for debt pools.
2115
+ */
2116
+ function generateDebtPoolId(parameters) {
2117
+ const { user, chainId, obligationId } = parameters;
2118
+ return `${user}-${chainId.toString()}-${obligationId}-debt`.toLowerCase();
2119
+ }
2120
+ /**
2121
+ * Generate pool ID for user position in a vault.
2122
+ */
2123
+ function generateUserVaultPositionPoolId(parameters) {
2124
+ const { user, chainId, vault } = parameters;
2125
+ return `${user}-${chainId.toString()}-${vault}-user-vault-position`.toLowerCase();
2126
+ }
2127
+ /**
2128
+ * Generate pool ID for vault position in a market.
2129
+ */
2130
+ function generateVaultPositionPoolId(parameters) {
2131
+ const { vault, chainId, marketId } = parameters;
2132
+ return `${vault}-${chainId.toString()}-${marketId}-vault-position`.toLowerCase();
2133
+ }
2134
+ /**
2135
+ * Generate pool ID for market total liquidity.
2136
+ */
2137
+ function generateMarketLiquidityPoolId(parameters) {
2138
+ const { chainId, marketId } = parameters;
2139
+ return `${chainId.toString()}-${marketId}-market-liquidity`.toLowerCase();
2140
+ }
2141
+
2092
2142
  //#endregion
2093
2143
  //#region src/core/Offer.ts
2094
2144
  var Offer_exports = /* @__PURE__ */ __exportAll({
@@ -2097,17 +2147,19 @@ var Offer_exports = /* @__PURE__ */ __exportAll({
2097
2147
  Status: () => Status,
2098
2148
  consumedEvent: () => consumedEvent,
2099
2149
  decode: () => decode$1,
2100
- domain: () => domain,
2101
2150
  encode: () => encode$1,
2102
2151
  from: () => from$14,
2103
2152
  fromSnakeCase: () => fromSnakeCase$1,
2104
2153
  hash: () => hash,
2154
+ liquidateEvent: () => liquidateEvent,
2105
2155
  obligationId: () => obligationId,
2106
2156
  random: () => random$1,
2157
+ repayEvent: () => repayEvent,
2107
2158
  serialize: () => serialize,
2159
+ supplyCollateralEvent: () => supplyCollateralEvent,
2108
2160
  takeEvent: () => takeEvent,
2109
2161
  toSnakeCase: () => toSnakeCase,
2110
- types: () => types
2162
+ withdrawCollateralEvent: () => withdrawCollateralEvent
2111
2163
  });
2112
2164
  /** Internal symbol for caching the computed hash. */
2113
2165
  const HASH_CACHE = Symbol("offer.hash");
@@ -2137,7 +2189,6 @@ const OfferSchema = () => {
2137
2189
  zod.bigint()
2138
2190
  ]).optional().default("0x0000000000000000000000000000000000000000000000000000000000000000").transform(transformBytes32),
2139
2191
  buy: zod.boolean(),
2140
- chainId: zod.number().min(0).max(Number.MAX_SAFE_INTEGER),
2141
2192
  loanToken: zod.string().transform(transformAddress),
2142
2193
  collaterals: CollateralsSchema,
2143
2194
  callback: zod.object({
@@ -2206,7 +2257,6 @@ const serialize = (offer) => ({
2206
2257
  group: offer.group,
2207
2258
  session: offer.session,
2208
2259
  buy: offer.buy,
2209
- chainId: offer.chainId,
2210
2260
  loanToken: offer.loanToken,
2211
2261
  collaterals: offer.collaterals.map((c) => ({
2212
2262
  asset: c.asset,
@@ -2227,7 +2277,6 @@ const serialize = (offer) => ({
2227
2277
  * @returns {Offer} A randomly generated Offer object.
2228
2278
  */
2229
2279
  function random$1(config) {
2230
- const chain = config?.chains ? config.chains[int(config.chains.length)] : chains$2.ethereum;
2231
2280
  const loanToken = config?.loanTokens ? config.loanTokens[int(config.loanTokens.length)] : address();
2232
2281
  const collateralCandidates = config?.collateralTokens ? config.collateralTokens.filter((a) => a !== loanToken) : [address()];
2233
2282
  collateralCandidates[int(collateralCandidates.length)];
@@ -2273,7 +2322,6 @@ function random$1(config) {
2273
2322
  group: config?.group ?? hex(32),
2274
2323
  session: config?.session ?? hex(32),
2275
2324
  buy,
2276
- chainId: chain.id,
2277
2325
  loanToken,
2278
2326
  collaterals: config?.collaterals ?? Array.from({ length: int(3) + 1 }, () => ({
2279
2327
  ...random$3(),
@@ -2293,150 +2341,36 @@ const weightedChoice = (pairs) => {
2293
2341
  return pairs[0][0];
2294
2342
  };
2295
2343
  /**
2296
- * Creates an EIP-712 domain object.
2297
- * @param chainId - The chain ID.
2298
- * @returns The EIP-712 domain object.
2299
- */
2300
- const domain = (chainId) => ({
2301
- chainId: BigInt(chainId),
2302
- verifyingContract: viem.zeroAddress
2303
- });
2304
- /**
2305
- * The EIP-712 types for the offer.
2306
- * @warning The ordering of the types should NEVER be changed. The offer hash is computed based on the order of the types.
2307
- * @returns The EIP-712 types.
2344
+ * Computes the canonical chain-agnostic offer hash.
2345
+ * The hash is `keccak256(abi.encode(offer))` using {@link encode}.
2346
+ *
2347
+ * @param offer - Offer payload to hash.
2348
+ * @returns 32-byte offer hash.
2308
2349
  */
2309
- const types = {
2310
- EIP712Domain: [{
2311
- name: "chainId",
2312
- type: "uint256"
2313
- }, {
2314
- name: "verifyingContract",
2315
- type: "address"
2316
- }],
2317
- Offer: [
2318
- {
2319
- name: "maker",
2320
- type: "address"
2321
- },
2322
- {
2323
- name: "assets",
2324
- type: "uint256"
2325
- },
2326
- {
2327
- name: "obligationUnits",
2328
- type: "uint256"
2329
- },
2330
- {
2331
- name: "obligationShares",
2332
- type: "uint256"
2333
- },
2334
- {
2335
- name: "tick",
2336
- type: "uint256"
2337
- },
2338
- {
2339
- name: "maturity",
2340
- type: "uint256"
2341
- },
2342
- {
2343
- name: "expiry",
2344
- type: "uint256"
2345
- },
2346
- {
2347
- name: "group",
2348
- type: "bytes32"
2349
- },
2350
- {
2351
- name: "session",
2352
- type: "bytes32"
2353
- },
2354
- {
2355
- name: "buy",
2356
- type: "bool"
2357
- },
2358
- {
2359
- name: "loanToken",
2360
- type: "address"
2361
- },
2362
- {
2363
- name: "collaterals",
2364
- type: "Collateral[]"
2365
- },
2366
- {
2367
- name: "callback",
2368
- type: "Callback"
2369
- },
2370
- {
2371
- name: "receiverIfMakerIsSeller",
2372
- type: "address"
2373
- }
2374
- ],
2375
- Collateral: [
2376
- {
2377
- name: "asset",
2378
- type: "address"
2379
- },
2380
- {
2381
- name: "oracle",
2382
- type: "address"
2383
- },
2384
- {
2385
- name: "lltv",
2386
- type: "uint256"
2387
- }
2388
- ],
2389
- Callback: [{
2390
- name: "address",
2391
- type: "address"
2392
- }, {
2393
- name: "data",
2394
- type: "bytes"
2395
- }]
2396
- };
2397
2350
  function hash(offer) {
2398
2351
  const cached = offer[HASH_CACHE];
2399
2352
  if (cached) return cached;
2400
- const computed = (0, viem.hashTypedData)({
2401
- domain: domain(offer.chainId),
2402
- message: {
2403
- maker: offer.maker.toLowerCase(),
2404
- assets: offer.assets,
2405
- obligationUnits: offer.obligationUnits,
2406
- obligationShares: offer.obligationShares,
2407
- tick: BigInt(offer.tick),
2408
- maturity: BigInt(offer.maturity),
2409
- expiry: BigInt(offer.expiry),
2410
- group: offer.group,
2411
- session: offer.session,
2412
- buy: offer.buy,
2413
- loanToken: offer.loanToken.toLowerCase(),
2414
- collaterals: offer.collaterals,
2415
- callback: {
2416
- address: offer.callback.address.toLowerCase(),
2417
- data: offer.callback.data
2418
- },
2419
- receiverIfMakerIsSeller: offer.receiverIfMakerIsSeller.toLowerCase()
2420
- },
2421
- primaryType: "Offer",
2422
- types
2423
- });
2353
+ const computed = (0, viem.keccak256)(encode$1(offer));
2424
2354
  offer[HASH_CACHE] = computed;
2425
2355
  return computed;
2426
2356
  }
2427
2357
  /**
2428
- * Calculates the obligation id for an offer based on the smart contract's Obligation struct.
2429
- * The id is computed as keccak256(abi.encode(chainId, loanToken, collaterals (sorted by token address), maturity)).
2358
+ * Calculates the onchain obligation id for an offer.
2359
+ * The id is computed with {@link Id.toId}.
2430
2360
  * @param offer - The offer to calculate the obligation id for.
2361
+ * @param parameters - The chain context used by the onchain id function.
2431
2362
  * @returns The obligation id as a 32-byte hex string.
2432
2363
  */
2433
- function obligationId(offer) {
2434
- return id(from$15({
2435
- chainId: offer.chainId,
2436
- loanToken: offer.loanToken,
2437
- collaterals: offer.collaterals,
2438
- maturity: offer.maturity
2439
- }));
2364
+ function obligationId(offer, parameters) {
2365
+ return toId({
2366
+ chainId: parameters.chainId,
2367
+ morphoV2: parameters.morphoV2,
2368
+ obligation: from$15({
2369
+ loanToken: offer.loanToken,
2370
+ collaterals: offer.collaterals,
2371
+ maturity: offer.maturity
2372
+ })
2373
+ });
2440
2374
  }
2441
2375
  const OfferAbi = [
2442
2376
  {
@@ -2479,10 +2413,6 @@ const OfferAbi = [
2479
2413
  name: "buy",
2480
2414
  type: "bool"
2481
2415
  },
2482
- {
2483
- name: "chainId",
2484
- type: "uint256"
2485
- },
2486
2416
  {
2487
2417
  name: "loanToken",
2488
2418
  type: "address"
@@ -2537,7 +2467,6 @@ function encode$1(offer) {
2537
2467
  offer.group,
2538
2468
  offer.session,
2539
2469
  offer.buy,
2540
- BigInt(offer.chainId),
2541
2470
  offer.loanToken,
2542
2471
  BigInt(offer.start),
2543
2472
  offer.collaterals,
@@ -2563,10 +2492,9 @@ function decode$1(data) {
2563
2492
  group: decoded[7],
2564
2493
  session: decoded[8],
2565
2494
  buy: decoded[9],
2566
- chainId: Number(decoded[10]),
2567
- loanToken: decoded[11],
2568
- start: Number(decoded[12]),
2569
- collaterals: decoded[13].map((c) => {
2495
+ loanToken: decoded[10],
2496
+ start: Number(decoded[11]),
2497
+ collaterals: decoded[12].map((c) => {
2570
2498
  return from$17({
2571
2499
  asset: c.asset,
2572
2500
  oracle: c.oracle,
@@ -2574,23 +2502,145 @@ function decode$1(data) {
2574
2502
  });
2575
2503
  }),
2576
2504
  callback: {
2577
- address: decoded[14].address,
2578
- data: decoded[14].data
2505
+ address: decoded[13].address,
2506
+ data: decoded[13].data
2507
+ },
2508
+ receiverIfMakerIsSeller: decoded[14]
2509
+ });
2510
+ }
2511
+ /**
2512
+ * ABI for the Take event emitted by the Morpho V2 contract.
2513
+ */
2514
+ const takeEvent = {
2515
+ type: "event",
2516
+ name: "Take",
2517
+ inputs: [
2518
+ {
2519
+ name: "caller",
2520
+ type: "address",
2521
+ indexed: false,
2522
+ internalType: "address"
2523
+ },
2524
+ {
2525
+ name: "id",
2526
+ type: "bytes32",
2527
+ indexed: true,
2528
+ internalType: "bytes32"
2529
+ },
2530
+ {
2531
+ name: "maker",
2532
+ type: "address",
2533
+ indexed: true,
2534
+ internalType: "address"
2535
+ },
2536
+ {
2537
+ name: "taker",
2538
+ type: "address",
2539
+ indexed: true,
2540
+ internalType: "address"
2541
+ },
2542
+ {
2543
+ name: "offerIsBuy",
2544
+ type: "bool",
2545
+ indexed: false,
2546
+ internalType: "bool"
2547
+ },
2548
+ {
2549
+ name: "buyerAssets",
2550
+ type: "uint256",
2551
+ indexed: false,
2552
+ internalType: "uint256"
2553
+ },
2554
+ {
2555
+ name: "sellerAssets",
2556
+ type: "uint256",
2557
+ indexed: false,
2558
+ internalType: "uint256"
2559
+ },
2560
+ {
2561
+ name: "obligationUnits",
2562
+ type: "uint256",
2563
+ indexed: false,
2564
+ internalType: "uint256"
2565
+ },
2566
+ {
2567
+ name: "obligationShares",
2568
+ type: "uint256",
2569
+ indexed: false,
2570
+ internalType: "uint256"
2571
+ },
2572
+ {
2573
+ name: "buyerIsLender",
2574
+ type: "bool",
2575
+ indexed: false,
2576
+ internalType: "bool"
2577
+ },
2578
+ {
2579
+ name: "sellerIsBorrower",
2580
+ type: "bool",
2581
+ indexed: false,
2582
+ internalType: "bool"
2583
+ },
2584
+ {
2585
+ name: "sellerReceiver",
2586
+ type: "address",
2587
+ indexed: false,
2588
+ internalType: "address"
2589
+ },
2590
+ {
2591
+ name: "group",
2592
+ type: "bytes32",
2593
+ indexed: false,
2594
+ internalType: "bytes32"
2595
+ },
2596
+ {
2597
+ name: "consumed",
2598
+ type: "uint256",
2599
+ indexed: false,
2600
+ internalType: "uint256"
2601
+ }
2602
+ ],
2603
+ anonymous: false
2604
+ };
2605
+ /**
2606
+ * ABI for the Consume event emitted by the Obligation contract.
2607
+ */
2608
+ const consumedEvent = {
2609
+ type: "event",
2610
+ name: "Consume",
2611
+ inputs: [
2612
+ {
2613
+ name: "user",
2614
+ type: "address",
2615
+ indexed: true,
2616
+ internalType: "address"
2579
2617
  },
2580
- receiverIfMakerIsSeller: decoded[15]
2581
- });
2582
- }
2618
+ {
2619
+ name: "group",
2620
+ type: "bytes32",
2621
+ indexed: true,
2622
+ internalType: "bytes32"
2623
+ },
2624
+ {
2625
+ name: "amount",
2626
+ type: "uint256",
2627
+ indexed: false,
2628
+ internalType: "uint256"
2629
+ }
2630
+ ],
2631
+ anonymous: false
2632
+ };
2583
2633
  /**
2584
- * ABI for the Take event emitted by the Morpho V2 contract.
2634
+ * ABI for the Repay event emitted by the MorphoV2 contract.
2585
2635
  */
2586
- const takeEvent = {
2636
+ const repayEvent = {
2587
2637
  type: "event",
2588
- name: "Take",
2638
+ name: "Repay",
2589
2639
  inputs: [
2590
2640
  {
2591
2641
  name: "caller",
2592
2642
  type: "address",
2593
- indexed: false,
2643
+ indexed: true,
2594
2644
  internalType: "address"
2595
2645
  },
2596
2646
  {
@@ -2600,104 +2650,165 @@ const takeEvent = {
2600
2650
  internalType: "bytes32"
2601
2651
  },
2602
2652
  {
2603
- name: "maker",
2653
+ name: "obligationUnits",
2654
+ type: "uint256",
2655
+ indexed: false,
2656
+ internalType: "uint256"
2657
+ },
2658
+ {
2659
+ name: "onBehalf",
2604
2660
  type: "address",
2605
2661
  indexed: true,
2606
2662
  internalType: "address"
2607
- },
2663
+ }
2664
+ ],
2665
+ anonymous: false
2666
+ };
2667
+ /**
2668
+ * ABI for the Liquidate event emitted by the MorphoV2 contract.
2669
+ */
2670
+ const liquidateEvent = {
2671
+ type: "event",
2672
+ name: "Liquidate",
2673
+ inputs: [
2608
2674
  {
2609
- name: "taker",
2675
+ name: "caller",
2610
2676
  type: "address",
2611
2677
  indexed: true,
2612
2678
  internalType: "address"
2613
2679
  },
2614
2680
  {
2615
- name: "offerIsBuy",
2616
- type: "bool",
2617
- indexed: false,
2618
- internalType: "bool"
2681
+ name: "id",
2682
+ type: "bytes32",
2683
+ indexed: true,
2684
+ internalType: "bytes32"
2619
2685
  },
2620
2686
  {
2621
- name: "buyerAssets",
2622
- type: "uint256",
2687
+ name: "seizures",
2688
+ type: "tuple[]",
2623
2689
  indexed: false,
2624
- internalType: "uint256"
2690
+ internalType: "struct IMorphoV2.Seizure[]",
2691
+ components: [
2692
+ {
2693
+ name: "collateralIndex",
2694
+ type: "uint256",
2695
+ internalType: "uint256"
2696
+ },
2697
+ {
2698
+ name: "repaid",
2699
+ type: "uint256",
2700
+ internalType: "uint256"
2701
+ },
2702
+ {
2703
+ name: "seized",
2704
+ type: "uint256",
2705
+ internalType: "uint256"
2706
+ }
2707
+ ]
2625
2708
  },
2626
2709
  {
2627
- name: "sellerAssets",
2628
- type: "uint256",
2629
- indexed: false,
2630
- internalType: "uint256"
2710
+ name: "borrower",
2711
+ type: "address",
2712
+ indexed: true,
2713
+ internalType: "address"
2631
2714
  },
2632
2715
  {
2633
- name: "obligationUnits",
2716
+ name: "totalRepaid",
2634
2717
  type: "uint256",
2635
2718
  indexed: false,
2636
2719
  internalType: "uint256"
2637
2720
  },
2638
2721
  {
2639
- name: "obligationShares",
2722
+ name: "badDebt",
2640
2723
  type: "uint256",
2641
2724
  indexed: false,
2642
2725
  internalType: "uint256"
2643
- },
2644
- {
2645
- name: "buyerIsLender",
2646
- type: "bool",
2647
- indexed: false,
2648
- internalType: "bool"
2649
- },
2650
- {
2651
- name: "sellerIsBorrower",
2652
- type: "bool",
2653
- indexed: false,
2654
- internalType: "bool"
2655
- },
2726
+ }
2727
+ ],
2728
+ anonymous: false
2729
+ };
2730
+ /**
2731
+ * ABI for the SupplyCollateral event emitted by the MorphoV2 contract.
2732
+ */
2733
+ const supplyCollateralEvent = {
2734
+ type: "event",
2735
+ name: "SupplyCollateral",
2736
+ inputs: [
2656
2737
  {
2657
- name: "sellerReceiver",
2738
+ name: "caller",
2658
2739
  type: "address",
2659
2740
  indexed: false,
2660
2741
  internalType: "address"
2661
2742
  },
2662
2743
  {
2663
- name: "group",
2744
+ name: "id",
2664
2745
  type: "bytes32",
2665
- indexed: false,
2746
+ indexed: true,
2666
2747
  internalType: "bytes32"
2667
2748
  },
2668
2749
  {
2669
- name: "consumed",
2750
+ name: "collateral",
2751
+ type: "address",
2752
+ indexed: true,
2753
+ internalType: "address"
2754
+ },
2755
+ {
2756
+ name: "assets",
2670
2757
  type: "uint256",
2671
2758
  indexed: false,
2672
2759
  internalType: "uint256"
2760
+ },
2761
+ {
2762
+ name: "onBehalf",
2763
+ type: "address",
2764
+ indexed: true,
2765
+ internalType: "address"
2673
2766
  }
2674
2767
  ],
2675
2768
  anonymous: false
2676
2769
  };
2677
2770
  /**
2678
- * ABI for the Consume event emitted by the Obligation contract.
2771
+ * ABI for the WithdrawCollateral event emitted by the MorphoV2 contract.
2679
2772
  */
2680
- const consumedEvent = {
2773
+ const withdrawCollateralEvent = {
2681
2774
  type: "event",
2682
- name: "Consume",
2775
+ name: "WithdrawCollateral",
2683
2776
  inputs: [
2684
2777
  {
2685
- name: "user",
2778
+ name: "caller",
2686
2779
  type: "address",
2687
- indexed: true,
2780
+ indexed: false,
2688
2781
  internalType: "address"
2689
2782
  },
2690
2783
  {
2691
- name: "group",
2784
+ name: "id",
2692
2785
  type: "bytes32",
2693
2786
  indexed: true,
2694
2787
  internalType: "bytes32"
2695
2788
  },
2696
2789
  {
2697
- name: "amount",
2790
+ name: "collateral",
2791
+ type: "address",
2792
+ indexed: true,
2793
+ internalType: "address"
2794
+ },
2795
+ {
2796
+ name: "assets",
2698
2797
  type: "uint256",
2699
2798
  indexed: false,
2700
2799
  internalType: "uint256"
2800
+ },
2801
+ {
2802
+ name: "onBehalf",
2803
+ type: "address",
2804
+ indexed: true,
2805
+ internalType: "address"
2806
+ },
2807
+ {
2808
+ name: "receiver",
2809
+ type: "address",
2810
+ indexed: false,
2811
+ internalType: "address"
2701
2812
  }
2702
2813
  ],
2703
2814
  anonymous: false
@@ -2773,8 +2884,9 @@ function fromCollateral(parameters) {
2773
2884
  * @returns The created oracles. {@link fromOffer.ReturnType}
2774
2885
  */
2775
2886
  function fromOffer(parameters) {
2776
- const { offer, blockNumber, price = null } = parameters;
2887
+ const { chainId, offer, blockNumber, price = null } = parameters;
2777
2888
  return fromOffers({
2889
+ chainId,
2778
2890
  offers: [offer],
2779
2891
  blockNumber,
2780
2892
  price
@@ -2788,13 +2900,13 @@ function fromOffer(parameters) {
2788
2900
  * @returns The created oracles. {@link fromOffers.ReturnType}
2789
2901
  */
2790
2902
  function fromOffers(parameters) {
2791
- const { offers, blockNumber, price = null } = parameters;
2903
+ const { chainId, offers, blockNumber, price = null } = parameters;
2792
2904
  const rowsByKey = /* @__PURE__ */ new Map();
2793
2905
  for (const offer of offers) for (const collateral of offer.collaterals) {
2794
- const key = `${offer.chainId}-${collateral.oracle}`.toLowerCase();
2906
+ const key = `${chainId}-${collateral.oracle}`.toLowerCase();
2795
2907
  if (rowsByKey.has(key)) continue;
2796
2908
  rowsByKey.set(key, fromCollateral({
2797
- chainId: offer.chainId,
2909
+ chainId,
2798
2910
  collateral,
2799
2911
  blockNumber,
2800
2912
  price
@@ -2819,11 +2931,14 @@ let Conversion;
2819
2931
  //#region src/core/Position.ts
2820
2932
  var Position_exports = /* @__PURE__ */ __exportAll({
2821
2933
  Type: () => Type,
2822
- from: () => from$12
2934
+ from: () => from$12,
2935
+ positionTypeId: () => positionTypeId
2823
2936
  });
2824
2937
  let Type = /* @__PURE__ */ function(Type) {
2825
2938
  Type["ERC20"] = "erc20";
2826
2939
  Type["VAULT_V1"] = "vault_v1";
2940
+ Type["DEBT_OF"] = "debtOf";
2941
+ Type["COLLATERAL_OF"] = "collateralOf";
2827
2942
  return Type;
2828
2943
  }({});
2829
2944
  /**
@@ -2839,10 +2954,16 @@ function from$12(parameters) {
2839
2954
  user: parameters.user.toLowerCase(),
2840
2955
  type: parameters.type,
2841
2956
  balance: parameters.balance,
2842
- ...parameters.asset !== void 0 ? { asset: parameters.asset.toLowerCase() } : {},
2957
+ asset: parameters.asset.toLowerCase(),
2843
2958
  blockNumber: parameters.blockNumber
2844
2959
  };
2845
2960
  }
2961
+ /**
2962
+ * Maps a {@link Type} enum value to its 1-based integer ID used in the database.
2963
+ * @param type - The position type.
2964
+ * @returns The 1-based integer ID.
2965
+ */
2966
+ const positionTypeId = (type) => Object.values(Type).indexOf(type) + 1;
2846
2967
 
2847
2968
  //#endregion
2848
2969
  //#region src/core/Tick.ts
@@ -2981,7 +3102,7 @@ function fromSnakeCase(snake) {
2981
3102
  */
2982
3103
  function random() {
2983
3104
  return from$11({
2984
- obligationId: id(random$2()),
3105
+ obligationId: hex(32),
2985
3106
  ask: { tick: int(TICK_RANGE + 1) },
2986
3107
  bid: { tick: int(TICK_RANGE + 1) }
2987
3108
  });
@@ -3171,6 +3292,8 @@ function from$9(parameters) {
3171
3292
  from: parameters.from.toLowerCase(),
3172
3293
  to: parameters.to.toLowerCase(),
3173
3294
  value: parameters.value,
3295
+ type: parameters.type,
3296
+ asset: parameters.asset.toLowerCase(),
3174
3297
  blockNumber: parameters.blockNumber
3175
3298
  };
3176
3299
  }
@@ -3352,7 +3475,7 @@ const verifySignatureAndRecoverAddress = async (params) => {
3352
3475
  const encode = async (tree, signature, domain) => {
3353
3476
  const errorFactory = (reason) => new EncodeError(reason);
3354
3477
  const normalizedDomain = normalizeSignatureDomain(domain, errorFactory);
3355
- validateTreeForEncoding(tree, normalizedDomain);
3478
+ validateTreeForEncoding(tree);
3356
3479
  await verifySignatureAndRecoverAddress({
3357
3480
  root: tree.root,
3358
3481
  signature,
@@ -3384,14 +3507,10 @@ const encodeUnsigned = (tree) => {
3384
3507
  validateTreeForEncoding(tree);
3385
3508
  return (0, viem.bytesToHex)(encodeUnsignedBytes(tree));
3386
3509
  };
3387
- const validateTreeForEncoding = (tree, domain) => {
3510
+ const validateTreeForEncoding = (tree) => {
3388
3511
  if (VERSION$1 > 255) throw new EncodeError(`version overflow: ${VERSION$1} exceeds 255`);
3389
3512
  const computed = from$8(tree.offers);
3390
3513
  if (tree.root !== computed.root) throw new EncodeError(`root mismatch: expected ${computed.root}, got ${tree.root}`);
3391
- if (domain) {
3392
- const mismatched = tree.offers.find((offer) => BigInt(offer.chainId) !== domain.chainId);
3393
- if (mismatched) throw new EncodeError(`chainId mismatch: expected ${domain.chainId}, got ${mismatched.chainId}`);
3394
- }
3395
3514
  };
3396
3515
  const encodeUnsignedBytes = (tree) => {
3397
3516
  const offersPayload = tree.offers.map(serialize);
@@ -3458,8 +3577,6 @@ const decode = async (encoded, domain) => {
3458
3577
  }
3459
3578
  const tree = from$8(rawOffers.map((o) => OfferSchema().parse(o)));
3460
3579
  if (root !== tree.root) throw new DecodeError(`root mismatch: expected ${tree.root}, got ${root}`);
3461
- const chainIdMismatch = tree.offers.find((offer) => BigInt(offer.chainId) !== normalizedDomain.chainId);
3462
- if (chainIdMismatch) throw new DecodeError(`chainId mismatch: expected ${normalizedDomain.chainId}, got ${chainIdMismatch.chainId}`);
3463
3580
  return {
3464
3581
  tree,
3465
3582
  signature,
@@ -3512,7 +3629,7 @@ const BrandTypeId = Symbol.for("mempool/Brand");
3512
3629
 
3513
3630
  //#endregion
3514
3631
  //#region src/database/drizzle/VERSION.ts
3515
- const VERSION = "router_v1.8";
3632
+ const VERSION = "router_v1.11";
3516
3633
 
3517
3634
  //#endregion
3518
3635
  //#region src/database/drizzle/schema.ts
@@ -3522,13 +3639,15 @@ var schema_exports = /* @__PURE__ */ __exportAll({
3522
3639
  TABLE_NAMES: () => TABLE_NAMES,
3523
3640
  VERSIONED_TABLE_NAMES: () => VERSIONED_TABLE_NAMES,
3524
3641
  callbacks: () => callbacks,
3525
- chains: () => chains$1,
3642
+ chains: () => chains,
3526
3643
  collectors: () => collectors,
3527
3644
  consumedEvents: () => consumedEvents,
3528
3645
  groups: () => groups,
3529
3646
  lots: () => lots,
3647
+ lotsPositions: () => lotsPositions,
3530
3648
  merklePaths: () => merklePaths,
3531
3649
  obligationCollateralsV2: () => obligationCollateralsV2,
3650
+ obligationIdKeys: () => obligationIdKeys,
3532
3651
  obligations: () => obligations,
3533
3652
  offers: () => offers,
3534
3653
  offersCallbacks: () => offersCallbacks,
@@ -3544,6 +3663,7 @@ var schema_exports = /* @__PURE__ */ __exportAll({
3544
3663
  const s = (0, drizzle_orm_pg_core.pgSchema)(VERSION);
3545
3664
  var EnumTableName = /* @__PURE__ */ function(EnumTableName) {
3546
3665
  EnumTableName["OBLIGATIONS"] = "obligations";
3666
+ EnumTableName["OBLIGATION_ID_KEYS"] = "obligation_id_keys";
3547
3667
  EnumTableName["GROUPS"] = "groups";
3548
3668
  EnumTableName["CONSUMED_EVENTS"] = "consumed_events";
3549
3669
  EnumTableName["OBLIGATION_COLLATERALS_V2"] = "obligation_collaterals_v2";
@@ -3557,6 +3677,7 @@ var EnumTableName = /* @__PURE__ */ function(EnumTableName) {
3557
3677
  EnumTableName["COLLECTORS"] = "collectors";
3558
3678
  EnumTableName["CHAINS"] = "chains";
3559
3679
  EnumTableName["LOTS"] = "lots";
3680
+ EnumTableName["LOTS_POSITIONS"] = "lots_positions";
3560
3681
  EnumTableName["OFFSETS"] = "offsets";
3561
3682
  EnumTableName["TREES"] = "trees";
3562
3683
  EnumTableName["MERKLE_PATHS"] = "merkle_paths";
@@ -3565,11 +3686,16 @@ var EnumTableName = /* @__PURE__ */ function(EnumTableName) {
3565
3686
  const TABLE_NAMES = Object.values(EnumTableName);
3566
3687
  const VERSIONED_TABLE_NAMES = TABLE_NAMES.map((table) => `"${VERSION}"."${table}"`);
3567
3688
  const obligations = s.table(EnumTableName.OBLIGATIONS, {
3568
- obligationId: (0, drizzle_orm_pg_core.varchar)("obligation_id", { length: 66 }).primaryKey(),
3569
- chainId: (0, drizzle_orm_pg_core.bigint)("chain_id", { mode: "number" }).$type().notNull(),
3689
+ obligationKey: (0, drizzle_orm_pg_core.varchar)("obligation_key", { length: 66 }).primaryKey(),
3570
3690
  loanToken: (0, drizzle_orm_pg_core.varchar)("loan_token", { length: 42 }).notNull(),
3571
3691
  maturity: (0, drizzle_orm_pg_core.integer)("maturity").notNull()
3572
3692
  });
3693
+ const obligationIdKeys = s.table(EnumTableName.OBLIGATION_ID_KEYS, {
3694
+ obligationId: (0, drizzle_orm_pg_core.varchar)("obligation_id", { length: 66 }).primaryKey(),
3695
+ obligationKey: (0, drizzle_orm_pg_core.varchar)("obligation_key", { length: 66 }).notNull().references(() => obligations.obligationKey, { onDelete: "cascade" }),
3696
+ chainId: (0, drizzle_orm_pg_core.bigint)("chain_id", { mode: "number" }).$type().notNull(),
3697
+ morphoV2: (0, drizzle_orm_pg_core.varchar)("morpho_v2", { length: 42 }).notNull()
3698
+ }, (table) => [(0, drizzle_orm_pg_core.index)("obligation_id_keys_obligation_key_idx").on(table.obligationKey), (0, drizzle_orm_pg_core.index)("obligation_id_keys_chain_id_idx").on(table.chainId)]);
3573
3699
  const groups = s.table(EnumTableName.GROUPS, {
3574
3700
  chainId: (0, drizzle_orm_pg_core.bigint)("chain_id", { mode: "number" }).$type().notNull(),
3575
3701
  maker: (0, drizzle_orm_pg_core.varchar)("maker", { length: 42 }).notNull(),
@@ -3617,24 +3743,19 @@ const consumedEvents = s.table(EnumTableName.CONSUMED_EVENTS, {
3617
3743
  (0, drizzle_orm_pg_core.index)("consumed_events_block_number_idx").on(t.blockNumber)
3618
3744
  ]);
3619
3745
  const obligationCollateralsV2 = s.table(EnumTableName.OBLIGATION_COLLATERALS_V2, {
3620
- obligationId: (0, drizzle_orm_pg_core.varchar)("obligation_id", { length: 66 }).notNull().references(() => obligations.obligationId, { onDelete: "cascade" }),
3746
+ obligationKey: (0, drizzle_orm_pg_core.varchar)("obligation_key", { length: 66 }).notNull().references(() => obligations.obligationKey, { onDelete: "cascade" }),
3621
3747
  asset: (0, drizzle_orm_pg_core.varchar)("asset", { length: 42 }).notNull(),
3622
- oracleChainId: (0, drizzle_orm_pg_core.bigint)("oracle_chain_id", { mode: "number" }).$type().notNull(),
3623
3748
  oracleAddress: (0, drizzle_orm_pg_core.varchar)("oracle_address", { length: 42 }).notNull(),
3624
3749
  lltv: (0, drizzle_orm_pg_core.bigint)("lltv", { mode: "bigint" }).notNull(),
3750
+ collateralIndex: (0, drizzle_orm_pg_core.integer)("collateral_index").notNull(),
3625
3751
  updatedAt: (0, drizzle_orm_pg_core.timestamp)("updated_at").defaultNow().notNull()
3626
3752
  }, (table) => [
3627
3753
  (0, drizzle_orm_pg_core.primaryKey)({
3628
- columns: [table.obligationId, table.asset],
3754
+ columns: [table.obligationKey, table.asset],
3629
3755
  name: "obligation_collaterals_v2_pk"
3630
3756
  }),
3631
- (0, drizzle_orm_pg_core.foreignKey)({
3632
- columns: [table.oracleChainId, table.oracleAddress],
3633
- foreignColumns: [oracles$1.chainId, oracles$1.address],
3634
- name: "obligation_collaterals_v2_oracles_fk"
3635
- }),
3636
- (0, drizzle_orm_pg_core.index)("obligation_collaterals_v2_obligation_id_idx").on(table.obligationId),
3637
- (0, drizzle_orm_pg_core.index)("obligation_collaterals_v2_oracle_fk_idx").on(table.oracleChainId, table.oracleAddress)
3757
+ (0, drizzle_orm_pg_core.index)("obligation_collaterals_v2_obligation_key_idx").on(table.obligationKey),
3758
+ (0, drizzle_orm_pg_core.index)("obligation_collaterals_v2_oracle_address_idx").on(table.oracleAddress)
3638
3759
  ]);
3639
3760
  const oracles$1 = s.table(EnumTableName.ORACLES, {
3640
3761
  chainId: (0, drizzle_orm_pg_core.bigint)("chain_id", { mode: "number" }).$type().notNull(),
@@ -3650,8 +3771,8 @@ const oracles$1 = s.table(EnumTableName.ORACLES, {
3650
3771
  name: "oracles_pk"
3651
3772
  })]);
3652
3773
  const offers = s.table(EnumTableName.OFFERS, {
3653
- hash: (0, drizzle_orm_pg_core.varchar)("hash", { length: 66 }).primaryKey(),
3654
- obligationId: (0, drizzle_orm_pg_core.varchar)("obligation_id", { length: 66 }).notNull().references(() => obligations.obligationId, { onDelete: "cascade" }),
3774
+ hash: (0, drizzle_orm_pg_core.varchar)("hash", { length: 66 }).notNull(),
3775
+ obligationId: (0, drizzle_orm_pg_core.varchar)("obligation_id", { length: 66 }).notNull().references(() => obligationIdKeys.obligationId, { onDelete: "cascade" }),
3655
3776
  assets: (0, drizzle_orm_pg_core.numeric)("assets", {
3656
3777
  precision: 78,
3657
3778
  scale: 0
@@ -3679,6 +3800,10 @@ const offers = s.table(EnumTableName.OFFERS, {
3679
3800
  blockNumber: (0, drizzle_orm_pg_core.bigint)("block_number", { mode: "number" }).notNull(),
3680
3801
  updatedAt: (0, drizzle_orm_pg_core.timestamp)("updated_at").defaultNow().notNull()
3681
3802
  }, (table) => [
3803
+ (0, drizzle_orm_pg_core.primaryKey)({
3804
+ columns: [table.hash, table.obligationId],
3805
+ name: "offers_pk"
3806
+ }),
3682
3807
  (0, drizzle_orm_pg_core.foreignKey)({
3683
3808
  columns: [
3684
3809
  table.groupChainId,
@@ -3697,10 +3822,19 @@ const offers = s.table(EnumTableName.OFFERS, {
3697
3822
  (0, drizzle_orm_pg_core.index)("offers_obligation_id_side_idx").on(table.obligationId, table.buy)
3698
3823
  ]);
3699
3824
  const offersCallbacks = s.table(EnumTableName.OFFERS_CALLBACKS, {
3700
- offerHash: (0, drizzle_orm_pg_core.varchar)("offer_hash", { length: 66 }).notNull().references(() => offers.hash, { onDelete: "cascade" }),
3825
+ offerHash: (0, drizzle_orm_pg_core.varchar)("offer_hash", { length: 66 }).notNull(),
3826
+ obligationId: (0, drizzle_orm_pg_core.varchar)("obligation_id", { length: 66 }).notNull(),
3701
3827
  callbackId: (0, drizzle_orm_pg_core.varchar)("callback_id", { length: 66 })
3702
- }, (table) => [(0, drizzle_orm_pg_core.primaryKey)({
3703
- columns: [table.offerHash, table.callbackId],
3828
+ }, (table) => [(0, drizzle_orm_pg_core.foreignKey)({
3829
+ columns: [table.offerHash, table.obligationId],
3830
+ foreignColumns: [offers.hash, offers.obligationId],
3831
+ name: "offers_callbacks_offer_fk"
3832
+ }).onDelete("cascade"), (0, drizzle_orm_pg_core.primaryKey)({
3833
+ columns: [
3834
+ table.offerHash,
3835
+ table.obligationId,
3836
+ table.callbackId
3837
+ ],
3704
3838
  name: "offers_callbacks_pk"
3705
3839
  })]);
3706
3840
  const callbacks = s.table(EnumTableName.CALLBACKS, {
@@ -3708,23 +3842,25 @@ const callbacks = s.table(EnumTableName.CALLBACKS, {
3708
3842
  positionChainId: (0, drizzle_orm_pg_core.bigint)("position_chain_id", { mode: "number" }).$type().notNull(),
3709
3843
  positionContract: (0, drizzle_orm_pg_core.varchar)("position_contract", { length: 42 }).notNull(),
3710
3844
  positionUser: (0, drizzle_orm_pg_core.varchar)("position_user", { length: 42 }).notNull(),
3845
+ positionTypeId: (0, drizzle_orm_pg_core.integer)("position_type_id").notNull().references(() => positionTypes.id, { onDelete: "no action" }),
3711
3846
  amount: (0, drizzle_orm_pg_core.numeric)("amount", {
3712
3847
  precision: 78,
3713
3848
  scale: 0
3714
3849
  })
3715
- }, (table) => [(0, drizzle_orm_pg_core.foreignKey)({
3850
+ });
3851
+ const lotsPositions = s.table(EnumTableName.LOTS_POSITIONS, {
3852
+ chainId: (0, drizzle_orm_pg_core.bigint)("chain_id", { mode: "number" }).$type().notNull(),
3853
+ contract: (0, drizzle_orm_pg_core.varchar)("contract", { length: 42 }).notNull(),
3854
+ user: (0, drizzle_orm_pg_core.varchar)("user", { length: 42 }).notNull(),
3855
+ positionTypeId: (0, drizzle_orm_pg_core.integer)("position_type_id").notNull().references(() => positionTypes.id, { onDelete: "no action" })
3856
+ }, (table) => [(0, drizzle_orm_pg_core.primaryKey)({
3716
3857
  columns: [
3717
- table.positionChainId,
3718
- table.positionContract,
3719
- table.positionUser
3720
- ],
3721
- foreignColumns: [
3722
- positions.chainId,
3723
- positions.contract,
3724
- positions.user
3858
+ table.chainId,
3859
+ table.contract,
3860
+ table.user
3725
3861
  ],
3726
- name: "callbacks_positions_fk"
3727
- }).onDelete("cascade")]);
3862
+ name: "lots_positions_pk"
3863
+ })]);
3728
3864
  const lots = s.table(EnumTableName.LOTS, {
3729
3865
  chainId: (0, drizzle_orm_pg_core.bigint)("chain_id", { mode: "number" }).$type().notNull(),
3730
3866
  user: (0, drizzle_orm_pg_core.varchar)("user", { length: 42 }).notNull(),
@@ -3757,11 +3893,11 @@ const lots = s.table(EnumTableName.LOTS, {
3757
3893
  table.user
3758
3894
  ],
3759
3895
  foreignColumns: [
3760
- positions.chainId,
3761
- positions.contract,
3762
- positions.user
3896
+ lotsPositions.chainId,
3897
+ lotsPositions.contract,
3898
+ lotsPositions.user
3763
3899
  ],
3764
- name: "lots_positions_fk"
3900
+ name: "lots_lots_positions_fk"
3765
3901
  }).onDelete("cascade"),
3766
3902
  (0, drizzle_orm_pg_core.foreignKey)({
3767
3903
  columns: [
@@ -3803,11 +3939,11 @@ const offsets = s.table(EnumTableName.OFFSETS, {
3803
3939
  table.user
3804
3940
  ],
3805
3941
  foreignColumns: [
3806
- positions.chainId,
3807
- positions.contract,
3808
- positions.user
3942
+ lotsPositions.chainId,
3943
+ lotsPositions.contract,
3944
+ lotsPositions.user
3809
3945
  ],
3810
- name: "offsets_positions_fk"
3946
+ name: "offsets_lots_positions_fk"
3811
3947
  }).onDelete("cascade")]);
3812
3948
  const PositionTypes = s.enum("position_type", Object.values(Type));
3813
3949
  const positionTypes = s.table("position_types", {
@@ -3816,34 +3952,38 @@ const positionTypes = s.table("position_types", {
3816
3952
  });
3817
3953
  const positions = s.table(EnumTableName.POSITIONS, {
3818
3954
  chainId: (0, drizzle_orm_pg_core.bigint)("chain_id", { mode: "number" }).$type().notNull(),
3819
- contract: (0, drizzle_orm_pg_core.varchar)("contract", { length: 42 }).notNull(),
3955
+ contract: (0, drizzle_orm_pg_core.varchar)("contract", { length: 66 }).notNull(),
3820
3956
  user: (0, drizzle_orm_pg_core.varchar)("user", { length: 42 }).notNull(),
3821
3957
  positionTypeId: (0, drizzle_orm_pg_core.integer)("position_type_id").notNull().references(() => positionTypes.id, { onDelete: "no action" }),
3822
3958
  balance: (0, drizzle_orm_pg_core.numeric)("balance", {
3823
3959
  precision: 78,
3824
3960
  scale: 0
3825
3961
  }),
3826
- asset: (0, drizzle_orm_pg_core.varchar)("asset", { length: 42 }),
3962
+ asset: (0, drizzle_orm_pg_core.varchar)("asset", { length: 42 }).notNull(),
3827
3963
  blockNumber: (0, drizzle_orm_pg_core.bigint)("block_number", { mode: "number" }).notNull(),
3828
3964
  updatedAt: (0, drizzle_orm_pg_core.timestamp)("updated_at").defaultNow().notNull()
3829
3965
  }, (table) => [(0, drizzle_orm_pg_core.primaryKey)({
3830
3966
  columns: [
3831
3967
  table.chainId,
3832
3968
  table.contract,
3833
- table.user
3969
+ table.user,
3970
+ table.positionTypeId,
3971
+ table.asset
3834
3972
  ],
3835
3973
  name: "positions_pk"
3836
3974
  })]);
3837
3975
  const transfers = s.table(EnumTableName.TRANSFERS, {
3838
3976
  eventId: (0, drizzle_orm_pg_core.varchar)("event_id", { length: 128 }).primaryKey(),
3839
3977
  chainId: (0, drizzle_orm_pg_core.bigint)("chain_id", { mode: "number" }).$type().notNull(),
3840
- contract: (0, drizzle_orm_pg_core.varchar)("contract", { length: 42 }).notNull(),
3978
+ contract: (0, drizzle_orm_pg_core.varchar)("contract", { length: 66 }).notNull(),
3841
3979
  from: (0, drizzle_orm_pg_core.varchar)("from", { length: 42 }).notNull(),
3842
3980
  to: (0, drizzle_orm_pg_core.varchar)("to", { length: 42 }).notNull(),
3843
3981
  value: (0, drizzle_orm_pg_core.numeric)("value", {
3844
3982
  precision: 78,
3845
3983
  scale: 0
3846
3984
  }).notNull(),
3985
+ positionTypeId: (0, drizzle_orm_pg_core.integer)("position_type_id").notNull().references(() => positionTypes.id, { onDelete: "no action" }),
3986
+ asset: (0, drizzle_orm_pg_core.varchar)("asset", { length: 42 }).notNull(),
3847
3987
  blockNumber: (0, drizzle_orm_pg_core.bigint)("block_number", { mode: "number" }).notNull(),
3848
3988
  createdAt: (0, drizzle_orm_pg_core.timestamp)("created_at").defaultNow().notNull()
3849
3989
  }, (table) => [
@@ -3851,12 +3991,16 @@ const transfers = s.table(EnumTableName.TRANSFERS, {
3851
3991
  columns: [
3852
3992
  table.chainId,
3853
3993
  table.contract,
3854
- table.from
3994
+ table.from,
3995
+ table.positionTypeId,
3996
+ table.asset
3855
3997
  ],
3856
3998
  foreignColumns: [
3857
3999
  positions.chainId,
3858
4000
  positions.contract,
3859
- positions.user
4001
+ positions.user,
4002
+ positions.positionTypeId,
4003
+ positions.asset
3860
4004
  ],
3861
4005
  name: "transfers_positions_from_fk"
3862
4006
  }).onDelete("cascade"),
@@ -3864,16 +4008,21 @@ const transfers = s.table(EnumTableName.TRANSFERS, {
3864
4008
  columns: [
3865
4009
  table.chainId,
3866
4010
  table.contract,
3867
- table.to
4011
+ table.to,
4012
+ table.positionTypeId,
4013
+ table.asset
3868
4014
  ],
3869
4015
  foreignColumns: [
3870
4016
  positions.chainId,
3871
4017
  positions.contract,
3872
- positions.user
4018
+ positions.user,
4019
+ positions.positionTypeId,
4020
+ positions.asset
3873
4021
  ],
3874
4022
  name: "transfers_positions_to_fk"
3875
4023
  }).onDelete("cascade"),
3876
- (0, drizzle_orm_pg_core.index)("transfers_chain_contract_user_idx").on(table.chainId, table.contract, table.from, table.to, table.blockNumber)
4024
+ (0, drizzle_orm_pg_core.index)("transfers_chain_contract_user_idx").on(table.chainId, table.contract, table.from, table.to, table.blockNumber),
4025
+ (0, drizzle_orm_pg_core.index)("transfers_chain_type_block_idx").on(table.chainId, table.positionTypeId, table.blockNumber)
3877
4026
  ]);
3878
4027
  const StatusCode = s.enum("status_code", Object.values(Status));
3879
4028
  const status = s.table("status", {
@@ -3881,12 +4030,20 @@ const status = s.table("status", {
3881
4030
  code: StatusCode("code").unique()
3882
4031
  });
3883
4032
  const validations = s.table("validations", {
3884
- offerHash: (0, drizzle_orm_pg_core.varchar)("offer_hash", { length: 66 }).primaryKey().references(() => offers.hash, { onDelete: "cascade" }),
4033
+ offerHash: (0, drizzle_orm_pg_core.varchar)("offer_hash", { length: 66 }).notNull(),
4034
+ obligationId: (0, drizzle_orm_pg_core.varchar)("obligation_id", { length: 66 }).notNull(),
3885
4035
  statusId: (0, drizzle_orm_pg_core.integer)("status_id").notNull().references(() => status.id, { onDelete: "no action" }),
3886
4036
  updatedAt: (0, drizzle_orm_pg_core.timestamp)("updated_at").defaultNow().notNull()
3887
- });
4037
+ }, (table) => [(0, drizzle_orm_pg_core.primaryKey)({
4038
+ columns: [table.offerHash, table.obligationId],
4039
+ name: "validations_pk"
4040
+ }), (0, drizzle_orm_pg_core.foreignKey)({
4041
+ columns: [table.offerHash, table.obligationId],
4042
+ foreignColumns: [offers.hash, offers.obligationId],
4043
+ name: "validations_offer_fk"
4044
+ }).onDelete("cascade")]);
3888
4045
  const collectors = s.table(EnumTableName.COLLECTORS, {
3889
- chainId: (0, drizzle_orm_pg_core.bigint)("chain_id", { mode: "number" }).$type().notNull().references(() => chains$1.chainId, { onDelete: "no action" }),
4046
+ chainId: (0, drizzle_orm_pg_core.bigint)("chain_id", { mode: "number" }).$type().notNull().references(() => chains.chainId, { onDelete: "no action" }),
3890
4047
  name: (0, drizzle_orm_pg_core.text)("name").$type().notNull(),
3891
4048
  blockNumber: (0, drizzle_orm_pg_core.bigint)("block_number", { mode: "number" }).notNull(),
3892
4049
  epoch: (0, drizzle_orm_pg_core.numeric)("epoch", {
@@ -3895,7 +4052,7 @@ const collectors = s.table(EnumTableName.COLLECTORS, {
3895
4052
  }).default("0").notNull(),
3896
4053
  updatedAt: (0, drizzle_orm_pg_core.timestamp)("updated_at").defaultNow().notNull()
3897
4054
  }, (table) => [(0, drizzle_orm_pg_core.uniqueIndex)("collectors_chain_name_idx").on(table.chainId, table.name)]);
3898
- const chains$1 = s.table(EnumTableName.CHAINS, {
4055
+ const chains = s.table(EnumTableName.CHAINS, {
3899
4056
  chainId: (0, drizzle_orm_pg_core.bigint)("chain_id", { mode: "number" }).$type().notNull(),
3900
4057
  blockNumber: (0, drizzle_orm_pg_core.bigint)("block_number", { mode: "number" }).notNull(),
3901
4058
  epoch: (0, drizzle_orm_pg_core.numeric)("epoch", {
@@ -3910,18 +4067,352 @@ const trees = s.table(EnumTableName.TREES, {
3910
4067
  createdAt: (0, drizzle_orm_pg_core.timestamp)("created_at").defaultNow().notNull()
3911
4068
  });
3912
4069
  const merklePaths = s.table(EnumTableName.MERKLE_PATHS, {
3913
- offerHash: (0, drizzle_orm_pg_core.varchar)("offer_hash", { length: 66 }).primaryKey().references(() => offers.hash, { onDelete: "cascade" }),
4070
+ offerHash: (0, drizzle_orm_pg_core.varchar)("offer_hash", { length: 66 }).notNull(),
4071
+ obligationId: (0, drizzle_orm_pg_core.varchar)("obligation_id", { length: 66 }).notNull(),
3914
4072
  treeRoot: (0, drizzle_orm_pg_core.varchar)("tree_root", { length: 66 }).notNull().references(() => trees.root, { onDelete: "cascade" }),
3915
4073
  proofNodes: (0, drizzle_orm_pg_core.text)("proof_nodes").notNull(),
3916
4074
  createdAt: (0, drizzle_orm_pg_core.timestamp)("created_at").defaultNow().notNull()
3917
- }, (table) => [(0, drizzle_orm_pg_core.index)("merkle_paths_tree_root_idx").on(table.treeRoot)]);
4075
+ }, (table) => [
4076
+ (0, drizzle_orm_pg_core.primaryKey)({
4077
+ columns: [table.offerHash, table.obligationId],
4078
+ name: "merkle_paths_pk"
4079
+ }),
4080
+ (0, drizzle_orm_pg_core.foreignKey)({
4081
+ columns: [table.offerHash, table.obligationId],
4082
+ foreignColumns: [offers.hash, offers.obligationId],
4083
+ name: "merkle_paths_offer_fk"
4084
+ }).onDelete("cascade"),
4085
+ (0, drizzle_orm_pg_core.index)("merkle_paths_tree_root_idx").on(table.treeRoot)
4086
+ ]);
4087
+
4088
+ //#endregion
4089
+ //#region src/indexer/collectors/CollectFunctions/processors/processCollateralSeizures.ts
4090
+ /**
4091
+ * Parse raw MorphoV2 liquidate logs and compute collateral seizures.
4092
+ *
4093
+ * Seizures only expose `(obligationId, collateralIndex)` and must be resolved
4094
+ * to collateral token addresses before creating transfer rows.
4095
+ *
4096
+ * @param parameters - The parsed event logs and chain ID.
4097
+ * @param parameters.logs - Parsed event logs from MorphoV2.
4098
+ * @param parameters.chainId - Chain ID for event attribution.
4099
+ * @returns Seizure events pending collateral address resolution.
4100
+ */
4101
+ function processCollateralSeizures(parameters) {
4102
+ const { logs, chainId } = parameters;
4103
+ const logger = getLogger();
4104
+ const seizureEvents = [];
4105
+ for (const rawLog of logs) {
4106
+ if (rawLog.blockNumber === null || rawLog.logIndex === null || rawLog.transactionHash === null) {
4107
+ logger.debug({
4108
+ chainId,
4109
+ msg: "Skipping collateral log because it is missing required fields"
4110
+ });
4111
+ continue;
4112
+ }
4113
+ if (rawLog.eventName !== liquidateEvent.name) continue;
4114
+ const args = rawLog.args;
4115
+ if (args?.id === void 0 || args?.borrower === void 0 || args?.seizures === void 0) {
4116
+ logger.debug({
4117
+ chainId,
4118
+ msg: "Skipping Liquidate log for collateral because it is missing required args"
4119
+ });
4120
+ continue;
4121
+ }
4122
+ const baseId = `${rawLog.blockNumber.toString()}-${rawLog.logIndex.toString()}-${chainId}-${rawLog.transactionHash}`;
4123
+ for (let seizureIndex = 0; seizureIndex < args.seizures.length; seizureIndex++) {
4124
+ const seizure = args.seizures[seizureIndex];
4125
+ if (seizure.seized === 0n) continue;
4126
+ seizureEvents.push({
4127
+ id: `${baseId}-collateral-liquidate-${seizureIndex}`,
4128
+ chainId,
4129
+ obligationId: args.id,
4130
+ collateralIndex: Number(seizure.collateralIndex),
4131
+ user: args.borrower,
4132
+ amount: seizure.seized,
4133
+ blockNumber: Number(rawLog.blockNumber)
4134
+ });
4135
+ }
4136
+ }
4137
+ return seizureEvents;
4138
+ }
4139
+
4140
+ //#endregion
4141
+ //#region src/indexer/collectors/CollectFunctions/processors/processCollateralTransfers.ts
4142
+ /**
4143
+ * Parse raw MorphoV2 logs and compute collateral transfers.
4144
+ *
4145
+ * A collateral position uses `contract = obligationId` and `asset = collateral token address`.
4146
+ * The 5-col PK `(chainId, contract, user, positionTypeId, asset)` distinguishes per-token positions.
4147
+ *
4148
+ * **SupplyCollateral**: Transfer from zeroAddress to onBehalf (supply)
4149
+ * **WithdrawCollateral**: Transfer from onBehalf to zeroAddress (withdrawal)
4150
+ *
4151
+ * @param parameters - The parsed event logs and chain ID.
4152
+ * @param parameters.logs - Parsed event logs from MorphoV2.
4153
+ * @param parameters.chainId - Chain ID for event attribution.
4154
+ * @returns Collateral transfers from SupplyCollateral/WithdrawCollateral.
4155
+ */
4156
+ function processCollateralTransfers(parameters) {
4157
+ const { logs, chainId } = parameters;
4158
+ const logger = getLogger();
4159
+ const transfers = [];
4160
+ for (const rawLog of logs) {
4161
+ if (rawLog.blockNumber === null || rawLog.logIndex === null || rawLog.transactionHash === null) {
4162
+ logger.debug({
4163
+ chainId,
4164
+ msg: "Skipping collateral log because it is missing required fields"
4165
+ });
4166
+ continue;
4167
+ }
4168
+ const eventName = rawLog.eventName;
4169
+ const baseId = `${rawLog.blockNumber.toString()}-${rawLog.logIndex.toString()}-${chainId}-${rawLog.transactionHash}`;
4170
+ if (eventName === supplyCollateralEvent.name) {
4171
+ const args = rawLog.args;
4172
+ if (args?.id === void 0 || args?.collateral === void 0 || args?.assets === void 0 || args?.onBehalf === void 0) {
4173
+ logger.debug({
4174
+ chainId,
4175
+ msg: "Skipping SupplyCollateral log because it is missing required args"
4176
+ });
4177
+ continue;
4178
+ }
4179
+ if (args.assets === 0n) continue;
4180
+ transfers.push(from$9({
4181
+ id: `${baseId}-collateral-supply`,
4182
+ chainId,
4183
+ contract: args.id,
4184
+ from: viem.zeroAddress,
4185
+ to: args.onBehalf,
4186
+ value: args.assets,
4187
+ type: Type.COLLATERAL_OF,
4188
+ asset: args.collateral,
4189
+ blockNumber: Number(rawLog.blockNumber)
4190
+ }));
4191
+ continue;
4192
+ }
4193
+ if (eventName === withdrawCollateralEvent.name) {
4194
+ const args = rawLog.args;
4195
+ if (args?.id === void 0 || args?.collateral === void 0 || args?.assets === void 0 || args?.onBehalf === void 0) {
4196
+ logger.debug({
4197
+ chainId,
4198
+ msg: "Skipping WithdrawCollateral log because it is missing required args"
4199
+ });
4200
+ continue;
4201
+ }
4202
+ if (args.assets === 0n) continue;
4203
+ transfers.push(from$9({
4204
+ id: `${baseId}-collateral-withdraw`,
4205
+ chainId,
4206
+ contract: args.id,
4207
+ from: args.onBehalf,
4208
+ to: viem.zeroAddress,
4209
+ value: args.assets,
4210
+ type: Type.COLLATERAL_OF,
4211
+ asset: args.collateral,
4212
+ blockNumber: Number(rawLog.blockNumber)
4213
+ }));
4214
+ }
4215
+ }
4216
+ return transfers;
4217
+ }
3918
4218
 
3919
4219
  //#endregion
3920
- //#region src/indexer/collectors/CollectFunctions/collectConsumedEvents.ts
4220
+ //#region src/indexer/collectors/CollectFunctions/processors/processConsumedLogs.ts
3921
4221
  const buildGroupKey = (parameters) => {
3922
4222
  return `${parameters.chainId}-${parameters.maker.toLowerCase()}-${parameters.group.toLowerCase()}`;
3923
4223
  };
3924
- async function* collectConsumedEvents(parameters) {
4224
+ /** Parse raw MorphoV2 logs and produce normalized consumed events.
4225
+ * @param parameters - The parsed event logs and chain ID.
4226
+ * @param parameters.logs - Parsed event logs from MorphoV2 (Consume and Take events).
4227
+ * @param parameters.chainId - Chain ID for event attribution.
4228
+ * @returns Flat array of consumed events.
4229
+ */
4230
+ function processConsumedLogs(parameters) {
4231
+ const { logs, chainId } = parameters;
4232
+ const logger = getLogger();
4233
+ const consumedEvents = [];
4234
+ for (const rawLog of logs) {
4235
+ if (rawLog.blockNumber === null || rawLog.logIndex === null || rawLog.transactionHash === null) {
4236
+ logger.debug({
4237
+ chainId,
4238
+ msg: "Skipping log because it is missing required fields"
4239
+ });
4240
+ continue;
4241
+ }
4242
+ const eventName = rawLog.eventName;
4243
+ if (eventName === consumedEvent.name) {
4244
+ const consumeArgs = rawLog.args;
4245
+ if (consumeArgs?.user === void 0 || consumeArgs?.group === void 0 || consumeArgs?.amount === void 0) {
4246
+ logger.debug({
4247
+ chainId,
4248
+ msg: "Skipping Consume log because it is missing required args"
4249
+ });
4250
+ continue;
4251
+ }
4252
+ consumedEvents.push({
4253
+ kind: "consume",
4254
+ id: `${rawLog.blockNumber.toString()}-${rawLog.logIndex.toString()}-${chainId}-${rawLog.transactionHash}`,
4255
+ chainId,
4256
+ maker: consumeArgs.user,
4257
+ group: consumeArgs.group,
4258
+ amount: consumeArgs.amount,
4259
+ blockNumber: Number(rawLog.blockNumber)
4260
+ });
4261
+ continue;
4262
+ }
4263
+ if (eventName === takeEvent.name) {
4264
+ const takeArgs = rawLog.args;
4265
+ if (takeArgs?.maker === void 0 || takeArgs?.group === void 0 || takeArgs?.consumed === void 0) {
4266
+ logger.debug({
4267
+ chainId,
4268
+ msg: "Skipping Take log because it is missing required args for consumed"
4269
+ });
4270
+ continue;
4271
+ }
4272
+ consumedEvents.push({
4273
+ kind: "take",
4274
+ id: `${rawLog.blockNumber.toString()}-${rawLog.logIndex.toString()}-${chainId}-${rawLog.transactionHash}`,
4275
+ chainId,
4276
+ maker: takeArgs.maker,
4277
+ group: takeArgs.group,
4278
+ consumed: takeArgs.consumed,
4279
+ blockNumber: Number(rawLog.blockNumber)
4280
+ });
4281
+ }
4282
+ }
4283
+ return consumedEvents;
4284
+ }
4285
+
4286
+ //#endregion
4287
+ //#region src/indexer/collectors/CollectFunctions/processors/processDebtTransfers.ts
4288
+ /**
4289
+ * Parse raw MorphoV2 logs and compute debt transfers.
4290
+ *
4291
+ * A debt is modeled as a negative position: borrowing = transfer FROM user TO zeroAddress,
4292
+ * repayment = FROM zeroAddress TO user. The contract field is the obligationId.
4293
+ *
4294
+ * Applies the 4-case Take matrix, Repay, and Liquidate attribution rules:
4295
+ *
4296
+ * **Take event** — Buyer = maker if offerIsBuy, taker otherwise. Seller = the other.
4297
+ * | buyerIsLender | sellerIsBorrower | Buyer transfer | Seller transfer |
4298
+ * |---------------|------------------|------------------------------|------------------------------|
4299
+ * | true | true | none | from: seller → to: 0x0 |
4300
+ * | true | false | none | none |
4301
+ * | false | true | from: 0x0 → to: buyer | from: seller → to: 0x0 |
4302
+ * | false | false | from: 0x0 → to: buyer | none |
4303
+ *
4304
+ * **Repay**: from: 0x0 → to: onBehalf
4305
+ * **Liquidate**: from: 0x0 → to: borrower (value = totalRepaid + badDebt)
4306
+ *
4307
+ * @param parameters - The parsed event logs and chain ID.
4308
+ * @param parameters.logs - Parsed event logs from MorphoV2 (Take, Repay, Liquidate events).
4309
+ * @param parameters.chainId - Chain ID for event attribution.
4310
+ * @returns Transfer events ready for DB insertion.
4311
+ */
4312
+ function processDebtTransfers(parameters) {
4313
+ const { logs, chainId } = parameters;
4314
+ const logger = getLogger();
4315
+ const transfers = [];
4316
+ for (const rawLog of logs) {
4317
+ if (rawLog.blockNumber === null || rawLog.logIndex === null || rawLog.transactionHash === null) {
4318
+ logger.debug({
4319
+ chainId,
4320
+ msg: "Skipping debt log because it is missing required fields"
4321
+ });
4322
+ continue;
4323
+ }
4324
+ const eventName = rawLog.eventName;
4325
+ const baseId = `${rawLog.blockNumber.toString()}-${rawLog.logIndex.toString()}-${chainId}-${rawLog.transactionHash}`;
4326
+ if (eventName === takeEvent.name) {
4327
+ const args = rawLog.args;
4328
+ if (args?.id === void 0 || args?.maker === void 0 || args?.taker === void 0 || args?.offerIsBuy === void 0 || args?.obligationUnits === void 0 || args?.buyerIsLender === void 0 || args?.sellerIsBorrower === void 0) {
4329
+ logger.debug({
4330
+ chainId,
4331
+ msg: "Skipping Take log because it is missing required args for debt"
4332
+ });
4333
+ continue;
4334
+ }
4335
+ if (args.obligationUnits === 0n) continue;
4336
+ const buyer = args.offerIsBuy ? args.maker : args.taker;
4337
+ const seller = args.offerIsBuy ? args.taker : args.maker;
4338
+ const blockNumber = Number(rawLog.blockNumber);
4339
+ if (!args.buyerIsLender) transfers.push(from$9({
4340
+ id: `${baseId}-debt-buyer`,
4341
+ chainId,
4342
+ contract: args.id,
4343
+ from: viem.zeroAddress,
4344
+ to: buyer,
4345
+ value: args.obligationUnits,
4346
+ type: Type.DEBT_OF,
4347
+ asset: viem.zeroAddress,
4348
+ blockNumber
4349
+ }));
4350
+ if (args.sellerIsBorrower) transfers.push(from$9({
4351
+ id: `${baseId}-debt-seller`,
4352
+ chainId,
4353
+ contract: args.id,
4354
+ from: seller,
4355
+ to: viem.zeroAddress,
4356
+ value: args.obligationUnits,
4357
+ type: Type.DEBT_OF,
4358
+ asset: viem.zeroAddress,
4359
+ blockNumber
4360
+ }));
4361
+ continue;
4362
+ }
4363
+ if (eventName === repayEvent.name) {
4364
+ const args = rawLog.args;
4365
+ if (args?.id === void 0 || args?.obligationUnits === void 0 || args?.onBehalf === void 0) {
4366
+ logger.debug({
4367
+ chainId,
4368
+ msg: "Skipping Repay log because it is missing required args"
4369
+ });
4370
+ continue;
4371
+ }
4372
+ if (args.obligationUnits === 0n) continue;
4373
+ transfers.push(from$9({
4374
+ id: `${baseId}-debt-repay`,
4375
+ chainId,
4376
+ contract: args.id,
4377
+ from: viem.zeroAddress,
4378
+ to: args.onBehalf,
4379
+ value: args.obligationUnits,
4380
+ type: Type.DEBT_OF,
4381
+ asset: viem.zeroAddress,
4382
+ blockNumber: Number(rawLog.blockNumber)
4383
+ }));
4384
+ continue;
4385
+ }
4386
+ if (eventName === liquidateEvent.name) {
4387
+ const args = rawLog.args;
4388
+ if (args?.id === void 0 || args?.borrower === void 0 || args?.totalRepaid === void 0 || args?.badDebt === void 0) {
4389
+ logger.debug({
4390
+ chainId,
4391
+ msg: "Skipping Liquidate log because it is missing required args"
4392
+ });
4393
+ continue;
4394
+ }
4395
+ const totalReduction = args.totalRepaid + args.badDebt;
4396
+ if (totalReduction === 0n) continue;
4397
+ transfers.push(from$9({
4398
+ id: `${baseId}-debt-liquidate`,
4399
+ chainId,
4400
+ contract: args.id,
4401
+ from: viem.zeroAddress,
4402
+ to: args.borrower,
4403
+ value: totalReduction,
4404
+ type: Type.DEBT_OF,
4405
+ asset: viem.zeroAddress,
4406
+ blockNumber: Number(rawLog.blockNumber)
4407
+ }));
4408
+ }
4409
+ }
4410
+ return transfers;
4411
+ }
4412
+
4413
+ //#endregion
4414
+ //#region src/indexer/collectors/CollectFunctions/collectMorphoV2.ts
4415
+ async function* collectMorphoV2(parameters) {
3925
4416
  let { db, collector, client, lastBlockNumber: blockNumber, epoch, options: { maxBatchSize = 1e3, blockWindow } = {} } = parameters;
3926
4417
  const logger = getLogger();
3927
4418
  let startBlock = blockNumber;
@@ -3940,179 +4431,63 @@ async function* collectConsumedEvents(parameters) {
3940
4431
  });
3941
4432
  for await (const { logs, blockNumber: lastStreamBlockNumber } of stream) {
3942
4433
  const parsedLogs = (0, viem.parseEventLogs)({
3943
- abi: [consumedEvent, takeEvent],
4434
+ abi: [
4435
+ consumedEvent,
4436
+ takeEvent,
4437
+ repayEvent,
4438
+ liquidateEvent,
4439
+ supplyCollateralEvent,
4440
+ withdrawCollateralEvent
4441
+ ],
3944
4442
  logs,
3945
4443
  strict: false
3946
4444
  });
3947
- const normalizedLogs = [];
3948
- const groups$3 = /* @__PURE__ */ new Map();
3949
- const eventIds = /* @__PURE__ */ new Set();
3950
- const recordLog = (log) => {
3951
- normalizedLogs.push(log);
3952
- eventIds.add(log.id);
3953
- const groupKey = buildGroupKey({
3954
- chainId: log.chainId,
3955
- maker: log.maker,
3956
- group: log.group
3957
- });
3958
- if (!groups$3.has(groupKey)) groups$3.set(groupKey, {
3959
- chainId: log.chainId,
3960
- maker: log.maker.toLowerCase(),
3961
- group: log.group.toLowerCase()
3962
- });
3963
- };
3964
- for (const rawLog of parsedLogs) {
3965
- if (rawLog.blockNumber === null || rawLog.logIndex === null || rawLog.transactionHash === null) {
3966
- logger.debug({
3967
- collector,
3968
- chainId: client.chain.id,
3969
- msg: "Skipping log because it is missing required fields"
3970
- });
3971
- continue;
3972
- }
3973
- if (rawLog.eventName === consumedEvent.name) {
3974
- const consumeArgs = rawLog.args;
3975
- if (consumeArgs.user === void 0 || consumeArgs.group === void 0 || consumeArgs.amount === void 0) {
3976
- logger.debug({
3977
- collector,
3978
- chainId: client.chain.id,
3979
- msg: "Skipping Consume log because it is missing required args"
3980
- });
3981
- continue;
3982
- }
3983
- recordLog({
3984
- kind: "consume",
3985
- id: `${rawLog.blockNumber.toString()}-${rawLog.logIndex.toString()}-${client.chain.id}-${rawLog.transactionHash}`,
3986
- chainId: client.chain.id,
3987
- maker: consumeArgs.user,
3988
- group: consumeArgs.group,
3989
- amount: consumeArgs.amount,
3990
- blockNumber: Number(rawLog.blockNumber)
3991
- });
3992
- continue;
3993
- }
3994
- if (rawLog.eventName === takeEvent.name) {
3995
- const takeArgs = rawLog.args;
3996
- if (takeArgs.maker === void 0 || takeArgs.group === void 0 || takeArgs.consumed === void 0) {
3997
- logger.debug({
3998
- collector,
3999
- chainId: client.chain.id,
4000
- msg: "Skipping Take log because it is missing required args"
4001
- });
4002
- continue;
4003
- }
4004
- recordLog({
4005
- kind: "take",
4006
- id: `${rawLog.blockNumber.toString()}-${rawLog.logIndex.toString()}-${client.chain.id}-${rawLog.transactionHash}`,
4007
- chainId: client.chain.id,
4008
- maker: takeArgs.maker,
4009
- group: takeArgs.group,
4010
- consumed: takeArgs.consumed,
4011
- blockNumber: Number(rawLog.blockNumber)
4012
- });
4013
- }
4014
- }
4445
+ const consumedEvents = processConsumedLogs({
4446
+ logs: parsedLogs,
4447
+ chainId: client.chain.id
4448
+ });
4449
+ const debtTransfers = processDebtTransfers({
4450
+ logs: parsedLogs,
4451
+ chainId: client.chain.id
4452
+ });
4453
+ const collateralTransfers = processCollateralTransfers({
4454
+ logs: parsedLogs,
4455
+ chainId: client.chain.id
4456
+ });
4457
+ const seizureEvents = processCollateralSeizures({
4458
+ logs: parsedLogs,
4459
+ chainId: client.chain.id
4460
+ });
4015
4461
  await db.transaction(async (dbTx) => {
4016
- const existingEventIds = /* @__PURE__ */ new Set();
4017
- if (eventIds.size > 0) {
4018
- const ids = Array.from(eventIds);
4019
- for (let index = 0; index < ids.length; index += 500) {
4020
- const slice = ids.slice(index, index + 500);
4021
- const { rows } = await dbTx.execute(drizzle_orm.sql`
4022
- SELECT event_id
4023
- FROM ${consumedEvents}
4024
- WHERE event_id IN (${drizzle_orm.sql.join(slice.map((id) => drizzle_orm.sql`${id}`), drizzle_orm.sql`,`)});
4025
- `);
4026
- for (const row of rows) existingEventIds.add(row.event_id);
4027
- }
4028
- }
4029
- const consumedByGroup = /* @__PURE__ */ new Map();
4030
- if (groups$3.size > 0) {
4031
- const groupList = Array.from(groups$3.values());
4032
- for (let index = 0; index < groupList.length; index += 500) {
4033
- const slice = groupList.slice(index, index + 500);
4034
- const { rows } = await dbTx.execute(drizzle_orm.sql`
4035
- WITH targets(chain_id, maker, "group") AS (
4036
- VALUES ${drizzle_orm.sql.join(slice.map((group) => drizzle_orm.sql`(${group.chainId}::bigint, ${group.maker.toLowerCase()}::varchar(42), ${group.group.toLowerCase()}::varchar(66))`), drizzle_orm.sql`,`)}
4037
- )
4038
- SELECT
4039
- targets.chain_id,
4040
- targets.maker,
4041
- targets."group",
4042
- COALESCE(g.consumed, 0)::numeric AS consumed
4043
- FROM targets
4044
- LEFT JOIN ${groups} g
4045
- ON g.chain_id = targets.chain_id
4046
- AND g.maker = targets.maker
4047
- AND g."group" = targets."group";
4048
- `);
4049
- for (const row of rows) {
4050
- const groupKey = buildGroupKey({
4051
- chainId: Number(row.chain_id),
4052
- maker: row.maker,
4053
- group: row.group
4054
- });
4055
- consumedByGroup.set(groupKey, BigInt(row.consumed ?? "0"));
4056
- }
4057
- }
4058
- }
4059
- const events = [];
4060
- for (const log of normalizedLogs) {
4061
- if (existingEventIds.has(log.id)) continue;
4062
- const groupKey = buildGroupKey({
4063
- chainId: log.chainId,
4064
- maker: log.maker,
4065
- group: log.group
4066
- });
4067
- const previousConsumed = consumedByGroup.get(groupKey) ?? 0n;
4068
- if (log.kind === "consume") {
4069
- events.push({
4070
- id: log.id,
4071
- chainId: log.chainId,
4072
- maker: log.maker,
4073
- group: log.group,
4074
- amount: log.amount,
4075
- blockNumber: log.blockNumber
4076
- });
4077
- consumedByGroup.set(groupKey, previousConsumed + log.amount);
4078
- continue;
4079
- }
4080
- const delta = log.consumed - previousConsumed;
4081
- if (delta <= 0n) {
4082
- logger.debug({
4083
- collector,
4084
- chainId: client.chain.id,
4085
- msg: "Skipping Take log because consumed did not increase",
4086
- previous_consumed: previousConsumed.toString(),
4087
- consumed: log.consumed.toString()
4088
- });
4089
- continue;
4090
- }
4091
- events.push({
4092
- id: log.id,
4093
- chainId: log.chainId,
4094
- maker: log.maker,
4095
- group: log.group,
4096
- amount: delta,
4097
- blockNumber: log.blockNumber
4098
- });
4099
- consumedByGroup.set(groupKey, log.consumed);
4462
+ const [resolvedSeizureTransfers, resolvedConsumedEvents] = await Promise.all([resolveSeizureTransfers({
4463
+ dbTx,
4464
+ seizureEvents
4465
+ }), resolveConsumedEvents({
4466
+ dbTx,
4467
+ consumedEvents,
4468
+ chainId: client.chain.id,
4469
+ collector,
4470
+ logger
4471
+ })]);
4472
+ if (debtTransfers.length > 0) {
4473
+ await dbTx.positions.upsert(transfersToPositions(debtTransfers));
4474
+ await dbTx.transfers.create(debtTransfers);
4100
4475
  }
4101
- try {
4102
- await dbTx.consumed.create(events);
4103
- if (events.length > 0) logger.info({
4104
- msg: `Events indexed`,
4105
- collector,
4106
- count: events.length,
4107
- chain_id: client.chain.id,
4108
- block_range: [startBlock, lastStreamBlockNumber]
4109
- });
4110
- } catch (err) {
4111
- logger.error({
4112
- err,
4113
- msg: "Failed to process consumed events"
4114
- });
4476
+ const allCollateralTransfers = [...resolvedSeizureTransfers, ...collateralTransfers];
4477
+ if (allCollateralTransfers.length > 0) {
4478
+ await dbTx.positions.upsert(transfersToPositions(allCollateralTransfers));
4479
+ await dbTx.transfers.create(allCollateralTransfers);
4115
4480
  }
4481
+ await dbTx.consumed.create(resolvedConsumedEvents);
4482
+ if (resolvedConsumedEvents.length > 0) logger.info({
4483
+ msg: "Events indexed",
4484
+ collector,
4485
+ consumed_count: resolvedConsumedEvents.length,
4486
+ debt_transfer_count: debtTransfers.length,
4487
+ collateral_transfer_count: allCollateralTransfers.length,
4488
+ chain_id: client.chain.id,
4489
+ block_range: [startBlock, lastStreamBlockNumber]
4490
+ });
4116
4491
  blockNumber = lastStreamBlockNumber;
4117
4492
  try {
4118
4493
  await dbTx.blocks.advanceCollector({
@@ -4128,15 +4503,27 @@ async function* collectConsumedEvents(parameters) {
4128
4503
  chainId: client.chain.id
4129
4504
  });
4130
4505
  blockNumber = ancestor.blockNumber;
4131
- const deleted = await dbTx.consumed.delete({
4506
+ const deletedConsumed = await dbTx.consumed.delete({
4132
4507
  chainId: client.chain.id,
4133
4508
  blockNumberGte: blockNumber + 1
4134
4509
  });
4510
+ const deletedDebtTransfers = await dbTx.transfers.delete({
4511
+ chainId: client.chain.id,
4512
+ blockNumberGte: blockNumber + 1,
4513
+ positionTypeId: positionTypeId(Type.DEBT_OF)
4514
+ });
4515
+ const deletedCollateralTransfers = await dbTx.transfers.delete({
4516
+ chainId: client.chain.id,
4517
+ blockNumberGte: blockNumber + 1,
4518
+ positionTypeId: positionTypeId(Type.COLLATERAL_OF)
4519
+ });
4135
4520
  logger.info({
4136
4521
  collector,
4137
4522
  chain_id: client.chain.id,
4138
- msg: `Reorg detected, events deleted`,
4139
- count: deleted,
4523
+ msg: "Reorg detected, events deleted",
4524
+ consumed_deleted: deletedConsumed,
4525
+ debt_transfers_deleted: deletedDebtTransfers,
4526
+ collateral_transfers_deleted: deletedCollateralTransfers,
4140
4527
  block_number: blockNumber
4141
4528
  });
4142
4529
  await dbTx.blocks.advanceCollector({
@@ -4147,7 +4534,7 @@ async function* collectConsumedEvents(parameters) {
4147
4534
  });
4148
4535
  reorgDetected = true;
4149
4536
  } catch (err) {
4150
- const msg = "Failed to delete consumed events when handling reorg.";
4537
+ const msg = "Failed to delete events when handling reorg.";
4151
4538
  logger.error({
4152
4539
  collector,
4153
4540
  chainId: client.chain.id,
@@ -4162,10 +4549,210 @@ async function* collectConsumedEvents(parameters) {
4162
4549
  yield blockNumber;
4163
4550
  startBlock = blockNumber;
4164
4551
  }
4552
+ if (!reorgDetected) await db.transaction(async (dbTx) => {
4553
+ const collectorState = await dbTx.blocks.getCollector({
4554
+ collectorName: collector,
4555
+ chainId: client.chain.id
4556
+ });
4557
+ const deletedConsumed = await dbTx.consumed.delete({
4558
+ chainId: client.chain.id,
4559
+ blockNumberGte: collectorState.blockNumber + 1
4560
+ });
4561
+ const deletedDebtTransfers = await dbTx.transfers.delete({
4562
+ chainId: client.chain.id,
4563
+ blockNumberGte: collectorState.blockNumber + 1,
4564
+ positionTypeId: positionTypeId(Type.DEBT_OF)
4565
+ });
4566
+ const deletedCollateralTransfers = await dbTx.transfers.delete({
4567
+ chainId: client.chain.id,
4568
+ blockNumberGte: collectorState.blockNumber + 1,
4569
+ positionTypeId: positionTypeId(Type.COLLATERAL_OF)
4570
+ });
4571
+ if (deletedConsumed > 0 || deletedDebtTransfers > 0 || deletedCollateralTransfers > 0) logger.info({
4572
+ collector,
4573
+ chain_id: client.chain.id,
4574
+ msg: "Reorg detected, events deleted",
4575
+ consumed_deleted: deletedConsumed,
4576
+ debt_transfers_deleted: deletedDebtTransfers,
4577
+ collateral_transfers_deleted: deletedCollateralTransfers,
4578
+ block_number: collectorState.blockNumber
4579
+ });
4580
+ });
4581
+ }
4582
+ async function resolveSeizureTransfers(parameters) {
4583
+ const { dbTx, seizureEvents } = parameters;
4584
+ if (seizureEvents.length === 0) return [];
4585
+ const uniqueObligationIds = [...new Set(seizureEvents.map((event) => event.obligationId.toLowerCase()))];
4586
+ const rows = await dbTx.select({
4587
+ obligationId: obligationIdKeys.obligationId,
4588
+ collateralIndex: obligationCollateralsV2.collateralIndex,
4589
+ asset: obligationCollateralsV2.asset
4590
+ }).from(obligationIdKeys).innerJoin(obligationCollateralsV2, (0, drizzle_orm.eq)(obligationIdKeys.obligationKey, obligationCollateralsV2.obligationKey)).where((0, drizzle_orm.inArray)(obligationIdKeys.obligationId, uniqueObligationIds));
4591
+ const resolutionMap = /* @__PURE__ */ new Map();
4592
+ for (const row of rows) resolutionMap.set(`${row.obligationId.toLowerCase()}-${row.collateralIndex}`, row.asset);
4593
+ const resolvedSeizureTransfers = [];
4594
+ for (const seizure of seizureEvents) {
4595
+ const key = `${seizure.obligationId.toLowerCase()}-${seizure.collateralIndex}`;
4596
+ const asset = resolutionMap.get(key);
4597
+ if (asset === void 0) throw new Error(`Unresolvable Liquidate seizure: obligationId=${seizure.obligationId}, collateralIndex=${seizure.collateralIndex}, chainId=${seizure.chainId}, blockNumber=${seizure.blockNumber}`);
4598
+ resolvedSeizureTransfers.push(from$9({
4599
+ id: seizure.id,
4600
+ chainId: seizure.chainId,
4601
+ contract: seizure.obligationId,
4602
+ from: seizure.user,
4603
+ to: viem.zeroAddress,
4604
+ value: seizure.amount,
4605
+ type: Type.COLLATERAL_OF,
4606
+ asset,
4607
+ blockNumber: seizure.blockNumber
4608
+ }));
4609
+ }
4610
+ return resolvedSeizureTransfers;
4611
+ }
4612
+ async function resolveConsumedEvents(parameters) {
4613
+ const { dbTx, consumedEvents, chainId, collector, logger } = parameters;
4614
+ if (consumedEvents.length === 0) return [];
4615
+ const [existingConsumedIds, consumedByGroup] = await Promise.all([getExistingConsumedIds({
4616
+ dbTx,
4617
+ consumedEvents
4618
+ }), getConsumedByGroup({
4619
+ dbTx,
4620
+ consumedEvents
4621
+ })]);
4622
+ const resolvedEvents = [];
4623
+ for (const log of consumedEvents) {
4624
+ if (existingConsumedIds.has(log.id)) continue;
4625
+ const groupKey = buildGroupKey({
4626
+ chainId: log.chainId,
4627
+ maker: log.maker,
4628
+ group: log.group
4629
+ });
4630
+ const previousConsumed = consumedByGroup.get(groupKey) ?? 0n;
4631
+ if (log.kind === "consume") {
4632
+ resolvedEvents.push({
4633
+ id: log.id,
4634
+ chainId: log.chainId,
4635
+ maker: log.maker,
4636
+ group: log.group,
4637
+ amount: log.amount,
4638
+ blockNumber: log.blockNumber
4639
+ });
4640
+ consumedByGroup.set(groupKey, previousConsumed + log.amount);
4641
+ continue;
4642
+ }
4643
+ const delta = log.consumed - previousConsumed;
4644
+ if (delta <= 0n) {
4645
+ logger.debug({
4646
+ collector,
4647
+ chainId,
4648
+ msg: "Skipping Take log because consumed did not increase",
4649
+ previous_consumed: previousConsumed.toString(),
4650
+ consumed: log.consumed.toString()
4651
+ });
4652
+ continue;
4653
+ }
4654
+ resolvedEvents.push({
4655
+ id: log.id,
4656
+ chainId: log.chainId,
4657
+ maker: log.maker,
4658
+ group: log.group,
4659
+ amount: delta,
4660
+ blockNumber: log.blockNumber
4661
+ });
4662
+ consumedByGroup.set(groupKey, log.consumed);
4663
+ }
4664
+ return resolvedEvents;
4665
+ }
4666
+ async function getExistingConsumedIds(parameters) {
4667
+ const { dbTx, consumedEvents: consumedEvents$1 } = parameters;
4668
+ const existingConsumedIds = /* @__PURE__ */ new Set();
4669
+ const ids = Array.from(new Set(consumedEvents$1.map((event) => event.id)));
4670
+ for (let index = 0; index < ids.length; index += 500) {
4671
+ const slice = ids.slice(index, index + 500);
4672
+ const { rows } = await dbTx.execute(drizzle_orm.sql`
4673
+ SELECT event_id
4674
+ FROM ${consumedEvents}
4675
+ WHERE event_id IN (${drizzle_orm.sql.join(slice.map((id) => drizzle_orm.sql`${id}`), drizzle_orm.sql`,`)});
4676
+ `);
4677
+ for (const row of rows) existingConsumedIds.add(row.event_id);
4678
+ }
4679
+ return existingConsumedIds;
4680
+ }
4681
+ async function getConsumedByGroup(parameters) {
4682
+ const { dbTx, consumedEvents } = parameters;
4683
+ const groups$3 = /* @__PURE__ */ new Map();
4684
+ for (const event of consumedEvents) {
4685
+ const key = buildGroupKey({
4686
+ chainId: event.chainId,
4687
+ maker: event.maker,
4688
+ group: event.group
4689
+ });
4690
+ if (!groups$3.has(key)) groups$3.set(key, {
4691
+ chainId: event.chainId,
4692
+ maker: event.maker,
4693
+ group: event.group
4694
+ });
4695
+ }
4696
+ const consumedByGroup = /* @__PURE__ */ new Map();
4697
+ const groupList = Array.from(groups$3.values());
4698
+ for (let index = 0; index < groupList.length; index += 500) {
4699
+ const slice = groupList.slice(index, index + 500);
4700
+ const { rows } = await dbTx.execute(drizzle_orm.sql`
4701
+ WITH targets(chain_id, maker, "group") AS (
4702
+ VALUES ${drizzle_orm.sql.join(slice.map((group) => drizzle_orm.sql`(${group.chainId}::bigint, ${group.maker.toLowerCase()}::varchar(42), ${group.group.toLowerCase()}::varchar(66))`), drizzle_orm.sql`,`)}
4703
+ )
4704
+ SELECT
4705
+ targets.chain_id,
4706
+ targets.maker,
4707
+ targets."group",
4708
+ COALESCE(g.consumed, 0)::numeric AS consumed
4709
+ FROM targets
4710
+ LEFT JOIN ${groups} g
4711
+ ON g.chain_id = targets.chain_id
4712
+ AND g.maker = targets.maker
4713
+ AND g."group" = targets."group";
4714
+ `);
4715
+ for (const row of rows) {
4716
+ const groupKey = buildGroupKey({
4717
+ chainId: Number(row.chain_id),
4718
+ maker: row.maker,
4719
+ group: row.group
4720
+ });
4721
+ consumedByGroup.set(groupKey, BigInt(row.consumed ?? "0"));
4722
+ }
4723
+ }
4724
+ return consumedByGroup;
4725
+ }
4726
+ function transfersToPositions(transfers) {
4727
+ if (transfers.length === 0) return [];
4728
+ const transferType = transfers[0].type;
4729
+ if (transfers.some((transfer) => transfer.type !== transferType)) throw new Error("Cannot map transfers with mixed position types to positions.");
4730
+ const positionKeys = /* @__PURE__ */ new Map();
4731
+ for (const transfer of transfers) for (const user of [transfer.from, transfer.to]) {
4732
+ const key = `${transfer.chainId}-${transfer.contract}-${user}-${transfer.asset}`.toLowerCase();
4733
+ const existing = positionKeys.get(key);
4734
+ if (!existing || transfer.blockNumber < existing.blockNumber) positionKeys.set(key, {
4735
+ chainId: transfer.chainId,
4736
+ contract: transfer.contract,
4737
+ user,
4738
+ asset: transfer.asset,
4739
+ blockNumber: transfer.blockNumber
4740
+ });
4741
+ }
4742
+ return Array.from(positionKeys.values()).map((position) => from$12({
4743
+ chainId: position.chainId,
4744
+ contract: position.contract,
4745
+ user: position.user,
4746
+ type: transferType,
4747
+ balance: 0n,
4748
+ asset: position.asset,
4749
+ blockNumber: position.blockNumber
4750
+ }));
4165
4751
  }
4166
4752
 
4167
4753
  //#endregion
4168
4754
  //#region src/indexer/collectors/CollectFunctions/collectOffers.ts
4755
+ const ERC20_TYPE_ID = Object.values(Type).indexOf(Type.ERC20) + 1;
4169
4756
  async function* collectOffersV2(parameters) {
4170
4757
  let { db, collector, client, lastBlockNumber: blockNumber, gatekeeper, options: { maxBatchSize = 1e3, blockWindow } = {} } = parameters;
4171
4758
  const logger = getLogger();
@@ -4179,9 +4766,10 @@ async function* collectOffersV2(parameters) {
4179
4766
  });
4180
4767
  throw new Error(msg);
4181
4768
  }
4769
+ const morphoV2 = client.chain.custom.morpho.address;
4182
4770
  const signatureDomain = {
4183
4771
  chainId: client.chain.id,
4184
- verifyingContract: client.chain.custom.morpho.address
4772
+ verifyingContract: morphoV2
4185
4773
  };
4186
4774
  const { blockNumber: latestBlockNumberChain } = await db.blocks.getChain(client.chain.id);
4187
4775
  const stream = streamLogs({
@@ -4244,10 +4832,14 @@ async function* collectOffersV2(parameters) {
4244
4832
  await db.transaction(async (dbTx) => {
4245
4833
  const { epoch, blockNumber: latestBlockNumber } = await dbTx.blocks.getChain(client.chain.id);
4246
4834
  const treesToInsert = [];
4835
+ const pathsToInsert = [];
4247
4836
  let totalValidOffers = 0;
4248
4837
  const offersWithBlock = [];
4249
4838
  for (const { tree, signature, blockNumber: treeBlockNumber } of decodedTrees) try {
4250
- const allowedResults = await gatekeeper.isAllowed(tree.offers);
4839
+ const allowedResults = await gatekeeper.isAllowed({
4840
+ offers: tree.offers,
4841
+ chainId: client.chain.id
4842
+ });
4251
4843
  const hasBlockWindowViolation = treeBlockNumber > latestBlockNumber;
4252
4844
  if (!(allowedResults.issues.length === 0 && allowedResults.valid.length === tree.offers.length) || hasBlockWindowViolation) {
4253
4845
  if (allowedResults.issues.length > 0) {
@@ -4266,14 +4858,35 @@ async function* collectOffersV2(parameters) {
4266
4858
  continue;
4267
4859
  }
4268
4860
  treesToInsert.push({
4269
- tree,
4861
+ root: tree.root,
4270
4862
  signature
4271
4863
  });
4272
4864
  totalValidOffers += tree.offers.length;
4273
- offersWithBlock.push(...tree.offers.map((offer) => ({
4274
- offer,
4275
- blockNumber: treeBlockNumber
4276
- })));
4865
+ const obligationIdsByOfferHash = /* @__PURE__ */ new Map();
4866
+ for (const offer of tree.offers) {
4867
+ const offerHash = hash(offer).toLowerCase();
4868
+ const obligationId$1 = obligationId(offer, {
4869
+ chainId: client.chain.id,
4870
+ morphoV2
4871
+ }).toLowerCase();
4872
+ offersWithBlock.push({
4873
+ offer,
4874
+ blockNumber: treeBlockNumber,
4875
+ obligationId: obligationId$1
4876
+ });
4877
+ obligationIdsByOfferHash.set(offerHash, obligationId$1);
4878
+ }
4879
+ for (const proof of proofs(tree)) {
4880
+ const offerHash = hash(proof.offer).toLowerCase();
4881
+ const obligationId = obligationIdsByOfferHash.get(offerHash);
4882
+ if (obligationId === void 0) continue;
4883
+ pathsToInsert.push({
4884
+ offerHash,
4885
+ obligationId,
4886
+ treeRoot: tree.root,
4887
+ proof: proof.path
4888
+ });
4889
+ }
4277
4890
  } catch (err) {
4278
4891
  const error = err instanceof Error ? err : new Error(String(err));
4279
4892
  logger.error({
@@ -4283,19 +4896,23 @@ async function* collectOffersV2(parameters) {
4283
4896
  });
4284
4897
  throw new Error("Gatekeeper validation failed", { cause: error });
4285
4898
  }
4286
- const dependencies = buildOfferDependencies(offersWithBlock);
4899
+ const dependencies = buildOfferDependencies({
4900
+ offers: offersWithBlock,
4901
+ chainId: client.chain.id,
4902
+ morphoV2
4903
+ });
4287
4904
  await dbTx.oracles.upsert(dependencies.oracles);
4288
4905
  await dbTx.obligations.create(dependencies.obligations);
4289
4906
  await dbTx.groups.create(dependencies.groups);
4290
- const insertedHashes = await dbTx.offers.create(dependencies.offerBatches);
4291
- if (treesToInsert.length > 0) await dbTx.trees.create(treesToInsert);
4292
- const insertedOffers = filterInsertedOffers({
4293
- offers: offersWithBlock,
4294
- hashes: insertedHashes
4295
- });
4907
+ const insertedReferences = await dbTx.offers.create(dependencies.offerBatches);
4908
+ if (treesToInsert.length > 0) await dbTx.trees.upsert(treesToInsert);
4909
+ if (pathsToInsert.length > 0) await dbTx.trees.upsertPaths(pathsToInsert);
4296
4910
  const { callbacks, positions, lots } = decodeCallbacks({
4297
- chainId: client.chain.id,
4298
- offers: insertedOffers
4911
+ offers: filterInsertedOffers({
4912
+ offers: offersWithBlock,
4913
+ references: insertedReferences
4914
+ }),
4915
+ chainId: client.chain.id
4299
4916
  });
4300
4917
  if (positions.length > 0) await dbTx.positions.upsert(positions);
4301
4918
  if (callbacks.length > 0) await dbTx.callbacks.upsert(callbacks);
@@ -4358,7 +4975,7 @@ async function* collectOffersV2(parameters) {
4358
4975
  }
4359
4976
  }
4360
4977
  function decodeCallbacks(parameters) {
4361
- const { offers } = parameters;
4978
+ const { offers, chainId } = parameters;
4362
4979
  if (offers.length === 0) return {
4363
4980
  callbacks: [],
4364
4981
  positions: [],
@@ -4367,12 +4984,12 @@ function decodeCallbacks(parameters) {
4367
4984
  const callbacks = [];
4368
4985
  const positions = [];
4369
4986
  const lots = [];
4370
- for (const { offer, blockNumber: offerBlockNumber } of offers) {
4987
+ for (const { offer, blockNumber: offerBlockNumber, obligationId } of offers) {
4371
4988
  if (!offer.buy) continue;
4372
4989
  if (!isEmptyCallback(offer)) continue;
4373
4990
  const loanToken = offer.loanToken.toLowerCase();
4374
4991
  positions.push(from$12({
4375
- chainId: offer.chainId,
4992
+ chainId,
4376
4993
  contract: loanToken,
4377
4994
  user: offer.maker,
4378
4995
  type: Type.ERC20,
@@ -4380,20 +4997,23 @@ function decodeCallbacks(parameters) {
4380
4997
  blockNumber: offerBlockNumber
4381
4998
  }));
4382
4999
  lots.push({
4383
- positionChainId: offer.chainId,
5000
+ positionChainId: chainId,
4384
5001
  positionContract: loanToken,
4385
5002
  positionUser: offer.maker,
5003
+ positionTypeId: ERC20_TYPE_ID,
4386
5004
  group: offer.group,
4387
- obligationId: obligationId(offer),
5005
+ obligationId,
4388
5006
  size: offer.assets
4389
5007
  });
4390
5008
  callbacks.push({
4391
5009
  offerHash: hash(offer),
5010
+ obligationId,
4392
5011
  callbacks: [{
4393
- chainId: offer.chainId,
5012
+ chainId,
4394
5013
  contract: loanToken,
4395
5014
  user: offer.maker,
4396
- amount: offer.assets
5015
+ amount: offer.assets,
5016
+ positionTypeId: ERC20_TYPE_ID
4397
5017
  }]
4398
5018
  });
4399
5019
  }
@@ -4403,34 +5023,42 @@ function decodeCallbacks(parameters) {
4403
5023
  lots
4404
5024
  };
4405
5025
  }
4406
- function buildOfferDependencies(offers) {
5026
+ function buildOfferDependencies(parameters) {
5027
+ const { offers, chainId, morphoV2 } = parameters;
4407
5028
  const obligationsById = /* @__PURE__ */ new Map();
4408
5029
  const oraclesByKey = /* @__PURE__ */ new Map();
4409
5030
  const groupsByKey = /* @__PURE__ */ new Map();
4410
5031
  const offersByBlock = /* @__PURE__ */ new Map();
4411
- for (const { offer, blockNumber } of offers) {
5032
+ for (const { offer, blockNumber, obligationId } of offers) {
4412
5033
  const list = offersByBlock.get(blockNumber) ?? [];
4413
- list.push(offer);
5034
+ list.push({
5035
+ offer,
5036
+ obligationId,
5037
+ chainId
5038
+ });
4414
5039
  offersByBlock.set(blockNumber, list);
4415
- const obligationId$1 = obligationId(offer);
4416
- if (!obligationsById.get(obligationId$1)) obligationsById.set(obligationId$1, from$15({
4417
- chainId: offer.chainId,
4418
- loanToken: offer.loanToken,
4419
- maturity: offer.maturity,
4420
- collaterals: offer.collaterals
4421
- }));
5040
+ if (!obligationsById.get(obligationId)) obligationsById.set(obligationId, {
5041
+ obligationId,
5042
+ chainId,
5043
+ morphoV2,
5044
+ obligation: from$15({
5045
+ loanToken: offer.loanToken,
5046
+ maturity: offer.maturity,
5047
+ collaterals: offer.collaterals
5048
+ })
5049
+ });
4422
5050
  for (const collateral of offer.collaterals) {
4423
- const oracleKey = `${offer.chainId}-${collateral.oracle}`.toLowerCase();
5051
+ const oracleKey = `${chainId}-${collateral.oracle}`.toLowerCase();
4424
5052
  if (!oraclesByKey.has(oracleKey)) oraclesByKey.set(oracleKey, from$13({
4425
- chainId: offer.chainId,
5053
+ chainId,
4426
5054
  address: collateral.oracle,
4427
5055
  price: null,
4428
5056
  blockNumber
4429
5057
  }));
4430
5058
  }
4431
- const groupKey = `${offer.chainId}-${offer.maker}-${offer.group}`.toLowerCase();
5059
+ const groupKey = `${chainId}-${offer.maker}-${offer.group}`.toLowerCase();
4432
5060
  if (!groupsByKey.has(groupKey)) groupsByKey.set(groupKey, {
4433
- chainId: offer.chainId,
5061
+ chainId,
4434
5062
  maker: offer.maker,
4435
5063
  group: offer.group,
4436
5064
  blockNumber
@@ -4447,15 +5075,22 @@ function buildOfferDependencies(offers) {
4447
5075
  };
4448
5076
  }
4449
5077
  function filterInsertedOffers(parameters) {
4450
- if (parameters.hashes.length === 0) return [];
4451
- const inserted = new Set(parameters.hashes.map((hash) => hash.toLowerCase()));
5078
+ if (parameters.references.length === 0) return [];
5079
+ const keyOf = (input) => `${input.hash.toLowerCase()}:${input.obligationId.toLowerCase()}`;
5080
+ const inserted = new Set(parameters.references.map((offer) => keyOf({
5081
+ hash: offer.hash,
5082
+ obligationId: offer.obligationId
5083
+ })));
4452
5084
  const seen = /* @__PURE__ */ new Set();
4453
5085
  const filtered = [];
4454
5086
  for (const entry of parameters.offers) {
4455
- const hash$3 = hash(entry.offer).toLowerCase();
4456
- if (!inserted.has(hash$3)) continue;
4457
- if (seen.has(hash$3)) continue;
4458
- seen.add(hash$3);
5087
+ const key = keyOf({
5088
+ hash: hash(entry.offer),
5089
+ obligationId: entry.obligationId
5090
+ });
5091
+ if (!inserted.has(key)) continue;
5092
+ if (seen.has(key)) continue;
5093
+ seen.add(key);
4459
5094
  filtered.push(entry);
4460
5095
  }
4461
5096
  return filtered;
@@ -4570,7 +5205,7 @@ async function snapshotVaultPositions(parameters) {
4570
5205
  convertToAssets: (shares) => {
4571
5206
  const contract = contracts.get(position.contract.toLowerCase());
4572
5207
  if (!contract) return;
4573
- if (contract.decimalsOffset === void 0 || contract.totalAssets === void 0 || contract.totalSupply === void 0) return;
5208
+ if (contract.decimalsOffset === void 0 || contract.totalAssets === void 0 || contract.totalSupply === void 0 || contract.asset === void 0) return;
4574
5209
  try {
4575
5210
  position.balance = convertToAssets({
4576
5211
  shares,
@@ -4725,12 +5360,15 @@ async function* collectPositions(parameters) {
4725
5360
  from: log.args.from,
4726
5361
  to: log.args.to,
4727
5362
  value: log.args.value,
5363
+ type: Type.ERC20,
5364
+ asset: log.address,
4728
5365
  blockNumber: Number(log.blockNumber)
4729
5366
  }));
4730
5367
  }
4731
5368
  const { positions } = await db.positions.get({
4732
5369
  chainId: client.chain.id,
4733
- filled: false
5370
+ filled: false,
5371
+ type: Type.ERC20
4734
5372
  });
4735
5373
  const newPositions = [];
4736
5374
  try {
@@ -4853,7 +5491,8 @@ async function* collectPositions(parameters) {
4853
5491
  blockNumber = ancestor.blockNumber;
4854
5492
  const emptied = await dbTx.positions.setEmptyAfter({
4855
5493
  chainId: client.chain.id,
4856
- blockNumber: blockNumber + 1
5494
+ blockNumber: blockNumber + 1,
5495
+ type: Type.ERC20
4857
5496
  });
4858
5497
  logger.info({
4859
5498
  msg: "Reorg detected, positions set to empty",
@@ -5080,10 +5719,10 @@ function createBuilder(parameters) {
5080
5719
  }
5081
5720
  }));
5082
5721
  },
5083
- buildConsumedEventsCollector: ({ options: { maxBatchSize = 1e3 } = {} } = {}) => {
5084
- return createCollector("consumed_events", (p) => collectConsumedEvents({
5722
+ buildMorphoV2Collector: ({ options: { maxBatchSize = 1e3 } = {} } = {}) => {
5723
+ return createCollector("morpho_v2", (p) => collectMorphoV2({
5085
5724
  ...p,
5086
- collector: "consumed_events",
5725
+ collector: "morpho_v2",
5087
5726
  options: {
5088
5727
  maxBatchSize,
5089
5728
  blockWindow
@@ -5132,7 +5771,7 @@ const from$7 = (parameters) => {
5132
5771
  });
5133
5772
  return {
5134
5773
  offersCollector: collectorBuilder.buildOffersCollector({ options: { maxBatchSize } }),
5135
- consumedEventsCollector: collectorBuilder.buildConsumedEventsCollector({ options: { maxBatchSize } }),
5774
+ morphoV2Collector: collectorBuilder.buildMorphoV2Collector({ options: { maxBatchSize } }),
5136
5775
  pricesCollector: collectorBuilder.buildPricesCollector({ options: {
5137
5776
  maxBatchSize,
5138
5777
  retryAttempts,
@@ -5154,7 +5793,7 @@ var Indexer_exports = /* @__PURE__ */ __exportAll({
5154
5793
  });
5155
5794
  function from$6(config) {
5156
5795
  const { client, gatekeeper, db, interval = 1e4, maxBatchSize = 1e3, maxBlockNumber, blockWindow, retryAttempts, retryDelayMs } = config;
5157
- const { offersCollector, consumedEventsCollector, positionsCollector, pricesCollector } = from$7({
5796
+ const { offersCollector, morphoV2Collector, positionsCollector, pricesCollector } = from$7({
5158
5797
  client,
5159
5798
  db,
5160
5799
  gatekeeper,
@@ -5169,7 +5808,7 @@ function from$6(config) {
5169
5808
  client,
5170
5809
  collectors: [
5171
5810
  offersCollector,
5172
- consumedEventsCollector,
5811
+ morphoV2Collector,
5173
5812
  positionsCollector,
5174
5813
  pricesCollector
5175
5814
  ]
@@ -5374,12 +6013,13 @@ var ObligationResponse_exports = /* @__PURE__ */ __exportAll({ from: () => from$
5374
6013
  * @constructor
5375
6014
  * @param obligation - {@link Obligation}
5376
6015
  * @param quote - {@link Quote}
6016
+ * @param chainId - The chain id used to compute `id`.
5377
6017
  * @returns The created `ObligationResponse`. {@link ObligationResponse}
5378
6018
  */
5379
- function from$4(obligation, quote) {
6019
+ function from$4(obligation, quote, chainId) {
5380
6020
  return {
5381
6021
  id: quote.obligationId,
5382
- chain_id: obligation.chainId,
6022
+ chain_id: chainId,
5383
6023
  loan_token: obligation.loanToken,
5384
6024
  collaterals: obligation.collaterals.map((c) => ({
5385
6025
  token: c.asset,
@@ -5446,12 +6086,7 @@ function from$3(input) {
5446
6086
  receiver_if_maker_is_seller: input.receiverIfMakerIsSeller
5447
6087
  },
5448
6088
  offer_hash: input.hash,
5449
- obligation_id: id({
5450
- chainId,
5451
- loanToken: input.loanToken,
5452
- collaterals: [...input.collaterals],
5453
- maturity: input.maturity
5454
- }),
6089
+ obligation_id: input.obligationId,
5455
6090
  chain_id: chainId,
5456
6091
  consumed: input.consumed.toString(),
5457
6092
  takeable: input.takeable.toString(),
@@ -6078,10 +6713,6 @@ __decorate([(0, openapi_metadata_decorators.ApiProperty)({
6078
6713
  type: "boolean",
6079
6714
  example: validateOfferExample.buy
6080
6715
  })], ValidateOfferRequest.prototype, "buy", void 0);
6081
- __decorate([(0, openapi_metadata_decorators.ApiProperty)({
6082
- type: "number",
6083
- example: validateOfferExample.chain_id
6084
- })], ValidateOfferRequest.prototype, "chain_id", void 0);
6085
6716
  __decorate([(0, openapi_metadata_decorators.ApiProperty)({
6086
6717
  type: "string",
6087
6718
  example: validateOfferExample.loan_token
@@ -6099,6 +6730,11 @@ __decorate([(0, openapi_metadata_decorators.ApiProperty)({
6099
6730
  example: validateOfferExample.receiver_if_maker_is_seller
6100
6731
  })], ValidateOfferRequest.prototype, "receiver_if_maker_is_seller", void 0);
6101
6732
  var ValidateOffersRequest = class {};
6733
+ __decorate([(0, openapi_metadata_decorators.ApiProperty)({
6734
+ type: "number",
6735
+ description: "Chain id used for chain-scoped validation rules.",
6736
+ example: validateOfferExample.chain_id
6737
+ })], ValidateOffersRequest.prototype, "chain_id", void 0);
6102
6738
  __decorate([(0, openapi_metadata_decorators.ApiProperty)({
6103
6739
  type: () => [ValidateOfferRequest],
6104
6740
  description: "Array of offers in snake_case format. Required, non-empty.",
@@ -6865,6 +7501,12 @@ function isValidBase64urlJson(val) {
6865
7501
  function isValidOfferHashCursor(val) {
6866
7502
  return /^0x[a-f0-9]{64}$/i.test(val);
6867
7503
  }
7504
+ function isValidOfferCursor(val) {
7505
+ const [hash, obligationId, ...rest] = val.split(":");
7506
+ if (rest.length !== 0) return false;
7507
+ if (!hash || !obligationId) return false;
7508
+ return isValidOfferHashCursor(hash) && isValidOfferHashCursor(obligationId);
7509
+ }
6868
7510
  const csvArray = (schema) => zod.preprocess((value) => {
6869
7511
  if (value === void 0) return void 0;
6870
7512
  if (Array.isArray(value)) {
@@ -6928,7 +7570,7 @@ const GetConfigContractsQueryParams = zod.object({
6928
7570
  });
6929
7571
  const GetOffersQueryParams = PaginationQueryParams.omit({ cursor: true }).extend({
6930
7572
  cursor: zod.string().optional().meta({
6931
- description: "Pagination cursor. Use offer hash (0x...) for maker queries, base64url for obligation queries.",
7573
+ description: "Pagination cursor. Use offer hash:obligation_id for maker queries, base64url for obligation queries.",
6932
7574
  example: "eyJzaWRlIjoic2VsbCIsImN1cnJlbnRQcmljZSI6IjEwMDAwMDAwMDAwMDAwMDAwMDAiLCJibG9ja051bWJlciI6MSwiYXNzZXRzIjoiMTAwMDAwMDAwMDAwMDAwMDAwMCIsImhhc2giOiIweGRmZDY4NTllM2UwODJkMTkzODlhMWFlYzFiZGFkN2U4ZDkyZDk2YjFhYTc5NDBkYTkxYTMxMjVkMzFlM2JlNWIiLCJ0b3RhbFJldHVybmVkIjoxMCwibm93IjoxNjAwMDAwMDAwfQ"
6933
7575
  }),
6934
7576
  side: zod.enum(["buy", "sell"]).optional().meta({
@@ -6955,10 +7597,10 @@ const GetOffersQueryParams = PaginationQueryParams.omit({ cursor: true }).extend
6955
7597
  return;
6956
7598
  }
6957
7599
  if (hasMaker) {
6958
- if (val.cursor !== void 0 && !isValidOfferHashCursor(val.cursor)) ctx.addIssue({
7600
+ if (val.cursor !== void 0 && !isValidOfferCursor(val.cursor)) ctx.addIssue({
6959
7601
  code: "custom",
6960
7602
  path: ["cursor"],
6961
- message: "Cursor must be a 32-byte hex offer hash when filtering by maker"
7603
+ message: "Cursor must be in the offer hash:obligation_id format when filtering by maker"
6962
7604
  });
6963
7605
  return;
6964
7606
  }
@@ -7068,7 +7710,13 @@ const GetBookParams = zod.object({
7068
7710
  example: "buy"
7069
7711
  })
7070
7712
  });
7071
- const ValidateOffersBody = zod.object({ offers: zod.array(zod.unknown()).min(1, { message: "'offers' must contain at least 1 offer" }) }).strict();
7713
+ const ValidateOffersBody = zod.object({
7714
+ chain_id: zod.number().int().positive("chain_id must be a positive integer").meta({
7715
+ description: "Chain id used for chain-scoped validation rules.",
7716
+ example: 1
7717
+ }),
7718
+ offers: zod.array(zod.unknown()).min(1, { message: "'offers' must contain at least 1 offer" })
7719
+ }).strict();
7072
7720
  const GetUserPositionsParams = zod.object({
7073
7721
  ...PaginationQueryParams.shape,
7074
7722
  user_address: zod.string().regex(/^0x[a-fA-F0-9]{40}$/, { error: "User address must be a valid 20-byte address" }).transform((val) => val.toLowerCase()).meta({
@@ -7207,19 +7855,19 @@ async function getConfigContracts(query, chainRegistry) {
7207
7855
  });
7208
7856
  let cursorContract = null;
7209
7857
  if (cursor) try {
7210
- cursorContract = parseCursor$1(cursor);
7858
+ cursorContract = parseCursor$3(cursor);
7211
7859
  } catch (err) {
7212
7860
  return failure(err);
7213
7861
  }
7214
7862
  const startIndex = cursorContract ? findStartIndex$1(contracts, cursorContract) : 0;
7215
7863
  const page = contracts.slice(startIndex, startIndex + limit);
7216
- const nextCursor = startIndex + limit < contracts.length && page.length > 0 ? formatCursor$1(page.at(-1)) : null;
7864
+ const nextCursor = startIndex + limit < contracts.length && page.length > 0 ? formatCursor$3(page.at(-1)) : null;
7217
7865
  return success({
7218
7866
  data: page,
7219
7867
  cursor: nextCursor
7220
7868
  });
7221
7869
  }
7222
- function parseCursor$1(cursor) {
7870
+ function parseCursor$3(cursor) {
7223
7871
  const [chain, address] = cursor.split(":", 2);
7224
7872
  if (!chain || !address) throw new BadRequestError$1("Cursor must be in the format chain_id:0x...");
7225
7873
  return {
@@ -7227,7 +7875,7 @@ function parseCursor$1(cursor) {
7227
7875
  address: address.toLowerCase()
7228
7876
  };
7229
7877
  }
7230
- function formatCursor$1(contract) {
7878
+ function formatCursor$3(contract) {
7231
7879
  return `${contract.chain_id}:${contract.address.toLowerCase()}`;
7232
7880
  }
7233
7881
  function findStartIndex$1(contracts, cursor) {
@@ -7470,7 +8118,7 @@ async function getConfigRules(query, chains) {
7470
8118
  const checksum = buildConfigRulesChecksum(filteredRules);
7471
8119
  let cursorRule = null;
7472
8120
  if (cursor) try {
7473
- cursorRule = parseCursor(cursor);
8121
+ cursorRule = parseCursor$2(cursor);
7474
8122
  } catch (err) {
7475
8123
  return failure(err);
7476
8124
  }
@@ -7478,7 +8126,7 @@ async function getConfigRules(query, chains) {
7478
8126
  if (cursorRule && chainFilter && !chainFilter.has(cursorRule.chain_id)) return failure(new BadRequestError$1("Cursor chain_id must match requested chains"));
7479
8127
  const startIndex = cursorRule ? findStartIndex(filteredRules, cursorRule) : 0;
7480
8128
  const page = filteredRules.slice(startIndex, startIndex + limit);
7481
- const nextCursor = startIndex + limit < filteredRules.length && page.length > 0 ? formatCursor(page.at(-1)) : null;
8129
+ const nextCursor = startIndex + limit < filteredRules.length && page.length > 0 ? formatCursor$2(page.at(-1)) : null;
7482
8130
  const response = success({
7483
8131
  data: page,
7484
8132
  cursor: nextCursor
@@ -7486,14 +8134,14 @@ async function getConfigRules(query, chains) {
7486
8134
  response.body.meta.checksum = checksum;
7487
8135
  return response;
7488
8136
  }
7489
- function formatCursor(rule) {
8137
+ function formatCursor$2(rule) {
7490
8138
  if (rule.type === "maturity") return `maturity:${rule.chain_id}:${rule.timestamp}:${rule.name}`;
7491
8139
  if (rule.type === "callback") return `callback:${rule.chain_id}:${rule.callback_type}:${rule.address.toLowerCase()}`;
7492
8140
  if (rule.type === "oracle") return `oracle:${rule.chain_id}:${rule.address.toLowerCase()}`;
7493
8141
  if (rule.type === "collateral_token") return `collateral_token:${rule.chain_id}:${rule.address.toLowerCase()}`;
7494
8142
  return `loan_token:${rule.chain_id}:${rule.address.toLowerCase()}`;
7495
8143
  }
7496
- function parseCursor(cursor) {
8144
+ function parseCursor$2(cursor) {
7497
8145
  const [type, chain, ...rest] = cursor.split(":");
7498
8146
  if (!type || !chain || rest.length === 0) throw new BadRequestError$1("Cursor must be in the format type:chain_id:<value>");
7499
8147
  if (!isConfigRuleType(type)) throw new BadRequestError$1("Cursor has an invalid rule type");
@@ -7786,23 +8434,22 @@ function create$16(parameters) {
7786
8434
  const loanTokenFilter = loanTokens !== void 0 && loanTokens.length > 0 ? drizzle_orm.sql`(${drizzle_orm.sql.join(loanTokens.map((token) => drizzle_orm.sql`LOWER(${obligations.loanToken}) = ${token.toLowerCase()}`), drizzle_orm.sql` OR `)})` : void 0;
7787
8435
  const collateralFilter = collateralTokens !== void 0 && collateralTokens.length > 0 ? drizzle_orm.sql`EXISTS (
7788
8436
  SELECT 1 FROM ${obligationCollateralsV2} oc
7789
- WHERE oc.obligation_id = ${obligations.obligationId}
8437
+ WHERE oc.obligation_key = ${obligations.obligationKey}
7790
8438
  AND (${drizzle_orm.sql.join(collateralTokens.map((token) => drizzle_orm.sql`LOWER(oc.asset) = ${token.toLowerCase()}`), drizzle_orm.sql` OR `)})
7791
8439
  )` : void 0;
7792
- const bestAskTick = db.select({ askTick: offers.tick }).from(offers).innerJoin(groups, (0, drizzle_orm.and)((0, drizzle_orm.eq)(offers.groupChainId, groups.chainId), (0, drizzle_orm.eq)(offers.groupMaker, groups.maker), (0, drizzle_orm.eq)(offers.group, groups.group))).leftJoin(validations, (0, drizzle_orm.eq)(offers.hash, validations.offerHash)).leftJoin(status, (0, drizzle_orm.eq)(validations.statusId, status.id)).where((0, drizzle_orm.and)((0, drizzle_orm.eq)(offers.obligationId, obligations.obligationId), (0, drizzle_orm.eq)(offers.buy, false), (0, drizzle_orm.gte)(offers.expiry, now$3), (0, drizzle_orm.gte)(offers.maturity, now$3), (0, drizzle_orm.lte)(offers.start, now$3), drizzle_orm.sql`(${status.code} IS NULL OR ${status.code} = ${Status.VALID})`)).orderBy((0, drizzle_orm.desc)(offers.tick)).limit(1).as("best_ask_tick");
7793
- const bestBidTick = db.select({ bidTick: offers.tick }).from(offers).innerJoin(groups, (0, drizzle_orm.and)((0, drizzle_orm.eq)(offers.groupChainId, groups.chainId), (0, drizzle_orm.eq)(offers.groupMaker, groups.maker), (0, drizzle_orm.eq)(offers.group, groups.group))).leftJoin(validations, (0, drizzle_orm.eq)(offers.hash, validations.offerHash)).leftJoin(status, (0, drizzle_orm.eq)(validations.statusId, status.id)).where((0, drizzle_orm.and)((0, drizzle_orm.eq)(offers.obligationId, obligations.obligationId), (0, drizzle_orm.eq)(offers.buy, true), (0, drizzle_orm.gte)(offers.expiry, now$3), (0, drizzle_orm.gte)(offers.maturity, now$3), (0, drizzle_orm.lte)(offers.start, now$3), drizzle_orm.sql`(${status.code} IS NULL OR ${status.code} = ${Status.VALID})`)).orderBy((0, drizzle_orm.asc)(offers.tick)).limit(1).as("best_bid_tick");
8440
+ const bestAskTick = db.select({ askTick: offers.tick }).from(offers).innerJoin(groups, (0, drizzle_orm.and)((0, drizzle_orm.eq)(offers.groupChainId, groups.chainId), (0, drizzle_orm.eq)(offers.groupMaker, groups.maker), (0, drizzle_orm.eq)(offers.group, groups.group))).leftJoin(validations, (0, drizzle_orm.and)((0, drizzle_orm.eq)(offers.hash, validations.offerHash), (0, drizzle_orm.eq)(offers.obligationId, validations.obligationId))).leftJoin(status, (0, drizzle_orm.eq)(validations.statusId, status.id)).where((0, drizzle_orm.and)((0, drizzle_orm.eq)(offers.obligationId, obligationIdKeys.obligationId), (0, drizzle_orm.eq)(offers.buy, false), (0, drizzle_orm.gte)(offers.expiry, now$3), (0, drizzle_orm.gte)(offers.maturity, now$3), (0, drizzle_orm.lte)(offers.start, now$3), drizzle_orm.sql`(${status.code} IS NULL OR ${status.code} = ${Status.VALID})`)).orderBy((0, drizzle_orm.desc)(offers.tick)).limit(1).as("best_ask_tick");
8441
+ const bestBidTick = db.select({ bidTick: offers.tick }).from(offers).innerJoin(groups, (0, drizzle_orm.and)((0, drizzle_orm.eq)(offers.groupChainId, groups.chainId), (0, drizzle_orm.eq)(offers.groupMaker, groups.maker), (0, drizzle_orm.eq)(offers.group, groups.group))).leftJoin(validations, (0, drizzle_orm.and)((0, drizzle_orm.eq)(offers.hash, validations.offerHash), (0, drizzle_orm.eq)(offers.obligationId, validations.obligationId))).leftJoin(status, (0, drizzle_orm.eq)(validations.statusId, status.id)).where((0, drizzle_orm.and)((0, drizzle_orm.eq)(offers.obligationId, obligationIdKeys.obligationId), (0, drizzle_orm.eq)(offers.buy, true), (0, drizzle_orm.gte)(offers.expiry, now$3), (0, drizzle_orm.gte)(offers.maturity, now$3), (0, drizzle_orm.lte)(offers.start, now$3), drizzle_orm.sql`(${status.code} IS NULL OR ${status.code} = ${Status.VALID})`)).orderBy((0, drizzle_orm.asc)(offers.tick)).limit(1).as("best_bid_tick");
7794
8442
  const obligationsWithQuotes = db.select({
7795
- obligationId: obligations.obligationId,
7796
- chainId: obligations.chainId,
8443
+ obligationId: obligationIdKeys.obligationId,
8444
+ chainId: obligationIdKeys.chainId,
7797
8445
  loanToken: obligations.loanToken,
7798
- collaterals: drizzle_orm.sql`ARRAY_AGG(jsonb_build_object('asset', ${obligationCollateralsV2.asset}, 'oracle', ${oracles$1.address}, 'lltv', ${obligationCollateralsV2.lltv}))`.as("collaterals"),
8446
+ collaterals: drizzle_orm.sql`ARRAY_AGG(jsonb_build_object('asset', ${obligationCollateralsV2.asset}, 'oracle', ${obligationCollateralsV2.oracleAddress}, 'lltv', ${obligationCollateralsV2.lltv}))`.as("collaterals"),
7799
8447
  maturity: obligations.maturity,
7800
8448
  askTick: drizzle_orm.sql`MAX(${bestAskTick.askTick})`.as("ask_tick"),
7801
8449
  bidTick: drizzle_orm.sql`MAX(${bestBidTick.bidTick})`.as("bid_tick"),
7802
8450
  ask: drizzle_orm.sql`COALESCE(MAX(${bestAskTick.askTick}) + 1, 0)`.as("ask"),
7803
8451
  bid: drizzle_orm.sql`COALESCE(MAX(${bestBidTick.bidTick}) + 1, 0)`.as("bid")
7804
- }).from(obligations).innerJoin(obligationCollateralsV2, (0, drizzle_orm.eq)(obligations.obligationId, obligationCollateralsV2.obligationId)).innerJoin(oracles$1, drizzle_orm.sql`${obligationCollateralsV2.oracleChainId} = ${oracles$1.chainId}
7805
- AND ${obligationCollateralsV2.oracleAddress} = ${oracles$1.address}`).leftJoinLateral(bestAskTick, drizzle_orm.sql`true`).leftJoinLateral(bestBidTick, drizzle_orm.sql`true`).groupBy(obligations.obligationId).where((0, drizzle_orm.and)(ids !== void 0 && ids.length > 0 ? (0, drizzle_orm.inArray)(obligations.obligationId, ids) : void 0, chainIds !== void 0 && chainIds.length > 0 ? (0, drizzle_orm.inArray)(obligations.chainId, chainIds) : void 0, loanTokenFilter, maturities !== void 0 && maturities.length > 0 ? (0, drizzle_orm.inArray)(obligations.maturity, maturities) : (0, drizzle_orm.gte)(obligations.maturity, now$3), collateralFilter)).as("obligations_with_quotes");
8452
+ }).from(obligationIdKeys).innerJoin(obligations, (0, drizzle_orm.eq)(obligationIdKeys.obligationKey, obligations.obligationKey)).innerJoin(obligationCollateralsV2, (0, drizzle_orm.eq)(obligations.obligationKey, obligationCollateralsV2.obligationKey)).leftJoinLateral(bestAskTick, drizzle_orm.sql`true`).leftJoinLateral(bestBidTick, drizzle_orm.sql`true`).groupBy(obligationIdKeys.obligationId, obligationIdKeys.chainId, obligations.loanToken, obligations.maturity).where((0, drizzle_orm.and)(ids !== void 0 && ids.length > 0 ? (0, drizzle_orm.inArray)(obligationIdKeys.obligationId, ids) : void 0, chainIds !== void 0 && chainIds.length > 0 ? (0, drizzle_orm.inArray)(obligationIdKeys.chainId, chainIds) : void 0, loanTokenFilter, maturities !== void 0 && maturities.length > 0 ? (0, drizzle_orm.inArray)(obligations.maturity, maturities) : (0, drizzle_orm.gte)(obligations.maturity, now$3), collateralFilter)).as("obligations_with_quotes");
7806
8453
  const sortColumns = {
7807
8454
  id: obligationsWithQuotes.obligationId,
7808
8455
  ask: obligationsWithQuotes.ask,
@@ -7822,22 +8469,25 @@ function create$16(parameters) {
7822
8469
  }).from(obligationsWithQuotes).where(buildCursorFilter(sortColumns, sort, cursorValues)).orderBy(...buildOrderBy(sortColumns, sort)).limit(limit + 1);
7823
8470
  const hasMore = rows.length > limit;
7824
8471
  const listedRows = (hasMore ? rows.slice(0, limit) : rows).map((row) => {
7825
- return {
7826
- obligation: from$15({
7827
- chainId: row.chainId,
7828
- loanToken: row.loanToken,
7829
- collaterals: row.collaterals.sort((left, right) => left.asset.localeCompare(right.asset)).map((collateral) => from$17({
7830
- asset: collateral.asset,
7831
- oracle: collateral.oracle,
7832
- lltv: from$18(BigInt(collateral.lltv))
7833
- })),
7834
- maturity: row.maturity
7835
- }),
7836
- quote: from$11({
7837
- obligationId: row.obligationId,
7838
- ask: { tick: row.askTick },
7839
- bid: { tick: row.bidTick }
7840
- }),
8472
+ const obligation = from$15({
8473
+ loanToken: row.loanToken,
8474
+ collaterals: row.collaterals.sort((left, right) => left.asset.localeCompare(right.asset)).map((collateral) => from$17({
8475
+ asset: collateral.asset,
8476
+ oracle: collateral.oracle,
8477
+ lltv: from$18(BigInt(collateral.lltv))
8478
+ })),
8479
+ maturity: row.maturity
8480
+ });
8481
+ const quote = from$11({
8482
+ obligationId: row.obligationId,
8483
+ ask: { tick: row.askTick },
8484
+ bid: { tick: row.bidTick }
8485
+ });
8486
+ return {
8487
+ obligationId: row.obligationId,
8488
+ chainId: row.chainId,
8489
+ obligation,
8490
+ quote,
7841
8491
  cursorValues: {
7842
8492
  id: row.obligationId,
7843
8493
  ask: toBigInt(row.ask),
@@ -7855,6 +8505,8 @@ function create$16(parameters) {
7855
8505
  }) : null;
7856
8506
  return {
7857
8507
  obligations: listedRows.map((row) => ({
8508
+ obligationId: row.obligationId,
8509
+ chainId: row.chainId,
7858
8510
  obligation: row.obligation,
7859
8511
  quote: row.quote
7860
8512
  })),
@@ -7992,7 +8644,7 @@ async function getObligation(params, db) {
7992
8644
  if (listing.obligations.length === 0) return failure(new NotFoundError("Obligation not found"));
7993
8645
  const obligation = listing.obligations[0];
7994
8646
  return success({
7995
- data: from$4(obligation.obligation, obligation.quote),
8647
+ data: from$4(obligation.obligation, obligation.quote, obligation.chainId),
7996
8648
  cursor: null
7997
8649
  });
7998
8650
  } catch (err) {
@@ -8033,7 +8685,7 @@ async function getObligations$1(queryParameters, db) {
8033
8685
  limit: query.limit
8034
8686
  });
8035
8687
  return success({
8036
- data: listing.obligations.map((item) => from$4(item.obligation, item.quote)),
8688
+ data: listing.obligations.map((item) => from$4(item.obligation, item.quote, item.chainId)),
8037
8689
  cursor: listing.nextCursor
8038
8690
  });
8039
8691
  } catch (err) {
@@ -8071,10 +8723,10 @@ function create$15(config) {
8071
8723
  return {
8072
8724
  create: async (batches) => {
8073
8725
  if (batches.length === 0) return [];
8074
- const offersRows = batches.flatMap(({ blockNumber, offers }) => offers.map((offer) => ({
8726
+ const offersRows = batches.flatMap(({ blockNumber, offers }) => offers.map(({ offer, obligationId, chainId }) => ({
8075
8727
  ...serialize(offer),
8076
- obligationId: obligationId(offer),
8077
- groupChainId: offer.chainId,
8728
+ obligationId: obligationId.toLowerCase(),
8729
+ groupChainId: chainId,
8078
8730
  groupMaker: offer.maker.toLowerCase(),
8079
8731
  callbackAddress: offer.callback.address.toLowerCase(),
8080
8732
  callbackData: offer.callback.data,
@@ -8084,48 +8736,67 @@ function create$15(config) {
8084
8736
  if (offersRows.length === 0) return [];
8085
8737
  try {
8086
8738
  return await db.transaction(async (dbTx) => {
8087
- const selectExisting = async (hashes) => {
8088
- if (hashes.length === 0) return /* @__PURE__ */ new Set();
8739
+ const keyOf = (input) => `${input.hash.toLowerCase()}:${input.obligationId.toLowerCase()}`;
8740
+ const selectExisting = async (offers$1) => {
8741
+ if (offers$1.length === 0) return /* @__PURE__ */ new Set();
8742
+ const expected = new Set(offers$1.map((offer) => keyOf({
8743
+ hash: offer.hash,
8744
+ obligationId: offer.obligationId
8745
+ })));
8089
8746
  const existing = /* @__PURE__ */ new Set();
8747
+ const hashes = [...new Set(offers$1.map((offer) => offer.hash.toLowerCase()))];
8090
8748
  for (const batch of batch$1(hashes, DEFAULT_BATCH_SIZE$1)) {
8091
- const rows = await dbTx.select({ hash: offers.hash }).from(offers).where((0, drizzle_orm.inArray)(offers.hash, batch));
8092
- for (const row of rows) existing.add(String(row.hash).toLowerCase());
8749
+ const rows = await dbTx.select({
8750
+ hash: offers.hash,
8751
+ obligationId: offers.obligationId
8752
+ }).from(offers).where((0, drizzle_orm.inArray)(offers.hash, batch));
8753
+ for (const row of rows) {
8754
+ const key = keyOf({
8755
+ hash: String(row.hash),
8756
+ obligationId: String(row.obligationId)
8757
+ });
8758
+ if (expected.has(key)) existing.add(key);
8759
+ }
8093
8760
  }
8094
8761
  return existing;
8095
8762
  };
8096
8763
  const inserted = [];
8097
8764
  for (const batch of batch$1(offersRows, DEFAULT_BATCH_SIZE$1)) {
8098
8765
  const rows = await dbTx.insert(offers).values(batch).onConflictDoNothing().returning();
8099
- inserted.push(...rows.map((row) => row.hash));
8766
+ inserted.push(...rows.map((row) => ({
8767
+ hash: row.hash,
8768
+ obligationId: row.obligationId
8769
+ })));
8100
8770
  }
8101
8771
  const existing = await selectExisting(inserted);
8102
- return inserted.filter((hash) => existing.has(hash));
8772
+ return inserted.filter((offer) => existing.has(keyOf({
8773
+ hash: offer.hash,
8774
+ obligationId: offer.obligationId
8775
+ })));
8103
8776
  });
8104
8777
  } catch (err) {
8105
8778
  const error = err instanceof Error ? err : new Error(String(err));
8106
- throw new Error("Offers.create failed. Ensure obligations and groups exist before inserting offers.", { cause: error });
8779
+ throw new Error("Offers.create failed. Ensure obligation id keys and groups exist before inserting offers.", { cause: error });
8107
8780
  }
8108
8781
  },
8109
8782
  get: async (parameters) => {
8110
8783
  const limit = parameters?.limit ?? DEFAULT_LIMIT$3;
8111
- const cursor = parameters?.cursor;
8784
+ const rawCursor = parameters?.cursor;
8112
8785
  const maker = parameters?.maker;
8113
- if (cursor !== null && cursor !== void 0) {
8114
- if (!cursor.startsWith("0x") || cursor.length !== 66) throw new Error("Invalid cursor format");
8115
- }
8786
+ const cursor = rawCursor !== null && rawCursor !== void 0 ? parseCursor$1(rawCursor) : void 0;
8116
8787
  const collateralsLateral = db.select({ collaterals: drizzle_orm.sql`COALESCE(
8117
8788
  jsonb_agg(
8118
8789
  jsonb_build_object(
8119
8790
  'asset', ${obligationCollateralsV2.asset},
8120
- 'oracle', ${oracles$1.address},
8791
+ 'oracle', ${obligationCollateralsV2.oracleAddress},
8121
8792
  'lltv', ${obligationCollateralsV2.lltv}
8122
8793
  )
8123
8794
  ),
8124
8795
  '[]'::jsonb
8125
- )`.as("collaterals") }).from(obligationCollateralsV2).innerJoin(oracles$1, drizzle_orm.sql`${obligationCollateralsV2.oracleChainId} = ${oracles$1.chainId}
8126
- AND ${obligationCollateralsV2.oracleAddress} = ${oracles$1.address}`).where((0, drizzle_orm.eq)(obligationCollateralsV2.obligationId, offers.obligationId)).as("collaterals_lateral");
8796
+ )`.as("collaterals") }).from(obligationCollateralsV2).where((0, drizzle_orm.eq)(obligationCollateralsV2.obligationKey, obligationIdKeys.obligationKey)).as("collaterals_lateral");
8127
8797
  const rows = (await db.select({
8128
8798
  hash: offers.hash,
8799
+ obligationId: offers.obligationId,
8129
8800
  maker: offers.groupMaker,
8130
8801
  assets: offers.assets,
8131
8802
  obligationUnits: offers.obligationUnits,
@@ -8137,17 +8808,18 @@ function create$15(config) {
8137
8808
  group: offers.group,
8138
8809
  session: offers.session,
8139
8810
  buy: offers.buy,
8140
- chainId: obligations.chainId,
8811
+ chainId: obligationIdKeys.chainId,
8141
8812
  loanToken: obligations.loanToken,
8142
8813
  callbackAddress: offers.callbackAddress,
8143
8814
  callbackData: offers.callbackData,
8144
8815
  receiverIfMakerIsSeller: offers.receiverIfMakerIsSeller,
8145
8816
  collaterals: collateralsLateral.collaterals,
8146
8817
  blockNumber: offers.blockNumber
8147
- }).from(offers).innerJoin(obligations, (0, drizzle_orm.eq)(offers.obligationId, obligations.obligationId)).innerJoinLateral(collateralsLateral, drizzle_orm.sql`true`).where((0, drizzle_orm.and)(cursor !== null && cursor !== void 0 ? (0, drizzle_orm.gt)(offers.hash, cursor) : void 0, maker !== void 0 ? (0, drizzle_orm.eq)(offers.groupMaker, maker.toLowerCase()) : void 0)).orderBy((0, drizzle_orm.asc)(offers.hash)).limit(limit)).map((row) => {
8818
+ }).from(offers).innerJoin(obligationIdKeys, (0, drizzle_orm.eq)(offers.obligationId, obligationIdKeys.obligationId)).innerJoin(obligations, (0, drizzle_orm.eq)(obligationIdKeys.obligationKey, obligations.obligationKey)).innerJoinLateral(collateralsLateral, drizzle_orm.sql`true`).where((0, drizzle_orm.and)(cursor !== void 0 ? (0, drizzle_orm.or)((0, drizzle_orm.gt)(offers.hash, cursor.hash), (0, drizzle_orm.and)((0, drizzle_orm.eq)(offers.hash, cursor.hash), (0, drizzle_orm.gt)(offers.obligationId, cursor.obligationId))) : void 0, maker !== void 0 ? (0, drizzle_orm.eq)(offers.groupMaker, maker.toLowerCase()) : void 0)).orderBy((0, drizzle_orm.asc)(offers.hash), (0, drizzle_orm.asc)(offers.obligationId)).limit(limit)).map((row) => {
8148
8819
  const receiverIfMakerIsSeller = (row.receiverIfMakerIsSeller ?? row.maker).toLowerCase();
8149
8820
  return {
8150
8821
  hash: row.hash,
8822
+ obligationId: row.obligationId,
8151
8823
  maker: row.maker,
8152
8824
  assets: BigInt(row.assets),
8153
8825
  obligationUnits: BigInt(row.obligationUnits),
@@ -8179,7 +8851,10 @@ function create$15(config) {
8179
8851
  });
8180
8852
  return {
8181
8853
  rows,
8182
- nextCursor: rows.length === limit ? rows[rows.length - 1].hash : null
8854
+ nextCursor: rows.length === limit ? formatCursor$1({
8855
+ hash: rows[rows.length - 1].hash,
8856
+ obligationId: rows[rows.length - 1].obligationId
8857
+ }) : null
8183
8858
  };
8184
8859
  },
8185
8860
  delete: async (parameters) => {
@@ -8202,7 +8877,7 @@ function create$15(config) {
8202
8877
  const query = ({ side }) => db.selectDistinctOn([offers.obligationId], {
8203
8878
  obligationId: offers.obligationId,
8204
8879
  tick: offers.tick
8205
- }).from(offers).innerJoin(groups, (0, drizzle_orm.and)((0, drizzle_orm.eq)(offers.groupChainId, groups.chainId), (0, drizzle_orm.eq)(offers.groupMaker, groups.maker), (0, drizzle_orm.eq)(offers.group, groups.group))).leftJoin(validations, (0, drizzle_orm.eq)(offers.hash, validations.offerHash)).leftJoin(status, (0, drizzle_orm.eq)(validations.statusId, status.id)).where((0, drizzle_orm.and)((0, drizzle_orm.inArray)(offers.obligationId, obligationIds), (0, drizzle_orm.eq)(offers.buy, side === "buy"), (0, drizzle_orm.gte)(offers.expiry, now$2), (0, drizzle_orm.gte)(offers.maturity, now$2), (0, drizzle_orm.lte)(offers.start, now$2), drizzle_orm.sql`(${status.code} IS NULL OR ${status.code} = ${Status.VALID})`)).orderBy(offers.obligationId, side === "buy" ? drizzle_orm.sql`${offers.tick} ASC` : drizzle_orm.sql`${offers.tick} DESC`);
8880
+ }).from(offers).innerJoin(groups, (0, drizzle_orm.and)((0, drizzle_orm.eq)(offers.groupChainId, groups.chainId), (0, drizzle_orm.eq)(offers.groupMaker, groups.maker), (0, drizzle_orm.eq)(offers.group, groups.group))).leftJoin(validations, (0, drizzle_orm.and)((0, drizzle_orm.eq)(offers.hash, validations.offerHash), (0, drizzle_orm.eq)(offers.obligationId, validations.obligationId))).leftJoin(status, (0, drizzle_orm.eq)(validations.statusId, status.id)).where((0, drizzle_orm.and)((0, drizzle_orm.inArray)(offers.obligationId, obligationIds), (0, drizzle_orm.eq)(offers.buy, side === "buy"), (0, drizzle_orm.gte)(offers.expiry, now$2), (0, drizzle_orm.gte)(offers.maturity, now$2), (0, drizzle_orm.lte)(offers.start, now$2), drizzle_orm.sql`(${status.code} IS NULL OR ${status.code} = ${Status.VALID})`)).orderBy(offers.obligationId, side === "buy" ? drizzle_orm.sql`${offers.tick} ASC` : drizzle_orm.sql`${offers.tick} DESC`);
8206
8881
  const [bestBuys, bestSells] = await Promise.all([query({ side: "buy" }), query({ side: "sell" })]);
8207
8882
  const quotes = /* @__PURE__ */ new Map();
8208
8883
  for (const row of bestSells) quotes.set(row.obligationId, {
@@ -8232,6 +8907,134 @@ function create$15(config) {
8232
8907
  }
8233
8908
  };
8234
8909
  }
8910
+ const HEX_32$2 = /^0x[a-fA-F0-9]{64}$/;
8911
+ function parseCursor$1(cursor) {
8912
+ const [hash, obligationId] = cursor.split(":");
8913
+ if (!hash || !obligationId || !HEX_32$2.test(hash) || !HEX_32$2.test(obligationId)) throw new Error("Invalid cursor format");
8914
+ return {
8915
+ hash: hash.toLowerCase(),
8916
+ obligationId: obligationId.toLowerCase()
8917
+ };
8918
+ }
8919
+ function formatCursor$1(input) {
8920
+ return `${input.hash.toLowerCase()}:${input.obligationId.toLowerCase()}`;
8921
+ }
8922
+
8923
+ //#endregion
8924
+ //#region src/database/domains/Trees.ts
8925
+ /**
8926
+ * Creates a Trees domain instance for managing merkle tree metadata.
8927
+ *
8928
+ * @param config - Configuration with database instance
8929
+ * @returns TreesDomain instance
8930
+ */
8931
+ function create$14(config) {
8932
+ const db = config.db;
8933
+ return {
8934
+ upsert: async (trees$1) => {
8935
+ if (trees$1.length === 0) return [];
8936
+ try {
8937
+ return await db.transaction(async (dbTx) => {
8938
+ const roots = [];
8939
+ for (const { root, signature } of trees$1) {
8940
+ const normalizedRoot = root.toLowerCase();
8941
+ const normalizedSignature = signature.toLowerCase();
8942
+ roots.push(normalizedRoot);
8943
+ await dbTx.insert(trees).values({
8944
+ root: normalizedRoot,
8945
+ rootSignature: normalizedSignature
8946
+ }).onConflictDoUpdate({
8947
+ target: [trees.root],
8948
+ set: {
8949
+ rootSignature: normalizedSignature,
8950
+ createdAt: drizzle_orm.sql`NOW()`
8951
+ }
8952
+ });
8953
+ }
8954
+ return roots;
8955
+ });
8956
+ } catch (err) {
8957
+ const error = err instanceof Error ? err : new Error(String(err));
8958
+ throw new Error("Trees.upsert failed. Ensure obligations and offers exist before upserting roots.", { cause: error });
8959
+ }
8960
+ },
8961
+ upsertPaths: async (paths) => {
8962
+ if (paths.length === 0) return;
8963
+ const rows = paths.map((path) => ({
8964
+ offerHash: path.offerHash.toLowerCase(),
8965
+ obligationId: path.obligationId.toLowerCase(),
8966
+ treeRoot: path.treeRoot.toLowerCase(),
8967
+ proofNodes: concatenateProofs(path.proof)
8968
+ }));
8969
+ try {
8970
+ for (const batch of batch$1(rows, DEFAULT_BATCH_SIZE$1)) await db.insert(merklePaths).values(batch).onConflictDoUpdate({
8971
+ target: [merklePaths.offerHash, merklePaths.obligationId],
8972
+ set: {
8973
+ treeRoot: drizzle_orm.sql`excluded.tree_root`,
8974
+ proofNodes: drizzle_orm.sql`excluded.proof_nodes`,
8975
+ createdAt: drizzle_orm.sql`NOW()`
8976
+ }
8977
+ });
8978
+ } catch (err) {
8979
+ const error = err instanceof Error ? err : new Error(String(err));
8980
+ throw new Error("Trees.upsertPaths failed. Ensure offers and roots exist before inserting merkle paths.", { cause: error });
8981
+ }
8982
+ },
8983
+ getAttestations: async (references) => {
8984
+ if (references.length === 0) return /* @__PURE__ */ new Map();
8985
+ const normalizedReferences = references.map((reference) => ({
8986
+ offerHash: reference.offerHash.toLowerCase(),
8987
+ obligationId: reference.obligationId.toLowerCase()
8988
+ }));
8989
+ const hashes = [...new Set(normalizedReferences.map((reference) => reference.offerHash))];
8990
+ const obligationIds = [...new Set(normalizedReferences.map((reference) => reference.obligationId))];
8991
+ const results = await db.select({
8992
+ offerHash: merklePaths.offerHash,
8993
+ obligationId: merklePaths.obligationId,
8994
+ treeRoot: merklePaths.treeRoot,
8995
+ proofNodes: merklePaths.proofNodes,
8996
+ rootSignature: trees.rootSignature
8997
+ }).from(merklePaths).innerJoin(trees, (0, drizzle_orm.eq)(merklePaths.treeRoot, trees.root)).where(drizzle_orm.sql`${(0, drizzle_orm.inArray)(merklePaths.offerHash, hashes)} AND ${(0, drizzle_orm.inArray)(merklePaths.obligationId, obligationIds)}`);
8998
+ const expectedKeys = new Set(normalizedReferences.map(toAttestationKey));
8999
+ const attestationMap = /* @__PURE__ */ new Map();
9000
+ for (const row of results) {
9001
+ const key = toAttestationKey({
9002
+ offerHash: row.offerHash,
9003
+ obligationId: row.obligationId
9004
+ });
9005
+ if (!expectedKeys.has(key)) continue;
9006
+ attestationMap.set(key, {
9007
+ root: row.treeRoot.toLowerCase(),
9008
+ signature: row.rootSignature.toLowerCase(),
9009
+ proof: splitProofs(row.proofNodes)
9010
+ });
9011
+ }
9012
+ return attestationMap;
9013
+ }
9014
+ };
9015
+ }
9016
+ /**
9017
+ * Concatenates an array of 32-byte hex hashes into a single hex string.
9018
+ * Empty arrays return "0x".
9019
+ */
9020
+ function concatenateProofs(proofs) {
9021
+ if (proofs.length === 0) return "0x";
9022
+ return `0x${proofs.map((proof) => proof.toLowerCase().slice(2)).join("")}`;
9023
+ }
9024
+ /**
9025
+ * Splits a concatenated hex string back into an array of 32-byte hex hashes.
9026
+ * Returns empty array for "0x" or empty string.
9027
+ */
9028
+ function splitProofs(concatenated) {
9029
+ if (!concatenated || concatenated === "0x" || concatenated.length <= 2) return [];
9030
+ const hex = concatenated.slice(2);
9031
+ const proofs = [];
9032
+ for (let i = 0; i < hex.length; i += 64) proofs.push(`0x${hex.slice(i, i + 64).toLowerCase()}`);
9033
+ return proofs;
9034
+ }
9035
+ function toAttestationKey(offer) {
9036
+ return `${offer.offerHash.toLowerCase()}:${offer.obligationId.toLowerCase()}`;
9037
+ }
8235
9038
 
8236
9039
  //#endregion
8237
9040
  //#region src/api/Controllers/getOffers.ts
@@ -8243,23 +9046,20 @@ function create$15(config) {
8243
9046
  */
8244
9047
  async function getOffersQuery(db, parameters) {
8245
9048
  const limit = parameters?.limit ?? DEFAULT_LIMIT$3;
8246
- const cursor = parameters?.cursor;
9049
+ const rawCursor = parameters?.cursor;
8247
9050
  const maker = parameters?.maker;
8248
- if (cursor !== null && cursor !== void 0) {
8249
- if (!cursor.startsWith("0x") || cursor.length !== 66) throw new Error("Invalid cursor format");
8250
- }
9051
+ const cursor = maker && rawCursor !== void 0 && rawCursor !== null ? parseMakerCursor(rawCursor) : void 0;
8251
9052
  const now = Math.floor((Date.now() - 1) / 1e3);
8252
9053
  const collateralsLateral = db.select({ collaterals: drizzle_orm.sql`COALESCE(
8253
9054
  jsonb_agg(
8254
9055
  jsonb_build_object(
8255
9056
  'asset', ${obligationCollateralsV2.asset},
8256
- 'oracle', ${oracles$1.address},
9057
+ 'oracle', ${obligationCollateralsV2.oracleAddress},
8257
9058
  'lltv', ${obligationCollateralsV2.lltv}
8258
9059
  )
8259
9060
  ),
8260
9061
  '[]'::jsonb
8261
- )`.as("collaterals") }).from(obligationCollateralsV2).innerJoin(oracles$1, drizzle_orm.sql`${obligationCollateralsV2.oracleChainId} = ${oracles$1.chainId}
8262
- AND ${obligationCollateralsV2.oracleAddress} = ${oracles$1.address}`).where((0, drizzle_orm.eq)(obligationCollateralsV2.obligationId, offers.obligationId)).as("collaterals_lateral");
9062
+ )`.as("collaterals") }).from(obligationCollateralsV2).where((0, drizzle_orm.eq)(obligationCollateralsV2.obligationKey, obligationIdKeys.obligationKey)).as("collaterals_lateral");
8263
9063
  const lotBalanceExpr = drizzle_orm.sql`GREATEST(0, LEAST(
8264
9064
  COALESCE(${positions.balance}, 0)::numeric
8265
9065
  + COALESCE((
@@ -8304,6 +9104,7 @@ async function getOffersQuery(db, parameters) {
8304
9104
  AND LOWER(${lots.user}) = LOWER(${callbacks.positionUser})
8305
9105
  AND LOWER(${lots.group}) = LOWER(${offers.group})
8306
9106
  WHERE ${offersCallbacks.offerHash} = ${offers.hash}
9107
+ AND ${offersCallbacks.obligationId} = ${offers.obligationId}
8307
9108
  ORDER BY
8308
9109
  ${callbacks.positionChainId},
8309
9110
  LOWER(${callbacks.positionContract}),
@@ -8313,6 +9114,7 @@ async function getOffersQuery(db, parameters) {
8313
9114
  ), 0)`;
8314
9115
  const rows = (await db.select({
8315
9116
  hash: offers.hash,
9117
+ obligationId: offers.obligationId,
8316
9118
  maker: offers.groupMaker,
8317
9119
  assets: offers.assets,
8318
9120
  obligationUnits: offers.obligationUnits,
@@ -8325,7 +9127,7 @@ async function getOffersQuery(db, parameters) {
8325
9127
  group: offers.group,
8326
9128
  session: offers.session,
8327
9129
  buy: offers.buy,
8328
- chainId: obligations.chainId,
9130
+ chainId: obligationIdKeys.chainId,
8329
9131
  loanToken: obligations.loanToken,
8330
9132
  callbackAddress: offers.callbackAddress,
8331
9133
  callbackData: offers.callbackData,
@@ -8342,7 +9144,7 @@ async function getOffersQuery(db, parameters) {
8342
9144
  )
8343
9145
  END
8344
9146
  ))`.as("takeable")
8345
- }).from(offers).innerJoin(obligations, (0, drizzle_orm.eq)(offers.obligationId, obligations.obligationId)).innerJoin(groups, (0, drizzle_orm.and)((0, drizzle_orm.eq)(offers.groupChainId, groups.chainId), (0, drizzle_orm.eq)(offers.groupMaker, groups.maker), (0, drizzle_orm.eq)(offers.group, groups.group))).innerJoinLateral(collateralsLateral, drizzle_orm.sql`true`).where((0, drizzle_orm.and)(cursor !== null && cursor !== void 0 ? (0, drizzle_orm.gt)(offers.hash, cursor) : void 0, maker !== void 0 ? (0, drizzle_orm.eq)(offers.groupMaker, maker.toLowerCase()) : void 0, (0, drizzle_orm.gte)(offers.expiry, now), (0, drizzle_orm.gte)(offers.maturity, now), maker === void 0 ? drizzle_orm.sql`GREATEST(0,
9147
+ }).from(offers).innerJoin(obligationIdKeys, (0, drizzle_orm.eq)(offers.obligationId, obligationIdKeys.obligationId)).innerJoin(obligations, (0, drizzle_orm.eq)(obligationIdKeys.obligationKey, obligations.obligationKey)).innerJoin(groups, (0, drizzle_orm.and)((0, drizzle_orm.eq)(offers.groupChainId, groups.chainId), (0, drizzle_orm.eq)(offers.groupMaker, groups.maker), (0, drizzle_orm.eq)(offers.group, groups.group))).innerJoinLateral(collateralsLateral, drizzle_orm.sql`true`).where((0, drizzle_orm.and)(cursor !== void 0 ? cursor.obligationId === void 0 ? (0, drizzle_orm.gt)(offers.hash, cursor.hash) : (0, drizzle_orm.or)((0, drizzle_orm.gt)(offers.hash, cursor.hash), (0, drizzle_orm.and)((0, drizzle_orm.eq)(offers.hash, cursor.hash), (0, drizzle_orm.gt)(offers.obligationId, cursor.obligationId))) : void 0, maker !== void 0 ? (0, drizzle_orm.eq)(offers.groupMaker, maker.toLowerCase()) : void 0, (0, drizzle_orm.gte)(offers.expiry, now), (0, drizzle_orm.gte)(offers.maturity, now), maker === void 0 ? drizzle_orm.sql`GREATEST(0,
8346
9148
  CASE WHEN ${offers.buy} = false
8347
9149
  THEN ${offers.assets}::numeric - ${groups.consumed}::numeric
8348
9150
  ELSE LEAST(
@@ -8350,10 +9152,11 @@ async function getOffersQuery(db, parameters) {
8350
9152
  ${availableExpr}::numeric
8351
9153
  )
8352
9154
  END
8353
- ) > 0` : void 0)).orderBy((0, drizzle_orm.asc)(offers.hash)).limit(limit)).map((row) => {
9155
+ ) > 0` : void 0)).orderBy((0, drizzle_orm.asc)(offers.hash), (0, drizzle_orm.asc)(offers.obligationId)).limit(limit)).map((row) => {
8354
9156
  const receiverIfMakerIsSeller = (row.receiverIfMakerIsSeller ?? row.maker).toLowerCase();
8355
9157
  return {
8356
9158
  hash: row.hash,
9159
+ obligationId: row.obligationId,
8357
9160
  maker: row.maker,
8358
9161
  assets: BigInt(row.assets),
8359
9162
  obligationUnits: BigInt(row.obligationUnits),
@@ -8385,7 +9188,7 @@ async function getOffersQuery(db, parameters) {
8385
9188
  });
8386
9189
  return {
8387
9190
  rows,
8388
- nextCursor: rows.length === limit ? rows[rows.length - 1].hash : null
9191
+ nextCursor: rows.length === limit ? maker === void 0 ? rows[rows.length - 1].hash : formatOfferCursor(rows[rows.length - 1]) : null
8389
9192
  };
8390
9193
  }
8391
9194
  async function getOffers$1(queryParameters, db) {
@@ -8404,12 +9207,18 @@ async function getOffers$1(queryParameters, db) {
8404
9207
  cursor: query.cursor,
8405
9208
  limit: query.limit
8406
9209
  });
8407
- const hashes = rows.map((row) => row.hash);
8408
- const attestationMap = await db.trees.getAttestations(hashes);
9210
+ const offers = rows.map((row) => ({
9211
+ offerHash: row.hash,
9212
+ obligationId: row.obligationId
9213
+ }));
9214
+ const attestationMap = await db.trees.getAttestations(offers);
8409
9215
  return success({
8410
9216
  data: rows.map((row) => {
8411
- const hash$2 = hash(row);
8412
- const attestation = attestationMap.get(hash$2);
9217
+ const key = toAttestationKey({
9218
+ offerHash: row.hash,
9219
+ obligationId: row.obligationId
9220
+ });
9221
+ const attestation = attestationMap.get(key);
8413
9222
  return from$3({
8414
9223
  ...row,
8415
9224
  ...attestation
@@ -8427,6 +9236,19 @@ async function getOffers$1(queryParameters, db) {
8427
9236
  return failure(err);
8428
9237
  }
8429
9238
  }
9239
+ function parseMakerCursor(cursor) {
9240
+ const [rawHash, rawObligationId, ...tail] = cursor.split(":");
9241
+ if (tail.length > 0) throw new Error("Invalid cursor format");
9242
+ if (!rawHash || !rawObligationId || !HEX_32$1.test(rawHash) || !HEX_32$1.test(rawObligationId)) throw new Error("Invalid cursor format");
9243
+ return {
9244
+ hash: rawHash.toLowerCase(),
9245
+ obligationId: rawObligationId.toLowerCase()
9246
+ };
9247
+ }
9248
+ function formatOfferCursor(row) {
9249
+ return `${row.hash.toLowerCase()}:${row.obligationId.toLowerCase()}`;
9250
+ }
9251
+ const HEX_32$1 = /^0x[a-fA-F0-9]{64}$/;
8430
9252
 
8431
9253
  //#endregion
8432
9254
  //#region src/api/Controllers/getUserPositions.ts
@@ -8468,7 +9290,8 @@ async function validateOffers(body, gatekeeper) {
8468
9290
  const logger = getLogger();
8469
9291
  const result = safeParse("validate_offers", body, (issue) => issue.message);
8470
9292
  if (!result.success) return failure(new BadRequestError$1(result.error.issues[0]?.message ?? "Invalid request body"));
8471
- const { offers: rawOffers } = result.data;
9293
+ const { offers: rawOffers, chain_id: rawChainId } = result.data;
9294
+ const chainId = rawChainId;
8472
9295
  const parsedOffers = [];
8473
9296
  const offerIndexByHash = /* @__PURE__ */ new Map();
8474
9297
  for (let i = 0; i < rawOffers.length; i++) {
@@ -8487,7 +9310,10 @@ async function validateOffers(body, gatekeeper) {
8487
9310
  }
8488
9311
  }
8489
9312
  try {
8490
- const { issues } = await gatekeeper.isAllowed(parsedOffers);
9313
+ const { issues } = await gatekeeper.isAllowed({
9314
+ offers: parsedOffers,
9315
+ chainId
9316
+ });
8491
9317
  if (issues.length > 0) {
8492
9318
  const mappedIssues = issues.map((issue) => {
8493
9319
  const index = offerIndexByHash.get(hash(issue.item));
@@ -8548,14 +9374,14 @@ var Controllers_exports = /* @__PURE__ */ __exportAll({
8548
9374
  //#region src/api/Api.ts
8549
9375
  function from$1(config) {
8550
9376
  const { db, gatekeeper, port, chainRegistry } = config;
8551
- return create$14({
9377
+ return create$13({
8552
9378
  port,
8553
9379
  db,
8554
9380
  gatekeeper,
8555
9381
  chainRegistry
8556
9382
  });
8557
9383
  }
8558
- function create$14(params) {
9384
+ function create$13(params) {
8559
9385
  return { serve: () => serve(params) };
8560
9386
  }
8561
9387
  /**
@@ -8682,7 +9508,7 @@ var RouterApi_exports = /* @__PURE__ */ __exportAll({
8682
9508
  RouterStatusResponse: () => RouterStatusResponse,
8683
9509
  UsersController: () => UsersController,
8684
9510
  ValidateController: () => ValidateController,
8685
- create: () => create$14,
9511
+ create: () => create$13,
8686
9512
  from: () => from$1,
8687
9513
  parse: () => parse,
8688
9514
  safeParse: () => safeParse
@@ -8765,7 +9591,6 @@ async function getOffers(apiClient, parameters) {
8765
9591
  group: offerData.group,
8766
9592
  session: offerData.session,
8767
9593
  buy: offerData.buy,
8768
- chain_id: item.chain_id,
8769
9594
  loan_token: offerData.obligation.loan_token,
8770
9595
  collaterals: offerData.obligation.collaterals.map((collateral) => ({
8771
9596
  asset: collateral.token,
@@ -8813,7 +9638,6 @@ async function getObligations(apiClient, parameters) {
8813
9638
  }
8814
9639
  const obligations = data?.data.map((item) => {
8815
9640
  const obligation = fromSnakeCase$2({
8816
- chain_id: item.chain_id,
8817
9641
  loan_token: item.loan_token,
8818
9642
  collaterals: item.collaterals.map((collateral) => ({
8819
9643
  asset: collateral.token,
@@ -8822,16 +9646,17 @@ async function getObligations(apiClient, parameters) {
8822
9646
  })),
8823
9647
  maturity: from$16(item.maturity)
8824
9648
  });
8825
- const { obligationId: _, ...returned } = {
8826
- id: () => id(obligation),
9649
+ const { obligationId: _, ...quote } = from$11({
9650
+ obligationId: item.id,
9651
+ ask: { tick: item.ask.tick },
9652
+ bid: { tick: item.bid.tick }
9653
+ });
9654
+ return {
9655
+ id: item.id,
9656
+ chainId: item.chain_id,
8827
9657
  ...obligation,
8828
- ...from$11({
8829
- obligationId: item.id,
8830
- ask: { tick: item.ask.tick },
8831
- bid: { tick: item.bid.tick }
8832
- })
9658
+ ...quote
8833
9659
  };
8834
- return returned;
8835
9660
  }) ?? [];
8836
9661
  return {
8837
9662
  cursor: data?.cursor ?? null,
@@ -8878,13 +9703,15 @@ var drizzle_exports = /* @__PURE__ */ __exportAll({
8878
9703
  VERSION: () => VERSION,
8879
9704
  VERSIONED_TABLE_NAMES: () => VERSIONED_TABLE_NAMES,
8880
9705
  callbacks: () => callbacks,
8881
- chains: () => chains$1,
9706
+ chains: () => chains,
8882
9707
  collectors: () => collectors,
8883
9708
  consumedEvents: () => consumedEvents,
8884
9709
  groups: () => groups,
8885
9710
  lots: () => lots,
9711
+ lotsPositions: () => lotsPositions,
8886
9712
  merklePaths: () => merklePaths,
8887
9713
  obligationCollateralsV2: () => obligationCollateralsV2,
9714
+ obligationIdKeys: () => obligationIdKeys,
8888
9715
  obligations: () => obligations,
8889
9716
  offers: () => offers,
8890
9717
  offersCallbacks: () => offersCallbacks,
@@ -8901,14 +9728,14 @@ var drizzle_exports = /* @__PURE__ */ __exportAll({
8901
9728
  //#endregion
8902
9729
  //#region src/database/domains/Blocks.ts
8903
9730
  /** Postgres implementation. */
8904
- const create$13 = (config) => {
9731
+ const create$12 = (config) => {
8905
9732
  const { db, chainRegistry } = config;
8906
9733
  const getChain = async (chainId) => {
8907
9734
  const rows = await db.select({
8908
- chainId: chains$1.chainId,
8909
- blockNumber: chains$1.blockNumber,
8910
- epoch: chains$1.epoch
8911
- }).from(chains$1).where((0, drizzle_orm.eq)(chains$1.chainId, chainId)).limit(1);
9735
+ chainId: chains.chainId,
9736
+ blockNumber: chains.blockNumber,
9737
+ epoch: chains.epoch
9738
+ }).from(chains).where((0, drizzle_orm.eq)(chains.chainId, chainId)).limit(1);
8912
9739
  if (rows.length === 0) throw new Error(`Chain state not initialized for chain ${chainId}. Call blocks.init first.`);
8913
9740
  const row = rows[0];
8914
9741
  return {
@@ -8936,11 +9763,11 @@ const create$13 = (config) => {
8936
9763
  };
8937
9764
  const getChains = async (parameters) => {
8938
9765
  return (await db.select({
8939
- chainId: chains$1.chainId,
8940
- blockNumber: chains$1.blockNumber,
8941
- epoch: chains$1.epoch,
8942
- updatedAt: chains$1.updatedAt
8943
- }).from(chains$1).where(parameters?.chainId !== void 0 ? (0, drizzle_orm.eq)(chains$1.chainId, parameters.chainId) : drizzle_orm.sql`TRUE`).orderBy((0, drizzle_orm.asc)(chains$1.chainId))).map((row) => ({
9766
+ chainId: chains.chainId,
9767
+ blockNumber: chains.blockNumber,
9768
+ epoch: chains.epoch,
9769
+ updatedAt: chains.updatedAt
9770
+ }).from(chains).where(parameters?.chainId !== void 0 ? (0, drizzle_orm.eq)(chains.chainId, parameters.chainId) : drizzle_orm.sql`TRUE`).orderBy((0, drizzle_orm.asc)(chains.chainId))).map((row) => ({
8944
9771
  chainId: row.chainId,
8945
9772
  blockNumber: Number(row.blockNumber),
8946
9773
  epoch: BigInt(row.epoch),
@@ -8966,14 +9793,14 @@ const create$13 = (config) => {
8966
9793
  const name = parameters.collectorName.toLowerCase();
8967
9794
  const deploymentBlock = chainRegistry.getById(parameters.chainId)?.custom?.mempool.blockCreated ?? 0;
8968
9795
  const { chain, collector } = await db.transaction(async (dbTx) => {
8969
- const chainRows = await dbTx.insert(chains$1).values({
9796
+ const chainRows = await dbTx.insert(chains).values({
8970
9797
  chainId: parameters.chainId,
8971
9798
  blockNumber: deploymentBlock
8972
9799
  }).onConflictDoUpdate({
8973
- target: chains$1.chainId,
9800
+ target: chains.chainId,
8974
9801
  set: {
8975
- blockNumber: drizzle_orm.sql`${chains$1.blockNumber}`,
8976
- epoch: drizzle_orm.sql`${chains$1.epoch}`
9802
+ blockNumber: drizzle_orm.sql`${chains.blockNumber}`,
9803
+ epoch: drizzle_orm.sql`${chains.epoch}`
8977
9804
  }
8978
9805
  }).returning();
8979
9806
  if (chainRows.length === 0) throw new Error(`Failed to initialize chain state for chain ${parameters.chainId}`);
@@ -9013,28 +9840,28 @@ const create$13 = (config) => {
9013
9840
  const advanceChain = async (parameters) => {
9014
9841
  if (parameters.blockNumber < 0) throw new Error("Block number cannot be negative");
9015
9842
  if (parameters.epoch < 0n) throw new Error("Epoch cannot be negative");
9016
- if ((await db.update(chains$1).set({
9843
+ if ((await db.update(chains).set({
9017
9844
  blockNumber: parameters.blockNumber,
9018
9845
  epoch: parameters.epoch.toString(),
9019
9846
  updatedAt: drizzle_orm.sql`now()`
9020
- }).where((0, drizzle_orm.eq)(chains$1.chainId, parameters.chainId)).returning()).length === 0) throw new Error(`Chain state not initialized for chain ${parameters.chainId}. Call blocks.init first.`);
9847
+ }).where((0, drizzle_orm.eq)(chains.chainId, parameters.chainId)).returning()).length === 0) throw new Error(`Chain state not initialized for chain ${parameters.chainId}. Call blocks.init first.`);
9021
9848
  };
9022
9849
  const advanceCollector = async (parameters) => {
9023
9850
  if (parameters.blockNumber < 0) throw new Error("Block number cannot be negative");
9024
9851
  if (parameters.epoch < 0n) throw new Error("Epoch cannot be negative");
9025
9852
  const name = parameters.collectorName.toLowerCase();
9026
9853
  const chain = db.select({
9027
- chainId: chains$1.chainId,
9028
- currentEpoch: chains$1.epoch,
9029
- currentBlockNumber: chains$1.blockNumber
9030
- }).from(chains$1).where((0, drizzle_orm.eq)(chains$1.chainId, parameters.chainId)).as("chain");
9854
+ chainId: chains.chainId,
9855
+ currentEpoch: chains.epoch,
9856
+ currentBlockNumber: chains.blockNumber
9857
+ }).from(chains).where((0, drizzle_orm.eq)(chains.chainId, parameters.chainId)).as("chain");
9031
9858
  const hasReorgHappened = (await db.select().from(collectors).leftJoin(chain, (0, drizzle_orm.eq)(collectors.chainId, chain.chainId)).where((0, drizzle_orm.and)((0, drizzle_orm.eq)(collectors.chainId, parameters.chainId), (0, drizzle_orm.eq)(collectors.name, name), (0, drizzle_orm.gt)(chain.currentEpoch, collectors.epoch), (0, drizzle_orm.eq)(chain.currentEpoch, parameters.epoch.toString()), (0, drizzle_orm.gte)(collectors.blockNumber, parameters.blockNumber))).limit(1)).length > 0;
9032
9859
  if ((await db.update(collectors).set({
9033
9860
  blockNumber: parameters.blockNumber,
9034
9861
  epoch: parameters.epoch.toString(),
9035
9862
  updatedAt: drizzle_orm.sql`now()`
9036
9863
  }).from(chain).where((0, drizzle_orm.and)((0, drizzle_orm.eq)(collectors.chainId, parameters.chainId), (0, drizzle_orm.eq)(collectors.name, name), (0, drizzle_orm.eq)(chain.currentEpoch, parameters.epoch.toString()), (0, drizzle_orm.gte)(chain.currentBlockNumber, parameters.blockNumber), ...hasReorgHappened ? [] : [(0, drizzle_orm.lte)(collectors.blockNumber, parameters.blockNumber)])).returning()).length > 0) return;
9037
- if ((await db.select({ chainId: chains$1.chainId }).from(chains$1).where((0, drizzle_orm.eq)(chains$1.chainId, parameters.chainId)).limit(1)).length === 0) throw new Error(`Chain state not initialized for chain ${parameters.chainId}. Call blocks.init first.`);
9864
+ if ((await db.select({ chainId: chains.chainId }).from(chains).where((0, drizzle_orm.eq)(chains.chainId, parameters.chainId)).limit(1)).length === 0) throw new Error(`Chain state not initialized for chain ${parameters.chainId}. Call blocks.init first.`);
9038
9865
  if ((await db.select({ chainId: collectors.chainId }).from(collectors).where((0, drizzle_orm.and)((0, drizzle_orm.eq)(collectors.chainId, parameters.chainId), (0, drizzle_orm.eq)(collectors.name, name))).limit(1)).length === 0) throw new Error(`Collector state not initialized for ${name} on chain ${parameters.chainId}. Call blocks.init first.`);
9039
9866
  throw new Error(`A chain reorg has happened on chain ${parameters.chainId}, update of the collector ${name} is aborted`);
9040
9867
  };
@@ -9082,7 +9909,7 @@ const create$13 = (config) => {
9082
9909
  //#region src/database/domains/Book.ts
9083
9910
  const DEFAULT_LIMIT$2 = 100;
9084
9911
  const MAX_TOTAL_OFFERS = 500;
9085
- function create$12(config) {
9912
+ function create$11(config) {
9086
9913
  const db = config.db;
9087
9914
  const logger = getLogger();
9088
9915
  const getOffers = async (parameters) => {
@@ -9168,18 +9995,17 @@ async function _getOffers(db, params) {
9168
9995
  const { obligationId, side, now, priceSortDirection, cursor, limit } = params;
9169
9996
  const raw = await db.execute(drizzle_orm.sql`
9170
9997
  WITH collats AS MATERIALIZED (
9171
- SELECT oc.obligation_id,
9998
+ SELECT oia.obligation_id,
9172
9999
  COALESCE(jsonb_agg(jsonb_build_object(
9173
10000
  'asset', oc.asset,
9174
- 'oracle', oracle.address,
10001
+ 'oracle', oc.oracle_address,
9175
10002
  'lltv', oc.lltv
9176
10003
  ) ORDER BY oc.asset), '[]'::jsonb) AS collaterals
9177
- FROM ${obligationCollateralsV2} oc
9178
- JOIN ${oracles$1} oracle
9179
- ON oracle.chain_id = oc.oracle_chain_id
9180
- AND oracle.address = oc.oracle_address
9181
- WHERE oc.obligation_id = ${obligationId}
9182
- GROUP BY oc.obligation_id
10004
+ FROM ${obligationIdKeys} oia
10005
+ JOIN ${obligationCollateralsV2} oc
10006
+ ON oc.obligation_key = oia.obligation_key
10007
+ WHERE oia.obligation_id = ${obligationId}
10008
+ GROUP BY oia.obligation_id
9183
10009
  ),
9184
10010
  winners AS (
9185
10011
  SELECT DISTINCT ON (o.group_chain_id, o.group_maker, o."group_group")
@@ -9187,6 +10013,7 @@ async function _getOffers(db, params) {
9187
10013
  FROM ${offers} o
9188
10014
  LEFT JOIN ${validations} v
9189
10015
  ON v.offer_hash = o.hash
10016
+ AND v.obligation_id = o.obligation_id
9190
10017
  LEFT JOIN ${status} s
9191
10018
  ON s.id = v.status_id
9192
10019
  WHERE o.obligation_id = ${obligationId}
@@ -9213,8 +10040,10 @@ async function _getOffers(db, params) {
9213
10040
  ON g.chain_id = w.group_chain_id
9214
10041
  AND g.maker = w.group_maker
9215
10042
  AND g."group" = w."group_group"
10043
+ JOIN ${obligationIdKeys} oia
10044
+ ON oia.obligation_id = w.obligation_id
9216
10045
  JOIN ${obligations} obl
9217
- ON obl.obligation_id = w.obligation_id
10046
+ ON obl.obligation_key = oia.obligation_key
9218
10047
  ),
9219
10048
  paged AS (
9220
10049
  SELECT e.*
@@ -9313,7 +10142,9 @@ async function _getOffers(db, params) {
9313
10142
  END
9314
10143
  )) AS lot_balance
9315
10144
  FROM paged p
9316
- LEFT JOIN ${offersCallbacks} oc ON oc.offer_hash = p.hash
10145
+ LEFT JOIN ${offersCallbacks} oc
10146
+ ON oc.offer_hash = p.hash
10147
+ AND oc.obligation_id = p.obligation_id
9317
10148
  LEFT JOIN ${callbacks} c ON c.id = oc.callback_id
9318
10149
  LEFT JOIN ${lots} l
9319
10150
  ON l.chain_id = c.position_chain_id
@@ -9398,11 +10229,13 @@ async function _getOffers(db, params) {
9398
10229
  AND NOT EXISTS (
9399
10230
  SELECT 1 FROM ${offersCallbacks} oc2
9400
10231
  WHERE oc2.offer_hash = p.hash
10232
+ AND oc2.obligation_id = p.obligation_id
9401
10233
  )
9402
10234
  )
9403
10235
  -- Final SELECT with inline takeable computation
9404
10236
  SELECT
9405
10237
  oc.hash,
10238
+ oc.obligation_id,
9406
10239
  oc.group_maker,
9407
10240
  oc.assets,
9408
10241
  oc.obligation_units,
@@ -9451,6 +10284,7 @@ async function _getOffers(db, params) {
9451
10284
  const receiverIfMakerIsSeller = (row.receiver_if_maker_is_seller ?? row.group_maker).toLowerCase();
9452
10285
  return {
9453
10286
  hash: row.hash,
10287
+ obligationId: row.obligation_id,
9454
10288
  maker: row.group_maker,
9455
10289
  assets: BigInt(row.assets),
9456
10290
  obligationUnits: BigInt(row.obligation_units ?? 0),
@@ -9550,7 +10384,7 @@ let LevelCursor;
9550
10384
  * @param db - Database core instance.
9551
10385
  * @returns Callbacks domain. {@link CallbacksDomain}
9552
10386
  */
9553
- function create$11(db) {
10387
+ function create$10(db) {
9554
10388
  return {
9555
10389
  upsert: async (inputs) => {
9556
10390
  if (inputs.length === 0) return;
@@ -9564,18 +10398,21 @@ function create$11(db) {
9564
10398
  idCache.set(preimage, id);
9565
10399
  return id;
9566
10400
  };
9567
- for (const { offerHash, callbacks } of inputs) {
10401
+ for (const { offerHash, obligationId, callbacks } of inputs) {
9568
10402
  const normalizedOfferHash = offerHash.toLowerCase();
10403
+ const normalizedObligationId = obligationId.toLowerCase();
9569
10404
  for (const callback of callbacks) {
9570
10405
  const normalized = {
9571
10406
  chainId: callback.chainId,
9572
10407
  contract: callback.contract.toLowerCase(),
9573
10408
  user: callback.user.toLowerCase(),
9574
- amount: callback.amount
10409
+ amount: callback.amount,
10410
+ positionTypeId: callback.positionTypeId
9575
10411
  };
9576
10412
  const id = callbackId(normalized);
9577
10413
  offersCallbacksRows.push({
9578
10414
  offerHash: normalizedOfferHash,
10415
+ obligationId: normalizedObligationId,
9579
10416
  callbackId: id
9580
10417
  });
9581
10418
  if (seenCallbackIds.has(id)) continue;
@@ -9585,6 +10422,7 @@ function create$11(db) {
9585
10422
  positionChainId: normalized.chainId,
9586
10423
  positionContract: normalized.contract,
9587
10424
  positionUser: normalized.user,
10425
+ positionTypeId: normalized.positionTypeId,
9588
10426
  amount: normalized.amount.toString()
9589
10427
  });
9590
10428
  }
@@ -9605,7 +10443,7 @@ function create$11(db) {
9605
10443
 
9606
10444
  //#endregion
9607
10445
  //#region src/database/domains/Consumed.ts
9608
- function create$10(db) {
10446
+ function create$9(db) {
9609
10447
  return {
9610
10448
  create: async (events) => {
9611
10449
  if (events.length === 0) return;
@@ -9653,7 +10491,7 @@ function create$10(db) {
9653
10491
  * @param db - Database core instance.
9654
10492
  * @returns Groups domain. {@link GroupsDomain}
9655
10493
  */
9656
- function create$9(db) {
10494
+ function create$8(db) {
9657
10495
  return { create: async (groups$1) => {
9658
10496
  if (groups$1.length === 0) return;
9659
10497
  const rows = groups$1.map((group) => ({
@@ -9669,7 +10507,7 @@ function create$9(db) {
9669
10507
 
9670
10508
  //#endregion
9671
10509
  //#region src/database/domains/Lots.ts
9672
- function create$8(db) {
10510
+ function create$7(db) {
9673
10511
  return {
9674
10512
  get: async (parameters) => {
9675
10513
  const { chainId, user, contract, group, obligationId } = parameters ?? {};
@@ -9697,6 +10535,17 @@ function create$8(db) {
9697
10535
  const existing = lotsByKey.get(key);
9698
10536
  if (!existing || offer.size > existing.size) lotsByKey.set(key, offer);
9699
10537
  }
10538
+ const positionsByKey = /* @__PURE__ */ new Map();
10539
+ for (const offer of lotsByKey.values()) {
10540
+ const posKey = `${offer.positionChainId}-${offer.positionContract}-${offer.positionUser}`.toLowerCase();
10541
+ if (!positionsByKey.has(posKey)) positionsByKey.set(posKey, {
10542
+ chainId: offer.positionChainId,
10543
+ contract: offer.positionContract.toLowerCase(),
10544
+ user: offer.positionUser.toLowerCase(),
10545
+ positionTypeId: offer.positionTypeId
10546
+ });
10547
+ }
10548
+ for (const row of positionsByKey.values()) await db.insert(lotsPositions).values(row).onConflictDoNothing();
9700
10549
  for (const offer of lotsByKey.values()) if ((await db.select().from(lots).where((0, drizzle_orm.and)((0, drizzle_orm.eq)(lots.chainId, offer.positionChainId), (0, drizzle_orm.eq)(lots.contract, offer.positionContract.toLowerCase()), (0, drizzle_orm.eq)(lots.user, offer.positionUser.toLowerCase()), (0, drizzle_orm.eq)(lots.group, offer.group.toLowerCase()), (0, drizzle_orm.eq)(lots.obligationId, offer.obligationId.toLowerCase()))).limit(1)).length === 0) {
9701
10550
  const maxUpperResult = await db.select({ maxUpper: drizzle_orm.sql`COALESCE(MAX(${lots.upper}::numeric), 0)` }).from(lots).where((0, drizzle_orm.and)((0, drizzle_orm.eq)(lots.chainId, offer.positionChainId), (0, drizzle_orm.eq)(lots.contract, offer.positionContract.toLowerCase()), (0, drizzle_orm.eq)(lots.user, offer.positionUser.toLowerCase()), (0, drizzle_orm.eq)(lots.obligationId, offer.obligationId.toLowerCase())));
9702
10551
  const newLower = BigInt(maxUpperResult[0]?.maxUpper ?? "0");
@@ -9722,58 +10571,72 @@ function create$8(db) {
9722
10571
  * @param db - Database core instance.
9723
10572
  * @returns Obligations domain. {@link ObligationsDomain}
9724
10573
  */
9725
- function create$7(db) {
10574
+ function create$6(db) {
9726
10575
  return {
9727
10576
  get: async (parameters) => {
9728
10577
  const chainIds = parameters?.chainId;
9729
10578
  const now$1 = now();
9730
10579
  return (await db.select({
9731
- chainId: obligations.chainId,
10580
+ obligationId: obligationIdKeys.obligationId,
10581
+ chainId: obligationIdKeys.chainId,
10582
+ morphoV2: obligationIdKeys.morphoV2,
9732
10583
  loanToken: obligations.loanToken,
9733
- collaterals: drizzle_orm.sql`ARRAY_AGG(jsonb_build_object('asset', ${obligationCollateralsV2.asset}, 'oracle', ${oracles$1.address}, 'lltv', ${obligationCollateralsV2.lltv}))`.as("collaterals"),
10584
+ collaterals: drizzle_orm.sql`ARRAY_AGG(jsonb_build_object('asset', ${obligationCollateralsV2.asset}, 'oracle', ${obligationCollateralsV2.oracleAddress}, 'lltv', ${obligationCollateralsV2.lltv}))`.as("collaterals"),
9734
10585
  maturity: obligations.maturity
9735
- }).from(obligations).innerJoin(obligationCollateralsV2, (0, drizzle_orm.eq)(obligations.obligationId, obligationCollateralsV2.obligationId)).innerJoin(oracles$1, drizzle_orm.sql`${obligationCollateralsV2.oracleChainId} = ${oracles$1.chainId}
9736
- AND ${obligationCollateralsV2.oracleAddress} = ${oracles$1.address}`).groupBy(obligations.obligationId).where((0, drizzle_orm.and)(chainIds !== void 0 && chainIds.length > 0 ? (0, drizzle_orm.inArray)(obligations.chainId, chainIds) : void 0, (0, drizzle_orm.gte)(obligations.maturity, now$1))).orderBy((0, drizzle_orm.asc)(obligations.obligationId))).map((row) => from$15({
10586
+ }).from(obligationIdKeys).innerJoin(obligations, (0, drizzle_orm.eq)(obligationIdKeys.obligationKey, obligations.obligationKey)).innerJoin(obligationCollateralsV2, (0, drizzle_orm.eq)(obligations.obligationKey, obligationCollateralsV2.obligationKey)).groupBy(obligationIdKeys.obligationId, obligationIdKeys.chainId, obligationIdKeys.morphoV2, obligations.loanToken, obligations.maturity).where((0, drizzle_orm.and)(chainIds !== void 0 && chainIds.length > 0 ? (0, drizzle_orm.inArray)(obligationIdKeys.chainId, chainIds) : void 0, (0, drizzle_orm.gte)(obligations.maturity, now$1))).orderBy((0, drizzle_orm.asc)(obligationIdKeys.obligationId))).map((row) => ({
10587
+ obligationId: row.obligationId,
9737
10588
  chainId: row.chainId,
9738
- loanToken: row.loanToken,
9739
- collaterals: row.collaterals.sort((left, right) => left.asset.localeCompare(right.asset)).map((collateral) => from$17({
9740
- asset: collateral.asset,
9741
- oracle: collateral.oracle,
9742
- lltv: from$18(BigInt(collateral.lltv))
9743
- })),
9744
- maturity: row.maturity
10589
+ morphoV2: row.morphoV2,
10590
+ obligation: from$15({
10591
+ loanToken: row.loanToken,
10592
+ collaterals: row.collaterals.sort((left, right) => left.asset.localeCompare(right.asset)).map((collateral) => from$17({
10593
+ asset: collateral.asset,
10594
+ oracle: collateral.oracle,
10595
+ lltv: from$18(BigInt(collateral.lltv))
10596
+ })),
10597
+ maturity: row.maturity
10598
+ })
9745
10599
  }));
9746
10600
  },
9747
10601
  create: async (obligations$1) => {
9748
10602
  if (obligations$1.length === 0) return;
9749
- const obligationsById = /* @__PURE__ */ new Map();
9750
- for (const obligation of obligations$1) {
9751
- const id$1 = id(obligation).toLowerCase();
9752
- if (!obligationsById.get(id$1)) obligationsById.set(id$1, obligation);
10603
+ const obligationsByKey = /* @__PURE__ */ new Map();
10604
+ const obligationIdKeysById = /* @__PURE__ */ new Map();
10605
+ for (const input of obligations$1) {
10606
+ const obligationKey = key(input.obligation).toLowerCase();
10607
+ if (!obligationsByKey.has(obligationKey)) obligationsByKey.set(obligationKey, input.obligation);
10608
+ const obligationId = input.obligationId.toLowerCase();
10609
+ if (!obligationIdKeysById.has(obligationId)) obligationIdKeysById.set(obligationId, input);
9753
10610
  }
9754
10611
  try {
9755
10612
  await db.transaction(async (dbTx) => {
9756
- const obligationRows = obligations$1.map((obligation) => ({
9757
- obligationId: id(obligation),
9758
- chainId: obligation.chainId,
9759
- loanToken: obligation.loanToken.toLowerCase(),
9760
- maturity: obligation.maturity
10613
+ const obligationRows = Array.from(obligationsByKey.entries()).map(([obligationKey, item]) => ({
10614
+ obligationKey,
10615
+ loanToken: item.loanToken.toLowerCase(),
10616
+ maturity: item.maturity
9761
10617
  }));
9762
10618
  for (const batch of batch$1(obligationRows, DEFAULT_BATCH_SIZE$1)) await dbTx.insert(obligations).values(batch).onConflictDoNothing();
9763
- const collateralRows = obligations$1.flatMap((obligation) => {
9764
- return obligation.collaterals.map((collateral) => ({
9765
- obligationId: id(obligation),
10619
+ const obligationIdKeyRows = Array.from(obligationIdKeysById.entries()).map(([obligationId, input]) => ({
10620
+ obligationId,
10621
+ obligationKey: key(input.obligation).toLowerCase(),
10622
+ chainId: input.chainId,
10623
+ morphoV2: input.morphoV2.toLowerCase()
10624
+ }));
10625
+ for (const batch of batch$1(obligationIdKeyRows, DEFAULT_BATCH_SIZE$1)) await dbTx.insert(obligationIdKeys).values(batch).onConflictDoNothing();
10626
+ const collateralRows = Array.from(obligationsByKey.entries()).flatMap(([obligationKey, item]) => {
10627
+ return [...item.collaterals].sort((a, b) => a.asset.toLowerCase().localeCompare(b.asset.toLowerCase())).map((collateral, collateralIndex) => ({
10628
+ obligationKey,
9766
10629
  asset: collateral.asset.toLowerCase(),
9767
- oracleChainId: obligation.chainId,
9768
10630
  oracleAddress: collateral.oracle.toLowerCase(),
9769
- lltv: collateral.lltv
10631
+ lltv: collateral.lltv,
10632
+ collateralIndex
9770
10633
  }));
9771
10634
  });
9772
10635
  for (const batch of batch$1(collateralRows, DEFAULT_BATCH_SIZE$1)) await dbTx.insert(obligationCollateralsV2).values(batch).onConflictDoNothing();
9773
10636
  });
9774
10637
  } catch (err) {
9775
10638
  const error = err instanceof Error ? err : new Error(String(err));
9776
- throw new Error("Obligations.create failed. Ensure oracles exist before inserting obligations.", { cause: error });
10639
+ throw new Error("Obligations.create failed. Ensure obligation id keys are valid before inserting offers.", { cause: error });
9777
10640
  }
9778
10641
  }
9779
10642
  };
@@ -9781,7 +10644,7 @@ function create$7(db) {
9781
10644
 
9782
10645
  //#endregion
9783
10646
  //#region src/database/domains/Offsets.ts
9784
- function create$6(db) {
10647
+ function create$5(db) {
9785
10648
  return { get: async (parameters) => {
9786
10649
  const { chainId, user, contract, group, obligationId } = parameters ?? {};
9787
10650
  const conditions = [];
@@ -9803,7 +10666,7 @@ function create$6(db) {
9803
10666
 
9804
10667
  //#endregion
9805
10668
  //#region src/database/domains/Oracles.ts
9806
- function create$5(db) {
10669
+ function create$4(db) {
9807
10670
  return {
9808
10671
  get: async ({ chainId }) => {
9809
10672
  return (await db.select({
@@ -9846,19 +10709,20 @@ function create$5(db) {
9846
10709
  //#endregion
9847
10710
  //#region src/database/domains/Positions.ts
9848
10711
  const DEFAULT_LIMIT$1 = 100;
9849
- const create$4 = (db) => {
10712
+ const create$3 = (db) => {
9850
10713
  return {
9851
10714
  upsert: async (positions$1) => {
9852
10715
  const positionsMap = /* @__PURE__ */ new Map();
9853
10716
  for (const p of positions$1) {
9854
- const key = `${p.chainId}-${p.contract}-${p.user}`.toLowerCase();
9855
- const zeroKey = `${p.chainId}-${p.contract}-${viem.zeroAddress}`.toLowerCase();
10717
+ const key = `${p.chainId}-${p.contract}-${p.user}-${p.asset}`.toLowerCase();
10718
+ const zeroKey = `${p.chainId}-${p.contract}-${viem.zeroAddress}-${p.asset}`.toLowerCase();
9856
10719
  if (!positionsMap.has(zeroKey)) positionsMap.set(zeroKey, {
9857
10720
  chainId: p.chainId,
9858
10721
  contract: p.contract,
9859
10722
  user: viem.zeroAddress,
9860
10723
  balance: 0n,
9861
10724
  type: p.type,
10725
+ asset: p.asset,
9862
10726
  blockNumber: p.blockNumber
9863
10727
  });
9864
10728
  if (!positionsMap.has(key)) {
@@ -9869,14 +10733,14 @@ const create$4 = (db) => {
9869
10733
  }
9870
10734
  if (positionsMap.size === 0) return 0;
9871
10735
  const rows = Array.from(positionsMap.values()).map((p) => {
9872
- const positionTypeId = Object.values(Type).indexOf(p.type) + 1;
10736
+ const positionTypeId$1 = positionTypeId(p.type);
9873
10737
  return {
9874
10738
  chainId: p.chainId,
9875
10739
  contract: p.contract.toLowerCase(),
9876
10740
  user: p.user.toLowerCase(),
9877
- positionTypeId,
10741
+ positionTypeId: positionTypeId$1,
9878
10742
  ...p.balance !== void 0 ? { balance: p.balance.toString() } : {},
9879
- ...p.asset !== void 0 ? { asset: p.asset.toLowerCase() } : {},
10743
+ asset: p.asset.toLowerCase(),
9880
10744
  blockNumber: p.blockNumber
9881
10745
  };
9882
10746
  });
@@ -9886,11 +10750,12 @@ const create$4 = (db) => {
9886
10750
  target: [
9887
10751
  positions.chainId,
9888
10752
  positions.contract,
9889
- positions.user
10753
+ positions.user,
10754
+ positions.positionTypeId,
10755
+ positions.asset
9890
10756
  ],
9891
10757
  set: {
9892
10758
  balance: drizzle_orm.sql`EXCLUDED.balance`,
9893
- asset: drizzle_orm.sql`COALESCE(EXCLUDED.asset, ${positions.asset})`,
9894
10759
  blockNumber: drizzle_orm.sql`EXCLUDED.block_number`,
9895
10760
  updatedAt: drizzle_orm.sql`NOW()`
9896
10761
  },
@@ -9909,20 +10774,23 @@ const create$4 = (db) => {
9909
10774
  cursor = {
9910
10775
  chainId: parsed.chainId,
9911
10776
  contract: parsed.contract,
9912
- user: parsed.user
10777
+ user: parsed.user,
10778
+ positionTypeId: parsed.positionTypeId ?? 0,
10779
+ asset: parsed.asset ?? ""
9913
10780
  };
9914
10781
  }
9915
- const positions$2 = await db.select().from(positions).where((0, drizzle_orm.and)((0, drizzle_orm.ne)(positions.user, viem.zeroAddress), filled === void 0 ? drizzle_orm.sql`true` : drizzle_orm.sql`${positions.balance} IS ${filled === true ? drizzle_orm.sql`NOT` : drizzle_orm.sql``} NULL`, type !== void 0 ? (0, drizzle_orm.eq)(positions.positionTypeId, Object.values(Type).indexOf(type) + 1) : drizzle_orm.sql`true`, chainId !== void 0 ? (0, drizzle_orm.eq)(positions.chainId, chainId) : drizzle_orm.sql`true`, (() => {
10782
+ const positions$2 = await db.select().from(positions).where((0, drizzle_orm.and)((0, drizzle_orm.ne)(positions.user, viem.zeroAddress), filled === void 0 ? drizzle_orm.sql`true` : drizzle_orm.sql`${positions.balance} IS ${filled === true ? drizzle_orm.sql`NOT` : drizzle_orm.sql``} NULL`, type !== void 0 ? (0, drizzle_orm.eq)(positions.positionTypeId, positionTypeId(type)) : drizzle_orm.sql`true`, chainId !== void 0 ? (0, drizzle_orm.eq)(positions.chainId, chainId) : drizzle_orm.sql`true`, (() => {
9916
10783
  if (cursor === null || cursor === void 0) return drizzle_orm.sql`true`;
9917
10784
  return drizzle_orm.sql`
9918
- (${chainId === void 0 ? drizzle_orm.sql`"chain_id", ` : drizzle_orm.sql``}"contract", "user") > (${chainId === void 0 ? drizzle_orm.sql`${cursor.chainId}, ` : drizzle_orm.sql``}${cursor.contract}, ${cursor.user})
10785
+ (${chainId === void 0 ? drizzle_orm.sql`"chain_id", ` : drizzle_orm.sql``}"contract", "user", "position_type_id", "asset") > (${chainId === void 0 ? drizzle_orm.sql`${cursor.chainId}, ` : drizzle_orm.sql``}${cursor.contract}, ${cursor.user}, ${cursor.positionTypeId}, ${cursor.asset})
9919
10786
  `;
9920
- })())).orderBy((0, drizzle_orm.asc)(positions.chainId), (0, drizzle_orm.asc)(positions.contract), (0, drizzle_orm.asc)(positions.user), (0, drizzle_orm.asc)(positions.blockNumber)).limit(limit);
10787
+ })())).orderBy((0, drizzle_orm.asc)(positions.chainId), (0, drizzle_orm.asc)(positions.contract), (0, drizzle_orm.asc)(positions.user), (0, drizzle_orm.asc)(positions.positionTypeId), (0, drizzle_orm.asc)(positions.asset)).limit(limit);
9921
10788
  const nextCursor = positions$2.length === limit ? Buffer.from(JSON.stringify({
9922
10789
  chainId: positions$2[positions$2.length - 1].chainId.toString(),
9923
10790
  contract: positions$2[positions$2.length - 1].contract,
9924
10791
  user: positions$2[positions$2.length - 1].user,
9925
- blockNumber: positions$2[positions$2.length - 1].blockNumber.toString()
10792
+ positionTypeId: positions$2[positions$2.length - 1].positionTypeId,
10793
+ asset: positions$2[positions$2.length - 1].asset
9926
10794
  })).toString("base64url") : null;
9927
10795
  return {
9928
10796
  positions: positions$2.map((p) => ({
@@ -9931,14 +10799,14 @@ const create$4 = (db) => {
9931
10799
  user: p.user,
9932
10800
  type: Object.values(Type)[p.positionTypeId - 1],
9933
10801
  balance: p.balance !== null ? BigInt(p.balance) : void 0,
9934
- ...p.asset !== null ? { asset: p.asset } : {},
10802
+ asset: p.asset,
9935
10803
  blockNumber: p.blockNumber
9936
10804
  })),
9937
10805
  nextCursor
9938
10806
  };
9939
10807
  },
9940
10808
  getByUser: async (parameters) => {
9941
- const { user, limit = DEFAULT_LIMIT$1, cursor: encodedCursor } = parameters;
10809
+ const { user, type, limit = DEFAULT_LIMIT$1, cursor: encodedCursor } = parameters;
9942
10810
  let cursor = null;
9943
10811
  if (encodedCursor !== null && encodedCursor !== void 0) {
9944
10812
  const parsed = JSON.parse(Buffer.from(encodedCursor, "base64url").toString("utf8"));
@@ -9946,6 +10814,8 @@ const create$4 = (db) => {
9946
10814
  cursor = {
9947
10815
  chainId: parsed.chainId,
9948
10816
  contract: parsed.contract,
10817
+ positionTypeId: parsed.positionTypeId ?? 0,
10818
+ asset: parsed.asset ?? "",
9949
10819
  obligationId: parsed.obligationId ?? null
9950
10820
  };
9951
10821
  }
@@ -10033,6 +10903,8 @@ const create$4 = (db) => {
10033
10903
  p.contract,
10034
10904
  p."user",
10035
10905
  p.block_number,
10906
+ p.position_type_id,
10907
+ p.asset,
10036
10908
  po.obligation_id,
10037
10909
  COALESCE(po.reserved_balance, '0') AS reserved_balance
10038
10910
  FROM ${positions} p
@@ -10042,14 +10914,18 @@ const create$4 = (db) => {
10042
10914
  AND LOWER(po."user") = LOWER(p."user")
10043
10915
  WHERE LOWER(p."user") = LOWER(${user})
10044
10916
  AND p."user" != ${viem.zeroAddress}
10045
- ${cursor !== null ? drizzle_orm.sql`AND (p.chain_id, p.contract, COALESCE(po.obligation_id, '')) > (${cursor.chainId}, ${cursor.contract}, ${cursor.obligationId ?? ""})` : drizzle_orm.sql``}
10046
- ORDER BY p.chain_id ASC, p.contract ASC, po.obligation_id ASC NULLS FIRST
10917
+ ${type !== void 0 ? drizzle_orm.sql`AND p.position_type_id = ${positionTypeId(type)}` : drizzle_orm.sql``}
10918
+ ${cursor !== null ? drizzle_orm.sql`AND (p.chain_id, p.contract, p.position_type_id, p.asset, COALESCE(po.obligation_id, '')) > (${cursor.chainId}, ${cursor.contract}, ${cursor.positionTypeId}, ${cursor.asset}, ${cursor.obligationId ?? ""})` : drizzle_orm.sql``}
10919
+ ORDER BY p.chain_id ASC, p.contract ASC, p.position_type_id ASC, p.asset ASC, po.obligation_id ASC NULLS FIRST
10047
10920
  LIMIT ${limit}
10048
10921
  `);
10049
- const nextCursor = raw.rows.length === limit ? Buffer.from(JSON.stringify({
10050
- chainId: raw.rows[raw.rows.length - 1].chain_id.toString(),
10051
- contract: raw.rows[raw.rows.length - 1].contract,
10052
- obligationId: raw.rows[raw.rows.length - 1].obligation_id
10922
+ const lastRow = raw.rows[raw.rows.length - 1];
10923
+ const nextCursor = raw.rows.length === limit && lastRow !== void 0 ? Buffer.from(JSON.stringify({
10924
+ chainId: lastRow.chain_id.toString(),
10925
+ contract: lastRow.contract,
10926
+ positionTypeId: lastRow.position_type_id,
10927
+ asset: lastRow.asset,
10928
+ obligationId: lastRow.obligation_id
10053
10929
  })).toString("base64url") : null;
10054
10930
  return {
10055
10931
  positions: raw.rows.map((row) => ({
@@ -10064,21 +10940,23 @@ const create$4 = (db) => {
10064
10940
  };
10065
10941
  },
10066
10942
  setEmptyAfter: async (parameters) => {
10067
- const { chainId, blockNumber } = parameters;
10943
+ const { chainId, blockNumber, type } = parameters;
10944
+ const typeId = positionTypeId(type);
10068
10945
  return await db.transaction(async (tx) => {
10069
10946
  const updatedPositions = await tx.update(positions).set({
10070
10947
  balance: null,
10071
10948
  blockNumber,
10072
10949
  updatedAt: drizzle_orm.sql`NOW()`
10073
- }).where((0, drizzle_orm.and)((0, drizzle_orm.eq)(positions.chainId, chainId), drizzle_orm.sql`${positions.blockNumber} >= ${blockNumber}`)).returning();
10950
+ }).where((0, drizzle_orm.and)((0, drizzle_orm.eq)(positions.chainId, chainId), drizzle_orm.sql`${positions.blockNumber} >= ${blockNumber}`, (0, drizzle_orm.eq)(positions.positionTypeId, typeId))).returning();
10074
10951
  if (updatedPositions.length === 0) return 0;
10075
10952
  await tx.execute(drizzle_orm.sql`
10076
10953
  DELETE FROM ${transfers} t
10077
- USING (VALUES ${drizzle_orm.sql.join(updatedPositions.map((u) => drizzle_orm.sql`(${u.chainId}::bigint, ${u.contract}::varchar(42), ${u.user}::varchar(42))`), drizzle_orm.sql`,`)}) AS s(chain_id, "contract", "user")
10954
+ USING (VALUES ${drizzle_orm.sql.join(updatedPositions.map((u) => drizzle_orm.sql`(${u.chainId}::bigint, ${u.contract}::varchar(66), ${u.user}::varchar(42))`), drizzle_orm.sql`,`)}) AS s(chain_id, "contract", "user")
10078
10955
  WHERE
10079
10956
  t.chain_id = s.chain_id
10080
10957
  AND t."contract" = s."contract"
10081
10958
  AND (t."from" = s."user" OR t."to" = s."user")
10959
+ AND t.position_type_id = ${typeId}
10082
10960
  `);
10083
10961
  return updatedPositions.length;
10084
10962
  });
@@ -10088,33 +10966,35 @@ const create$4 = (db) => {
10088
10966
 
10089
10967
  //#endregion
10090
10968
  //#region src/database/domains/Transfers.ts
10091
- const create$3 = (db) => ({ create: async (transfers$1) => {
10092
- if (transfers$1.length === 0) return 0;
10093
- return await db.transaction(async (dbTx) => {
10094
- let totalInserted = 0;
10095
- for (const transfersBatch of batch$1(transfers$1, DEFAULT_BATCH_SIZE$1)) {
10096
- const { rows: inserted } = await dbTx.execute(drizzle_orm.sql`
10097
- INSERT INTO ${transfers} (event_id, chain_id, "contract", "from", "to", "value", block_number)
10098
- SELECT * FROM (VALUES ${drizzle_orm.sql.join(transfersBatch.map((transfer) => drizzle_orm.sql`(${transfer.id}::varchar(128), ${transfer.chainId}::bigint, ${transfer.contract.toLowerCase()}::varchar(42), ${transfer.from.toLowerCase()}::varchar(42), ${transfer.to.toLowerCase()}::varchar(42), ${transfer.value.toString()}::numeric(78, 0), ${transfer.blockNumber}::bigint)`), drizzle_orm.sql`,`)}) AS v(event_id, chain_id, "contract", "from", "to", "value", block_number)
10969
+ const create$2 = (db) => ({
10970
+ create: async (transfers$1) => {
10971
+ if (transfers$1.length === 0) return 0;
10972
+ const typeId = positionTypeId(transfers$1[0].type);
10973
+ return await db.transaction(async (dbTx) => {
10974
+ let totalInserted = 0;
10975
+ for (const transfersBatch of batch$1(transfers$1, DEFAULT_BATCH_SIZE$1)) {
10976
+ const { rows: inserted } = await dbTx.execute(drizzle_orm.sql`
10977
+ INSERT INTO ${transfers} (event_id, chain_id, "contract", "from", "to", "value", position_type_id, asset, block_number)
10978
+ SELECT * FROM (VALUES ${drizzle_orm.sql.join(transfersBatch.map((transfer) => drizzle_orm.sql`(${transfer.id}::varchar(128), ${transfer.chainId}::bigint, ${transfer.contract.toLowerCase()}::varchar(66), ${transfer.from.toLowerCase()}::varchar(42), ${transfer.to.toLowerCase()}::varchar(42), ${transfer.value.toString()}::numeric(78, 0), ${typeId}::integer, ${transfer.asset.toLowerCase()}::varchar(42), ${transfer.blockNumber}::bigint)`), drizzle_orm.sql`,`)}) AS v(event_id, chain_id, "contract", "from", "to", "value", position_type_id, asset, block_number)
10099
10979
  WHERE
10100
10980
  EXISTS (
10101
10981
  SELECT 1 FROM ${positions} p
10102
- WHERE p.chain_id = v.chain_id AND p."contract" = v."contract" AND p."user" = v."from" AND p.balance IS NOT NULL
10982
+ WHERE p.chain_id = v.chain_id AND p."contract" = v."contract" AND p."user" = v."from" AND p.position_type_id = v.position_type_id AND p.asset = v.asset AND p.balance IS NOT NULL
10103
10983
  )
10104
10984
  AND
10105
10985
  EXISTS (
10106
10986
  SELECT 1 FROM ${positions} p
10107
- WHERE p.chain_id = v.chain_id AND p."contract" = v."contract" AND p."user" = v."to" AND p.balance IS NOT NULL
10987
+ WHERE p.chain_id = v.chain_id AND p."contract" = v."contract" AND p."user" = v."to" AND p.position_type_id = v.position_type_id AND p.asset = v.asset AND p.balance IS NOT NULL
10108
10988
  )
10109
10989
  ON CONFLICT DO NOTHING
10110
10990
  RETURNING *;
10111
10991
  `);
10112
- if (inserted.length === 0) continue;
10113
- await dbTx.execute(drizzle_orm.sql`
10992
+ if (inserted.length === 0) continue;
10993
+ await dbTx.execute(drizzle_orm.sql`
10114
10994
  WITH inserted AS (
10115
10995
  VALUES ${drizzle_orm.sql.join(inserted.map((t) => {
10116
- return drizzle_orm.sql`(${t.chain_id}::bigint, ${t.contract}::varchar(42), ${t.from}::varchar(42), ${t.to}::varchar(42), ${t.value}::numeric(78, 0), ${t.block_number}::bigint)`;
10117
- }), drizzle_orm.sql`,`)}
10996
+ return drizzle_orm.sql`(${t.chain_id}::bigint, ${t.contract}::varchar(66), ${t.from}::varchar(42), ${t.to}::varchar(42), ${t.value}::numeric(78, 0), ${t.asset}::varchar(42), ${t.block_number}::bigint)`;
10997
+ }), drizzle_orm.sql`,`)}
10118
10998
  ),
10119
10999
  new_transfers AS (
10120
11000
  SELECT
@@ -10123,7 +11003,8 @@ const create$3 = (db) => ({ create: async (transfers$1) => {
10123
11003
  column3 AS "from",
10124
11004
  column4 AS "to",
10125
11005
  column5 AS "value",
10126
- column6 AS block_number
11006
+ column6 AS asset,
11007
+ column7 AS block_number
10127
11008
  FROM inserted
10128
11009
  ),
10129
11010
  diffs AS (
@@ -10132,6 +11013,7 @@ const create$3 = (db) => ({ create: async (transfers$1) => {
10132
11013
  nt."contract",
10133
11014
  nt."from" AS "user",
10134
11015
  -nt."value" AS delta,
11016
+ nt.asset,
10135
11017
  nt.block_number
10136
11018
  FROM new_transfers nt
10137
11019
  UNION ALL
@@ -10140,6 +11022,7 @@ const create$3 = (db) => ({ create: async (transfers$1) => {
10140
11022
  nt."contract",
10141
11023
  nt."to" AS "user",
10142
11024
  nt."value" AS delta,
11025
+ nt.asset,
10143
11026
  nt.block_number
10144
11027
  FROM new_transfers nt
10145
11028
  ),
@@ -10149,12 +11032,15 @@ const create$3 = (db) => ({ create: async (transfers$1) => {
10149
11032
  d."contract",
10150
11033
  d."user",
10151
11034
  d.delta,
11035
+ d.asset,
10152
11036
  d.block_number
10153
11037
  FROM diffs d
10154
11038
  JOIN ${positions} p
10155
11039
  ON p.chain_id = d.chain_id
10156
11040
  AND p."contract" = d."contract"
10157
11041
  AND p."user" = d."user"
11042
+ AND p.position_type_id = ${typeId}
11043
+ AND p.asset = d.asset
10158
11044
  -- Only keep per-event diffs that are at or after the current position block
10159
11045
  WHERE d.block_number >= p.block_number
10160
11046
  ),
@@ -10163,10 +11049,11 @@ const create$3 = (db) => ({ create: async (transfers$1) => {
10163
11049
  chain_id,
10164
11050
  "contract",
10165
11051
  "user",
11052
+ asset,
10166
11053
  SUM(delta) AS sum_delta,
10167
11054
  MAX(block_number) AS max_block
10168
11055
  FROM valid_diffs
10169
- GROUP BY 1,2,3
11056
+ GROUP BY 1,2,3,4
10170
11057
  )
10171
11058
  UPDATE ${positions} AS p
10172
11059
  SET
@@ -10178,101 +11065,109 @@ const create$3 = (db) => ({ create: async (transfers$1) => {
10178
11065
  p.chain_id = a.chain_id
10179
11066
  AND p."contract" = a."contract"
10180
11067
  AND p."user" = a."user"
11068
+ AND p.asset = a.asset
11069
+ AND p.position_type_id = ${typeId}
10181
11070
  `);
10182
- totalInserted += inserted.length;
10183
- }
10184
- return totalInserted;
10185
- });
10186
- } });
10187
-
10188
- //#endregion
10189
- //#region src/database/domains/Trees.ts
10190
- /**
10191
- * Creates a Trees domain instance for managing merkle tree metadata.
10192
- *
10193
- * @param config - Configuration with database instance
10194
- * @returns TreesDomain instance
10195
- */
10196
- function create$2(config) {
10197
- const db = config.db;
10198
- return {
10199
- create: async (trees$1) => {
10200
- if (trees$1.length === 0) return [];
10201
- try {
10202
- return await db.transaction(async (dbTx) => {
10203
- const roots = [];
10204
- for (const { tree, signature } of trees$1) {
10205
- const root = tree.root.toLowerCase();
10206
- roots.push(root);
10207
- await dbTx.insert(trees).values({
10208
- root,
10209
- rootSignature: signature.toLowerCase()
10210
- }).onConflictDoUpdate({
10211
- target: [trees.root],
10212
- set: {
10213
- rootSignature: signature.toLowerCase(),
10214
- createdAt: drizzle_orm.sql`NOW()`
10215
- }
10216
- });
10217
- const pathRows = proofs(tree).map((proof) => ({
10218
- offerHash: hash(proof.offer).toLowerCase(),
10219
- treeRoot: root,
10220
- proofNodes: concatenateProofs(proof.path)
10221
- }));
10222
- for (const batch of batch$1(pathRows, DEFAULT_BATCH_SIZE$1)) await dbTx.insert(merklePaths).values(batch).onConflictDoUpdate({
10223
- target: [merklePaths.offerHash],
10224
- set: {
10225
- treeRoot: drizzle_orm.sql`excluded.tree_root`,
10226
- proofNodes: drizzle_orm.sql`excluded.proof_nodes`,
10227
- createdAt: drizzle_orm.sql`NOW()`
10228
- }
10229
- });
10230
- }
10231
- return roots;
10232
- });
10233
- } catch (err) {
10234
- const error = err instanceof Error ? err : new Error(String(err));
10235
- throw new Error("Trees.create failed. Ensure offers exist before inserting merkle paths.", { cause: error });
11071
+ totalInserted += inserted.length;
10236
11072
  }
10237
- },
10238
- getAttestations: async (hashes) => {
10239
- if (hashes.length === 0) return /* @__PURE__ */ new Map();
10240
- const normalizedHashes = hashes.map((h) => h.toLowerCase());
10241
- const results = await db.select({
10242
- offerHash: merklePaths.offerHash,
10243
- treeRoot: merklePaths.treeRoot,
10244
- proofNodes: merklePaths.proofNodes,
10245
- rootSignature: trees.rootSignature
10246
- }).from(merklePaths).innerJoin(trees, (0, drizzle_orm.eq)(merklePaths.treeRoot, trees.root)).where((0, drizzle_orm.inArray)(merklePaths.offerHash, normalizedHashes));
10247
- const attestationMap = /* @__PURE__ */ new Map();
10248
- for (const row of results) attestationMap.set(row.offerHash, {
10249
- root: row.treeRoot,
10250
- signature: row.rootSignature,
10251
- proof: splitProofs(row.proofNodes)
10252
- });
10253
- return attestationMap;
10254
- }
10255
- };
10256
- }
10257
- /**
10258
- * Concatenates an array of 32-byte hex hashes into a single hex string.
10259
- * Empty arrays return "0x".
10260
- */
10261
- function concatenateProofs(proofs) {
10262
- if (proofs.length === 0) return "0x";
10263
- return `0x${proofs.map((p) => p.slice(2)).join("")}`;
10264
- }
10265
- /**
10266
- * Splits a concatenated hex string back into an array of 32-byte hex hashes.
10267
- * Returns empty array for "0x" or empty string.
10268
- */
10269
- function splitProofs(concatenated) {
10270
- if (!concatenated || concatenated === "0x" || concatenated.length <= 2) return [];
10271
- const hex = concatenated.slice(2);
10272
- const proofs = [];
10273
- for (let i = 0; i < hex.length; i += 64) proofs.push(`0x${hex.slice(i, i + 64)}`);
10274
- return proofs;
10275
- }
11073
+ return totalInserted;
11074
+ });
11075
+ },
11076
+ delete: async (parameters) => {
11077
+ const { chainId, blockNumberGte, positionTypeId } = parameters;
11078
+ return await db.transaction(async (dbTx) => {
11079
+ const { rows: toDelete } = await dbTx.execute(drizzle_orm.sql`
11080
+ SELECT event_id, chain_id, "contract", "from", "to", "value"::text, asset, block_number
11081
+ FROM ${transfers}
11082
+ WHERE chain_id = ${chainId}
11083
+ AND position_type_id = ${positionTypeId}
11084
+ AND block_number >= ${blockNumberGte}
11085
+ `);
11086
+ if (toDelete.length === 0) return 0;
11087
+ await dbTx.execute(drizzle_orm.sql`
11088
+ WITH to_delete AS (
11089
+ VALUES ${drizzle_orm.sql.join(toDelete.map((t) => drizzle_orm.sql`(${t.chain_id}::bigint, ${t.contract}::varchar(66), ${t.from}::varchar(42), ${t.to}::varchar(42), ${t.value}::numeric(78, 0), ${t.asset}::varchar(42))`), drizzle_orm.sql`,`)}
11090
+ ),
11091
+ reverse_diffs AS (
11092
+ SELECT column1 AS chain_id, column2 AS "contract", column3 AS "user", column5 AS delta, column6 AS asset
11093
+ FROM to_delete
11094
+ UNION ALL
11095
+ SELECT column1 AS chain_id, column2 AS "contract", column4 AS "user", -column5 AS delta, column6 AS asset
11096
+ FROM to_delete
11097
+ ),
11098
+ aggregated AS (
11099
+ SELECT chain_id, "contract", "user", asset, SUM(delta) AS sum_delta
11100
+ FROM reverse_diffs
11101
+ GROUP BY 1,2,3,4
11102
+ ),
11103
+ remaining_max AS (
11104
+ SELECT t.chain_id, t."contract",
11105
+ CASE WHEN t."from" = a."user" THEN t."from" ELSE t."to" END AS "user",
11106
+ t.asset,
11107
+ MAX(t.block_number) AS max_block
11108
+ FROM ${transfers} t
11109
+ JOIN aggregated a
11110
+ ON t.chain_id = a.chain_id AND t."contract" = a."contract"
11111
+ AND t.asset = a.asset
11112
+ AND (t."from" = a."user" OR t."to" = a."user")
11113
+ WHERE t.position_type_id = ${positionTypeId}
11114
+ AND t.block_number < ${blockNumberGte}
11115
+ GROUP BY t.chain_id, t."contract",
11116
+ CASE WHEN t."from" = a."user" THEN t."from" ELSE t."to" END,
11117
+ t.asset
11118
+ )
11119
+ UPDATE ${positions} AS p
11120
+ SET
11121
+ balance = COALESCE(p.balance, 0) + a.sum_delta,
11122
+ block_number = COALESCE(rm.max_block, p.block_number),
11123
+ updated_at = NOW()
11124
+ FROM aggregated a
11125
+ LEFT JOIN remaining_max rm
11126
+ ON rm.chain_id = a.chain_id AND rm."contract" = a."contract" AND rm."user" = a."user" AND rm.asset = a.asset
11127
+ WHERE p.chain_id = a.chain_id
11128
+ AND p."contract" = a."contract"
11129
+ AND p."user" = a."user"
11130
+ AND p.asset = a.asset
11131
+ AND p.position_type_id = ${positionTypeId}
11132
+ `);
11133
+ await dbTx.execute(drizzle_orm.sql`
11134
+ DELETE FROM ${transfers}
11135
+ WHERE chain_id = ${chainId}
11136
+ AND position_type_id = ${positionTypeId}
11137
+ AND block_number >= ${blockNumberGte}
11138
+ `);
11139
+ await dbTx.execute(drizzle_orm.sql`
11140
+ DELETE FROM ${positions} p
11141
+ USING (
11142
+ SELECT DISTINCT chain_id, "contract", "user", asset
11143
+ FROM (
11144
+ SELECT chain_id, "contract", "from" AS "user", asset FROM (
11145
+ VALUES ${drizzle_orm.sql.join(toDelete.map((t) => drizzle_orm.sql`(${t.chain_id}::bigint, ${t.contract}::varchar(66), ${t.from}::varchar(42), ${t.asset}::varchar(42))`), drizzle_orm.sql`,`)}
11146
+ ) AS d(chain_id, "contract", "from", asset)
11147
+ UNION
11148
+ SELECT chain_id, "contract", "to" AS "user", asset FROM (
11149
+ VALUES ${drizzle_orm.sql.join(toDelete.map((t) => drizzle_orm.sql`(${t.chain_id}::bigint, ${t.contract}::varchar(66), ${t.to}::varchar(42), ${t.asset}::varchar(42))`), drizzle_orm.sql`,`)}
11150
+ ) AS d(chain_id, "contract", "to", asset)
11151
+ ) AS combined
11152
+ ) AS affected
11153
+ WHERE p.chain_id = affected.chain_id
11154
+ AND p."contract" = affected."contract"
11155
+ AND p."user" = affected."user"
11156
+ AND p.asset = affected.asset
11157
+ AND p.position_type_id = ${positionTypeId}
11158
+ AND NOT EXISTS (
11159
+ SELECT 1 FROM ${transfers} t
11160
+ WHERE t.chain_id = p.chain_id
11161
+ AND t."contract" = p."contract"
11162
+ AND t.asset = p.asset
11163
+ AND t.position_type_id = ${positionTypeId}
11164
+ AND (t."from" = p."user" OR t."to" = p."user")
11165
+ )
11166
+ `);
11167
+ return toDelete.length;
11168
+ });
11169
+ }
11170
+ });
10276
11171
 
10277
11172
  //#endregion
10278
11173
  //#region src/database/domains/Validations.ts
@@ -10281,26 +11176,26 @@ function create$1(db) {
10281
11176
  return {
10282
11177
  get: async (params) => {
10283
11178
  const { status: status$1, cursor, limit = DEFAULT_LIMIT } = params ?? {};
10284
- if (cursor !== null && cursor !== void 0) {
10285
- if (!cursor.startsWith("0x") || cursor.length !== 66) throw new Error("Invalid cursor format");
10286
- }
11179
+ const parsedCursor = cursor !== null && cursor !== void 0 ? parseCursor(cursor) : void 0;
10287
11180
  let query = db.select({
10288
11181
  offerHash: validations.offerHash,
11182
+ obligationId: validations.obligationId,
10289
11183
  code: status.code
10290
11184
  }).from(validations).innerJoin(status, (0, drizzle_orm.eq)(validations.statusId, status.id));
10291
11185
  if (status$1 !== void 0) query = query.where((0, drizzle_orm.eq)(status.code, status$1));
10292
- if (cursor !== null && cursor !== void 0) {
10293
- const cursorCondition = (0, drizzle_orm.gt)(validations.offerHash, cursor.toLowerCase());
11186
+ if (parsedCursor !== void 0) {
11187
+ const cursorCondition = (0, drizzle_orm.or)((0, drizzle_orm.gt)(validations.offerHash, parsedCursor.offerHash), (0, drizzle_orm.and)((0, drizzle_orm.eq)(validations.offerHash, parsedCursor.offerHash), (0, drizzle_orm.gt)(validations.obligationId, parsedCursor.obligationId)));
10294
11188
  if (status$1 !== void 0) query = query.where((0, drizzle_orm.and)((0, drizzle_orm.eq)(status.code, status$1), cursorCondition));
10295
11189
  else query = query.where(cursorCondition);
10296
11190
  }
10297
- const mapped = (await query.orderBy((0, drizzle_orm.asc)(validations.offerHash)).limit(limit)).map((row) => ({
11191
+ const mapped = (await query.orderBy((0, drizzle_orm.asc)(validations.offerHash), (0, drizzle_orm.asc)(validations.obligationId)).limit(limit)).map((row) => ({
10298
11192
  offerHash: row.offerHash,
11193
+ obligationId: row.obligationId,
10299
11194
  status: row.code
10300
11195
  }));
10301
11196
  return {
10302
11197
  validations: mapped,
10303
- nextCursor: mapped.length === limit ? mapped[mapped.length - 1].offerHash : null
11198
+ nextCursor: mapped.length === limit ? formatCursor(mapped[mapped.length - 1]) : null
10304
11199
  };
10305
11200
  },
10306
11201
  upsert: async (results) => {
@@ -10310,6 +11205,7 @@ function create$1(db) {
10310
11205
  if (invalidStatuses.length > 0) throw new Error(`Unknown validation status: ${invalidStatuses.join(", ")}`);
10311
11206
  const normalized = results.map((r) => ({
10312
11207
  offerHash: r.offerHash.toLowerCase(),
11208
+ obligationId: r.obligationId.toLowerCase(),
10313
11209
  status: r.status
10314
11210
  }));
10315
11211
  const uniqueStatuses = Array.from(new Set(normalized.map((r) => r.status)));
@@ -10321,10 +11217,11 @@ function create$1(db) {
10321
11217
  for (const status of uniqueStatuses) if (!statusMap.has(status)) throw new Error(`Unknown validation status: ${status}`);
10322
11218
  const values = normalized.map((row) => ({
10323
11219
  offerHash: row.offerHash,
11220
+ obligationId: row.obligationId,
10324
11221
  statusId: statusMap.get(row.status)
10325
11222
  }));
10326
11223
  for (const batch of batch$1(values, DEFAULT_BATCH_SIZE$1)) await db.insert(validations).values(batch).onConflictDoUpdate({
10327
- target: [validations.offerHash],
11224
+ target: [validations.offerHash, validations.obligationId],
10328
11225
  set: {
10329
11226
  statusId: drizzle_orm.sql`excluded.status_id`,
10330
11227
  updatedAt: drizzle_orm.sql`NOW()`
@@ -10333,6 +11230,18 @@ function create$1(db) {
10333
11230
  }
10334
11231
  };
10335
11232
  }
11233
+ const HEX_32 = /^0x[a-fA-F0-9]{64}$/;
11234
+ function parseCursor(cursor) {
11235
+ const [offerHash, obligationId] = cursor.split(":");
11236
+ if (!offerHash || !obligationId || !HEX_32.test(offerHash) || !HEX_32.test(obligationId)) throw new Error("Invalid cursor format");
11237
+ return {
11238
+ offerHash: offerHash.toLowerCase(),
11239
+ obligationId: obligationId.toLowerCase()
11240
+ };
11241
+ }
11242
+ function formatCursor(input) {
11243
+ return `${input.offerHash.toLowerCase()}:${input.obligationId.toLowerCase()}`;
11244
+ }
10336
11245
 
10337
11246
  //#endregion
10338
11247
  //#region src/database/Database.ts
@@ -10342,23 +11251,23 @@ var Database_exports = /* @__PURE__ */ __exportAll({
10342
11251
  });
10343
11252
  function createDomains(core, chainRegistry) {
10344
11253
  return {
10345
- book: create$12({ db: core }),
10346
- blocks: create$13({
11254
+ book: create$11({ db: core }),
11255
+ blocks: create$12({
10347
11256
  db: core,
10348
11257
  chainRegistry
10349
11258
  }),
10350
- callbacks: create$11(core),
11259
+ callbacks: create$10(core),
10351
11260
  offers: create$15({ db: core }),
10352
- consumed: create$10(core),
10353
- groups: create$9(core),
10354
- lots: create$8(core),
10355
- obligations: create$7(core),
10356
- offsets: create$6(core),
10357
- oracles: create$5(core),
10358
- trees: create$2({ db: core }),
11261
+ consumed: create$9(core),
11262
+ groups: create$8(core),
11263
+ lots: create$7(core),
11264
+ obligations: create$6(core),
11265
+ offsets: create$5(core),
11266
+ oracles: create$4(core),
11267
+ trees: create$14({ db: core }),
10359
11268
  validations: create$1(core),
10360
- positions: create$4(core),
10361
- transfers: create$3(core)
11269
+ positions: create$3(core),
11270
+ transfers: create$2(core)
10362
11271
  };
10363
11272
  }
10364
11273
  function createReaders(core) {
@@ -10523,7 +11432,7 @@ async function postMigrate(driver) {
10523
11432
  const tracer = getTracer("db.postMigrate");
10524
11433
  await startActiveSpan(tracer, "db.postMigrate", async () => {
10525
11434
  await driver.execute(`INSERT INTO "${VERSION}"."status" ("code") VALUES ('${Status.VALID}'), ('${Status.SIMULATION_ERROR}') ON CONFLICT DO NOTHING;`);
10526
- await driver.execute(`INSERT INTO "${VERSION}"."position_types" ("type") VALUES ('${Type.ERC20}'), ('${Type.VAULT_V1}') ON CONFLICT DO NOTHING;`);
11435
+ await driver.execute(`INSERT INTO "${VERSION}"."position_types" ("type") VALUES ('${Type.ERC20}'), ('${Type.VAULT_V1}'), ('${Type.DEBT_OF}'), ('${Type.COLLATERAL_OF}') ON CONFLICT DO NOTHING;`);
10527
11436
  await driver.execute(`
10528
11437
  CREATE OR REPLACE FUNCTION apply_group_consumed_stmt_ins()
10529
11438
  RETURNS trigger
@@ -10677,19 +11586,23 @@ async function postMigrate(driver) {
10677
11586
  BEGIN
10678
11587
  DELETE FROM "${VERSION}"."positions" p
10679
11588
  USING (
10680
- SELECT DISTINCT c.position_chain_id, c.position_contract, c.position_user
11589
+ SELECT DISTINCT c.position_chain_id, c.position_contract, c.position_user, c.position_type_id
10681
11590
  FROM deleted_rows d
10682
11591
  JOIN "${VERSION}"."callbacks" c ON c.id = d.callback_id
10683
11592
  ) AS affected
11593
+ JOIN "${VERSION}"."position_types" pt ON pt.id = affected.position_type_id
10684
11594
  WHERE p.chain_id = affected.position_chain_id
10685
11595
  AND p.contract = affected.position_contract
10686
11596
  AND p."user" = affected.position_user
11597
+ AND p.position_type_id = affected.position_type_id
11598
+ AND pt.type = 'erc20'
10687
11599
  AND NOT EXISTS (
10688
11600
  SELECT 1 FROM "${VERSION}"."callbacks" c2
10689
11601
  JOIN "${VERSION}"."offers_callbacks" oc ON oc.callback_id = c2.id
10690
11602
  WHERE c2.position_chain_id = p.chain_id
10691
11603
  AND c2.position_contract = p.contract
10692
11604
  AND c2.position_user = p."user"
11605
+ AND c2.position_type_id = p.position_type_id
10693
11606
  );
10694
11607
  RETURN NULL;
10695
11608
  END;
@@ -10736,7 +11649,7 @@ async function postMigrate(driver) {
10736
11649
  RETURNS TRIGGER AS $$
10737
11650
  DECLARE
10738
11651
  orphan_obligation_ids TEXT[];
10739
- candidate_oracles RECORD;
11652
+ orphan_obligation_keys TEXT[];
10740
11653
  BEGIN
10741
11654
  -- 1. Find orphan obligation IDs
10742
11655
  SELECT ARRAY_AGG(DISTINCT obligation_id) INTO orphan_obligation_ids
@@ -10744,33 +11657,39 @@ async function postMigrate(driver) {
10744
11657
  WHERE NOT EXISTS (
10745
11658
  SELECT 1 FROM "${VERSION}"."offers" ov
10746
11659
  WHERE ov.obligation_id = d.obligation_id
11660
+ )
11661
+ AND NOT EXISTS (
11662
+ SELECT 1 FROM "${VERSION}"."positions" p
11663
+ JOIN "${VERSION}"."position_types" pt ON pt.id = p.position_type_id
11664
+ WHERE p."contract" = d.obligation_id
11665
+ AND pt.type IN ('debtOf', 'collateralOf')
10747
11666
  );
10748
11667
 
10749
- -- 2. If no orphan obligations, exit early
11668
+ -- 2. If no orphan obligation IDs, exit early
10750
11669
  IF orphan_obligation_ids IS NULL OR array_length(orphan_obligation_ids, 1) IS NULL THEN
10751
11670
  RETURN NULL;
10752
11671
  END IF;
10753
11672
 
10754
- -- 3. Capture candidate oracles (from collaterals of orphan obligations)
10755
- CREATE TEMP TABLE _candidate_oracles ON COMMIT DROP AS
10756
- SELECT DISTINCT oc.oracle_chain_id, oc.oracle_address
10757
- FROM "${VERSION}"."obligation_collaterals_v2" oc
10758
- WHERE oc.obligation_id = ANY(orphan_obligation_ids);
11673
+ -- 3. Delete orphan obligation id keys
11674
+ DELETE FROM "${VERSION}"."obligation_id_keys" oia
11675
+ WHERE oia.obligation_id = ANY(orphan_obligation_ids);
10759
11676
 
10760
- -- 4. Delete orphan obligations (cascades to collaterals)
10761
- DELETE FROM "${VERSION}"."obligations" ob
10762
- WHERE ob.obligation_id = ANY(orphan_obligation_ids);
11677
+ -- 4. Find canonical obligation keys with no remaining obligation id keys
11678
+ SELECT ARRAY_AGG(ob.obligation_key) INTO orphan_obligation_keys
11679
+ FROM "${VERSION}"."obligations" ob
11680
+ WHERE NOT EXISTS (
11681
+ SELECT 1 FROM "${VERSION}"."obligation_id_keys" oia
11682
+ WHERE oia.obligation_key = ob.obligation_key
11683
+ );
10763
11684
 
10764
- -- 5. Delete oracles that are now orphaned (no remaining collateral references)
10765
- DELETE FROM "${VERSION}"."oracles" o
10766
- USING _candidate_oracles co
10767
- WHERE o.chain_id = co.oracle_chain_id
10768
- AND o.address = co.oracle_address
10769
- AND NOT EXISTS (
10770
- SELECT 1 FROM "${VERSION}"."obligation_collaterals_v2" oc
10771
- WHERE oc.oracle_chain_id = o.chain_id
10772
- AND oc.oracle_address = o.address
10773
- );
11685
+ -- 5. If no orphan canonical obligations, exit early
11686
+ IF orphan_obligation_keys IS NULL OR array_length(orphan_obligation_keys, 1) IS NULL THEN
11687
+ RETURN NULL;
11688
+ END IF;
11689
+
11690
+ -- 6. Delete orphan canonical obligations (cascades to collaterals)
11691
+ DELETE FROM "${VERSION}"."obligations" ob
11692
+ WHERE ob.obligation_key = ANY(orphan_obligation_keys);
10774
11693
 
10775
11694
  RETURN NULL;
10776
11695
  END;
@@ -10788,16 +11707,21 @@ async function postMigrate(driver) {
10788
11707
  RETURNS trigger
10789
11708
  LANGUAGE plpgsql AS $$
10790
11709
  BEGIN
10791
- INSERT INTO "${VERSION}"."offsets" (chain_id, "user", contract, "group", obligation_id, value)
10792
- VALUES (
10793
- OLD.chain_id,
10794
- OLD."user",
10795
- OLD.contract,
10796
- OLD."group",
10797
- OLD.obligation_id,
10798
- OLD.upper::numeric - OLD.lower::numeric
10799
- )
10800
- ON CONFLICT (chain_id, "user", contract, "group", obligation_id) DO NOTHING;
11710
+ BEGIN
11711
+ INSERT INTO "${VERSION}"."offsets" (chain_id, "user", contract, "group", obligation_id, value)
11712
+ VALUES (
11713
+ OLD.chain_id,
11714
+ OLD."user",
11715
+ OLD.contract,
11716
+ OLD."group",
11717
+ OLD.obligation_id,
11718
+ OLD.upper::numeric - OLD.lower::numeric
11719
+ )
11720
+ ON CONFLICT (chain_id, "user", contract, "group", obligation_id) DO NOTHING;
11721
+ EXCEPTION WHEN foreign_key_violation THEN
11722
+ -- lots_positions may be gone during cascaded deletes
11723
+ NULL;
11724
+ END;
10801
11725
  RETURN OLD;
10802
11726
  END;
10803
11727
  $$;
@@ -10820,11 +11744,26 @@ async function postMigrate(driver) {
10820
11744
  AND l.contract = OLD.contract
10821
11745
  AND l."user" = OLD."user"
10822
11746
  ) THEN
10823
- -- No lots remain, delete the position (cascades to offsets)
11747
+ -- No lots remain, delete erc20 positions and their lots_positions bridge rows.
11748
+ -- debtOf/collateralOf positions live independently of offers and must not be deleted here.
11749
+ -- First delete the position (for ERC20, asset = contract).
10824
11750
  DELETE FROM "${VERSION}"."positions" p
10825
- WHERE p.chain_id = OLD.chain_id
10826
- AND p.contract = OLD.contract
10827
- AND p."user" = OLD."user";
11751
+ USING "${VERSION}"."lots_positions" lp
11752
+ JOIN "${VERSION}"."position_types" pt ON pt.id = lp.position_type_id
11753
+ WHERE lp.chain_id = OLD.chain_id
11754
+ AND lp.contract = OLD.contract
11755
+ AND lp."user" = OLD."user"
11756
+ AND p.chain_id = lp.chain_id
11757
+ AND p.contract = lp.contract
11758
+ AND p."user" = lp."user"
11759
+ AND p.position_type_id = lp.position_type_id
11760
+ AND pt.type = 'erc20';
11761
+
11762
+ -- Delete the lots_positions bridge row (cascades to offsets via offsets_lots_positions_fk).
11763
+ DELETE FROM "${VERSION}"."lots_positions" lp
11764
+ WHERE lp.chain_id = OLD.chain_id
11765
+ AND lp.contract = OLD.contract
11766
+ AND lp."user" = OLD."user";
10828
11767
  END IF;
10829
11768
  RETURN NULL;
10830
11769
  END;
@@ -10893,8 +11832,12 @@ function createHttpClient(config) {
10893
11832
  body: json
10894
11833
  };
10895
11834
  };
10896
- const isAllowed = async (offers) => {
10897
- const { statusCode, body } = await validate({ offers: offers.map((offer) => toSnakeCase(offer)) });
11835
+ const isAllowed = async (parameters) => {
11836
+ const { offers, chainId } = parameters;
11837
+ const { statusCode, body } = await validate({
11838
+ chain_id: chainId,
11839
+ offers: offers.map((offer) => toSnakeCase(offer))
11840
+ });
10898
11841
  if (statusCode !== 200) {
10899
11842
  const errorMessage = extractErrorMessage(body);
10900
11843
  throw new Error(`Gatekeeper validation failed: ${errorMessage ?? `status ${statusCode}`}`);
@@ -11036,11 +11979,12 @@ var Gatekeeper_exports = /* @__PURE__ */ __exportAll({ create: () => create });
11036
11979
  * @returns Gatekeeper instance. {@link Gatekeeper}
11037
11980
  */
11038
11981
  function create(parameters) {
11039
- const { rules } = parameters;
11040
- return { isAllowed: async (offers) => {
11982
+ const { rules: rulesFactory } = parameters;
11983
+ return { isAllowed: async (parameters) => {
11984
+ const { offers, chainId } = parameters;
11041
11985
  return await run({
11042
11986
  items: offers,
11043
- rules
11987
+ rules: rulesFactory(chainId)
11044
11988
  });
11045
11989
  } };
11046
11990
  }
@@ -11050,28 +11994,11 @@ function create(parameters) {
11050
11994
  var Rules_exports = /* @__PURE__ */ __exportAll({
11051
11995
  amountMutualExclusivity: () => amountMutualExclusivity,
11052
11996
  callback: () => callback,
11053
- chains: () => chains,
11054
11997
  collateralToken: () => collateralToken,
11055
11998
  loanToken: () => loanToken,
11056
11999
  maturity: () => maturity,
11057
12000
  oracle: () => oracle,
11058
- sameMaker: () => sameMaker,
11059
- validity: () => validity
11060
- });
11061
- /**
11062
- * set of rules to validate offers.
11063
- *
11064
- * @param _parameters - Validity parameters with chain and client
11065
- * @returns Array of validation rules to evaluate against offers
11066
- */
11067
- function validity(_parameters) {
11068
- return [single("expiry", "Validates that offer has not expired", (offer) => {
11069
- if (offer.expiry < Math.floor(Date.now() / 1e3)) return { message: "Expiry mismatch" };
11070
- })];
11071
- }
11072
- const chains = ({ chains }) => single("chain_ids", `Validates that offer chain is one of: [${chains.map((c) => c.id).join(", ")}]`, (offer) => {
11073
- const allowedChainIds = chains.map((c) => c.id);
11074
- if (!allowedChainIds.some((id) => id === offer.chainId)) return { message: `Chain ID ${offer.chainId} is not in the allowed chains (${allowedChainIds.join(", ")})` };
12001
+ sameMaker: () => sameMaker
11075
12002
  });
11076
12003
  const maturity = ({ maturities }) => single("maturity", `Validates that offer maturity is one of: [${maturities.join(", ")}]`, (offer) => {
11077
12004
  const allowedMaturities = maturities.map((m) => from$16(m));
@@ -11087,9 +12014,9 @@ const callback = ({ callbacks }) => single("callback", `Validates callbacks: buy
11087
12014
  * @param assetsByChainId - Allowed loan tokens indexed by chain id.
11088
12015
  * @returns The issue that was found. If the offer is valid, this will be undefined.
11089
12016
  */
11090
- const loanToken = ({ assetsByChainId }) => single("loan_token", "Validates that offer loan token is in the allowed token list for the offer chain", (offer) => {
11091
- const allowedLoanTokens = assetsByChainId[offer.chainId]?.map((asset) => asset.toLowerCase());
11092
- if (!allowedLoanTokens || allowedLoanTokens.length === 0) return { message: `No allowed loan tokens for chain ${offer.chainId}` };
12017
+ const loanToken = ({ assetsByChainId, chainId }) => single("loan_token", "Validates that offer loan token is in the allowed token list for the request chain", (offer) => {
12018
+ const allowedLoanTokens = assetsByChainId[chainId]?.map((asset) => asset.toLowerCase());
12019
+ if (!allowedLoanTokens || allowedLoanTokens.length === 0) return { message: `No allowed loan tokens for chain ${chainId}` };
11093
12020
  if (!allowedLoanTokens.includes(offer.loanToken.toLowerCase())) return { message: "Loan token is not allowed" };
11094
12021
  });
11095
12022
  /**
@@ -11097,9 +12024,9 @@ const loanToken = ({ assetsByChainId }) => single("loan_token", "Validates that
11097
12024
  * @param collateralAssetsByChainId - Allowed collateral tokens indexed by chain id.
11098
12025
  * @returns The issue that was found. If the offer is valid, this will be undefined.
11099
12026
  */
11100
- const collateralToken = ({ collateralAssetsByChainId }) => single("collateral_token", "Validates that offer collateral tokens are in the allowed token list for the offer chain", (offer) => {
11101
- const allowedCollateralTokens = collateralAssetsByChainId[offer.chainId]?.map((asset) => asset.toLowerCase()) ?? [];
11102
- if (allowedCollateralTokens.length === 0) return { message: `No allowed collateral tokens for chain ${offer.chainId}` };
12027
+ const collateralToken = ({ collateralAssetsByChainId, chainId }) => single("collateral_token", "Validates that offer collateral tokens are in the allowed token list for the request chain", (offer) => {
12028
+ const allowedCollateralTokens = collateralAssetsByChainId[chainId]?.map((asset) => asset.toLowerCase()) ?? [];
12029
+ if (allowedCollateralTokens.length === 0) return { message: `No allowed collateral tokens for chain ${chainId}` };
11103
12030
  if (offer.collaterals.length === 0) return { message: "At least one collateral token is required" };
11104
12031
  if (offer.collaterals.some((collateral) => !allowedCollateralTokens.includes(collateral.asset.toLowerCase()))) return { message: "Collateral token is not allowed" };
11105
12032
  });
@@ -11108,9 +12035,9 @@ const collateralToken = ({ collateralAssetsByChainId }) => single("collateral_to
11108
12035
  * @param oraclesByChainId - Allowed oracles indexed by chain id.
11109
12036
  * @returns The issue that was found. If the offer is valid, this will be undefined.
11110
12037
  */
11111
- const oracle = ({ oraclesByChainId }) => single("oracle", "Validates that offer collateral oracles are in the allowed oracle list for the offer chain", (offer) => {
11112
- const allowedOracles = oraclesByChainId[offer.chainId]?.map((oracle) => oracle.toLowerCase());
11113
- if (!allowedOracles || allowedOracles.length === 0) return { message: `No allowed oracles for chain ${offer.chainId}` };
12038
+ const oracle = ({ oraclesByChainId, chainId }) => single("oracle", "Validates that offer collateral oracles are in the allowed oracle list for the request chain", (offer) => {
12039
+ const allowedOracles = oraclesByChainId[chainId]?.map((oracle) => oracle.toLowerCase());
12040
+ if (!allowedOracles || allowedOracles.length === 0) return { message: `No allowed oracles for chain ${chainId}` };
11114
12041
  if (offer.collaterals.some((collateral) => !allowedOracles.includes(collateral.oracle.toLowerCase()))) return { message: "Oracle is not allowed" };
11115
12042
  });
11116
12043
  /**
@@ -11142,11 +12069,12 @@ const amountMutualExclusivity = () => single("amount_mutual_exclusivity", "Valid
11142
12069
 
11143
12070
  //#endregion
11144
12071
  //#region src/gatekeeper/morphoRules.ts
11145
- const morphoRules = (chains$3) => {
12072
+ const morphoRules = (parameters) => {
12073
+ const { chains, chainId } = parameters;
11146
12074
  const assetsByChainId = {};
11147
12075
  const collateralAssetsByChainId = {};
11148
12076
  const oraclesByChainId = {};
11149
- for (const chain of chains$3) {
12077
+ for (const chain of chains) {
11150
12078
  assetsByChainId[chain.id] = assets[chain.id.toString()] ?? [];
11151
12079
  collateralAssetsByChainId[chain.id] = collateralAssets[chain.id.toString()] ?? [];
11152
12080
  oraclesByChainId[chain.id] = oracles[chain.id.toString()] ?? [];
@@ -11154,15 +12082,23 @@ const morphoRules = (chains$3) => {
11154
12082
  return [
11155
12083
  sameMaker(),
11156
12084
  amountMutualExclusivity(),
11157
- chains({ chains: chains$3 }),
11158
12085
  maturity({ maturities: [MaturityType.EndOfWeek, MaturityType.EndOfNextWeek] }),
11159
12086
  callback({
11160
12087
  callbacks: [Type$1.BuyWithEmptyCallback, Type$1.SellWithEmptyCallback],
11161
12088
  allowedAddresses: []
11162
12089
  }),
11163
- loanToken({ assetsByChainId }),
11164
- collateralToken({ collateralAssetsByChainId }),
11165
- oracle({ oraclesByChainId })
12090
+ loanToken({
12091
+ assetsByChainId,
12092
+ chainId
12093
+ }),
12094
+ collateralToken({
12095
+ collateralAssetsByChainId,
12096
+ chainId
12097
+ }),
12098
+ oracle({
12099
+ oraclesByChainId,
12100
+ chainId
12101
+ })
11166
12102
  ];
11167
12103
  };
11168
12104
 
@@ -11192,9 +12128,7 @@ function from(parameters) {
11192
12128
  async function add(config, offers) {
11193
12129
  if (!config.client.account) throw new WalletAccountNotSetError();
11194
12130
  const tree = from$8(offers.map((o) => from$14(o)));
11195
- const chainId = await getChainId(config.client);
11196
- for (const offer of tree.offers) if (chainId !== offer.chainId) throw new ChainIdMismatchError(offer.chainId, chainId);
11197
- const signatureDomain$1 = resolveSignatureDomain(config, chainId);
12131
+ const signatureDomain$1 = resolveSignatureDomain(config, await getChainId(config.client));
11198
12132
  const signature = await config.client.signTypedData({
11199
12133
  account: config.client.account,
11200
12134
  domain: signatureDomain(signatureDomain$1),
@@ -11299,12 +12233,6 @@ var WalletAccountNotSetError = class extends BaseError {
11299
12233
  var ViemClientError = class extends BaseError {
11300
12234
  name = "Mempool.ViemClientError";
11301
12235
  };
11302
- var ChainIdMismatchError = class extends BaseError {
11303
- name = "Mempool.ChainIdMismatchError";
11304
- constructor(expected, actual) {
11305
- super(`Chain ID mismatch. Offer chain ID is ${expected}, network chain ID is ${actual}.`);
11306
- }
11307
- };
11308
12236
  const resolveSignatureDomain = (config, chainId) => {
11309
12237
  const chain = config.client.chain;
11310
12238
  const verifyingContract = config.morphoAddress ?? chain?.custom?.morpho?.address ?? getChain(chainId)?.custom.morpho.address;
@@ -11333,7 +12261,6 @@ function connect(parameters) {
11333
12261
  //#endregion
11334
12262
  //#region src/mempool/index.ts
11335
12263
  var mempool_exports = /* @__PURE__ */ __exportAll({
11336
- ChainIdMismatchError: () => ChainIdMismatchError,
11337
12264
  MissingMorphoAddressError: () => MissingMorphoAddressError,
11338
12265
  ViemClientError: () => ViemClientError,
11339
12266
  WalletAccountNotSetError: () => WalletAccountNotSetError,
@@ -11451,6 +12378,12 @@ Object.defineProperty(exports, 'HealthController', {
11451
12378
  return HealthController;
11452
12379
  }
11453
12380
  });
12381
+ Object.defineProperty(exports, 'Id', {
12382
+ enumerable: true,
12383
+ get: function () {
12384
+ return Id_exports;
12385
+ }
12386
+ });
11454
12387
  Object.defineProperty(exports, 'Indexer', {
11455
12388
  enumerable: true,
11456
12389
  get: function () {