@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.
@@ -1,7 +1,7 @@
1
1
  import { t as __exportAll } from "./chunk-Bo1DHCg-.mjs";
2
2
  import { getBlock, getBlockNumber, getLogs, multicall } from "viem/actions";
3
3
  import { AsyncLocalStorage } from "node:async_hooks";
4
- import { bytesToHex, decodeAbiParameters, encodeAbiParameters, erc20Abi, getAddress, hashTypedData, hexToBytes, isAddress, isHex, keccak256, maxUint256, numberToHex, pad, parseAbi, parseEventLogs, publicActions, recoverAddress, stringify, zeroAddress } from "viem";
4
+ import { bytesToHex, concatHex, decodeAbiParameters, encodeAbiParameters, erc20Abi, getAddress, hashTypedData, hexToBytes, isAddress, isHex, keccak256, maxUint256, numberToHex, pad, parseAbi, parseEventLogs, publicActions, recoverAddress, stringify, zeroAddress } from "viem";
5
5
  import { SpanStatusCode, trace } from "@opentelemetry/api";
6
6
  import "@opentelemetry/exporter-trace-otlp-proto";
7
7
  import "@opentelemetry/id-generator-aws-xray";
@@ -12,7 +12,7 @@ import "@opentelemetry/propagator-aws-xray";
12
12
  import "@opentelemetry/resources";
13
13
  import "@opentelemetry/sdk-trace-node";
14
14
  import "@opentelemetry/semantic-conventions";
15
- import { and, asc, desc, eq, gt, gte, inArray, lte, ne, sql } from "drizzle-orm";
15
+ import { and, asc, desc, eq, gt, gte, inArray, lte, ne, or, sql } from "drizzle-orm";
16
16
  import { anvil, base, mainnet } from "viem/chains";
17
17
  import * as z$1 from "zod";
18
18
  import { StandardMerkleTree } from "@openzeppelin/merkle-tree";
@@ -802,7 +802,7 @@ const reconcile = async (parameters) => {
802
802
  //#region src/indexer/collectors/Collector.ts
803
803
  const names = [
804
804
  "offers",
805
- "consumed_events",
805
+ "morpho_v2",
806
806
  "positions",
807
807
  "prices"
808
808
  ];
@@ -1179,7 +1179,7 @@ var Chain_exports = /* @__PURE__ */ __exportAll({
1179
1179
  MissingBlockNumberError: () => MissingBlockNumberError,
1180
1180
  chainIds: () => chainIds,
1181
1181
  chainNames: () => chainNames,
1182
- chains: () => chains$2,
1182
+ chains: () => chains$1,
1183
1183
  getChain: () => getChain,
1184
1184
  getWhitelistedChains: () => getWhitelistedChains,
1185
1185
  streamLogs: () => streamLogs
@@ -1196,17 +1196,17 @@ const chainNameLookup = new Map(Object.entries(ChainId).map(([key, value]) => [v
1196
1196
  function getChain(chainId) {
1197
1197
  const chainName = chainNameLookup.get(chainId);
1198
1198
  if (!chainName) return void 0;
1199
- return chains$2[chainName];
1199
+ return chains$1[chainName];
1200
1200
  }
1201
1201
  const getWhitelistedChains = () => {
1202
1202
  return [
1203
- chains$2.ethereum,
1204
- chains$2.base,
1205
- chains$2["ethereum-virtual-testnet"],
1206
- chains$2.anvil
1203
+ chains$1.ethereum,
1204
+ chains$1.base,
1205
+ chains$1["ethereum-virtual-testnet"],
1206
+ chains$1.anvil
1207
1207
  ];
1208
1208
  };
1209
- const chains$2 = {
1209
+ const chains$1 = {
1210
1210
  ethereum: {
1211
1211
  ...mainnet,
1212
1212
  id: ChainId.ETHEREUM,
@@ -1243,16 +1243,16 @@ const chains$2 = {
1243
1243
  name: "base",
1244
1244
  custom: {
1245
1245
  morpho: {
1246
- address: "0x0000000000000000000000000000000000000000",
1247
- blockCreated: 0
1246
+ address: "0x3F067BC9D8898F6ec02D6480c3fF1026E512BcBF",
1247
+ blockCreated: 41799989
1248
1248
  },
1249
1249
  morphoBlue: {
1250
1250
  address: "0xBBBBBbbBBb9cC5e90e3b3Af64bdAF62C37EEFFCb",
1251
1251
  blockCreated: 0
1252
1252
  },
1253
1253
  mempool: {
1254
- address: "0x0000000000000000000000000000000000000000",
1255
- blockCreated: 35449942
1254
+ address: "0xd25c7512EA5035bef4F18c708C0862E1B6151765",
1255
+ blockCreated: 41800226
1256
1256
  },
1257
1257
  vaults: { factories: {
1258
1258
  v1_0: {
@@ -1576,9 +1576,24 @@ const LLTVSchema = z$1.bigint({ coerce: true }).refine((lltv) => {
1576
1576
  var Collateral_exports = /* @__PURE__ */ __exportAll({
1577
1577
  CollateralSchema: () => CollateralSchema,
1578
1578
  CollateralsSchema: () => CollateralsSchema,
1579
+ abi: () => abi$1,
1579
1580
  from: () => from$17,
1580
1581
  random: () => random$3
1581
1582
  });
1583
+ const abi$1 = [
1584
+ {
1585
+ type: "address",
1586
+ name: "token"
1587
+ },
1588
+ {
1589
+ type: "uint256",
1590
+ name: "lltv"
1591
+ },
1592
+ {
1593
+ type: "address",
1594
+ name: "oracle"
1595
+ }
1596
+ ];
1582
1597
  const CollateralSchema = z$1.object({
1583
1598
  asset: z$1.string().transform(transformAddress),
1584
1599
  oracle: z$1.string().transform(transformAddress),
@@ -1683,80 +1698,6 @@ var DenominatorIsZeroError = class extends BaseError {
1683
1698
  }
1684
1699
  };
1685
1700
 
1686
- //#endregion
1687
- //#region src/core/Liquidity.ts
1688
- var Liquidity_exports = /* @__PURE__ */ __exportAll({
1689
- calculateMaxDebt: () => calculateMaxDebt,
1690
- generateAllowancePoolId: () => generateAllowancePoolId,
1691
- generateBalancePoolId: () => generateBalancePoolId,
1692
- generateDebtPoolId: () => generateDebtPoolId,
1693
- generateMarketLiquidityPoolId: () => generateMarketLiquidityPoolId,
1694
- generateObligationCollateralPoolId: () => generateObligationCollateralPoolId,
1695
- generateUserVaultPositionPoolId: () => generateUserVaultPositionPoolId,
1696
- generateVaultPositionPoolId: () => generateVaultPositionPoolId
1697
- });
1698
- /**
1699
- * Calculate maximum debt capacity from collateral amount.
1700
- * @param amount - Collateral amount
1701
- * @param oraclePrice - Oracle price (scaled to 36 decimals)
1702
- * @param lltv - Loan-to-value ratio (scaled to 18 decimals)
1703
- * @returns Maximum debt capacity
1704
- */
1705
- function calculateMaxDebt(amount, oraclePrice, lltv) {
1706
- return amount * oraclePrice / 10n ** 36n * lltv / 10n ** 18n;
1707
- }
1708
- /**
1709
- * Generate pool ID for balance pools.
1710
- */
1711
- function generateBalancePoolId(parameters) {
1712
- const { user, chainId, token } = parameters;
1713
- return `${user}-${chainId.toString()}-${token}-balance`.toLowerCase();
1714
- }
1715
- /**
1716
- * Generate pool ID for allowance pools.
1717
- */
1718
- function generateAllowancePoolId(parameters) {
1719
- const { user, chainId, token } = parameters;
1720
- return `${user}-${chainId.toString()}-${token}-allowance`.toLowerCase();
1721
- }
1722
- /**
1723
- * Generate pool ID for obligation collateral pools.
1724
- * Obligation collateral pools represent collateral already deposited in the obligation.
1725
- * These pools are shared across all offers with the same obligation.
1726
- */
1727
- function generateObligationCollateralPoolId(parameters) {
1728
- const { user, chainId, obligationId, token } = parameters;
1729
- return `${user}-${chainId.toString()}-${obligationId}-${token}-obligation-collateral`.toLowerCase();
1730
- }
1731
- /**
1732
- * Generate pool ID for debt pools.
1733
- */
1734
- function generateDebtPoolId(parameters) {
1735
- const { user, chainId, obligationId } = parameters;
1736
- return `${user}-${chainId.toString()}-${obligationId}-debt`.toLowerCase();
1737
- }
1738
- /**
1739
- * Generate pool ID for user position in a vault.
1740
- */
1741
- function generateUserVaultPositionPoolId(parameters) {
1742
- const { user, chainId, vault } = parameters;
1743
- return `${user}-${chainId.toString()}-${vault}-user-vault-position`.toLowerCase();
1744
- }
1745
- /**
1746
- * Generate pool ID for vault position in a market.
1747
- */
1748
- function generateVaultPositionPoolId(parameters) {
1749
- const { vault, chainId, marketId } = parameters;
1750
- return `${vault}-${chainId.toString()}-${marketId}-vault-position`.toLowerCase();
1751
- }
1752
- /**
1753
- * Generate pool ID for market total liquidity.
1754
- */
1755
- function generateMarketLiquidityPoolId(parameters) {
1756
- const { chainId, marketId } = parameters;
1757
- return `${chainId.toString()}-${marketId}-market-liquidity`.toLowerCase();
1758
- }
1759
-
1760
1701
  //#endregion
1761
1702
  //#region src/core/Maturity.ts
1762
1703
  var Maturity_exports = /* @__PURE__ */ __exportAll({
@@ -1889,18 +1830,38 @@ var Obligation_exports = /* @__PURE__ */ __exportAll({
1889
1830
  CollateralsAreNotSortedError: () => CollateralsAreNotSortedError,
1890
1831
  InvalidObligationError: () => InvalidObligationError,
1891
1832
  ObligationSchema: () => ObligationSchema,
1833
+ abi: () => abi,
1892
1834
  from: () => from$15,
1893
1835
  fromOffer: () => fromOffer$1,
1894
1836
  fromSnakeCase: () => fromSnakeCase$2,
1895
- id: () => id,
1896
- random: () => random$2
1837
+ key: () => key,
1838
+ random: () => random$2,
1839
+ tupleAbi: () => tupleAbi
1897
1840
  });
1898
1841
  const ObligationSchema = z$1.object({
1899
- chainId: z$1.number().min(0).max(Number.MAX_SAFE_INTEGER),
1900
1842
  loanToken: z$1.string().transform(transformAddress),
1901
1843
  collaterals: CollateralsSchema,
1902
1844
  maturity: MaturitySchema
1903
1845
  });
1846
+ const abi = [
1847
+ {
1848
+ type: "address",
1849
+ name: "loanToken"
1850
+ },
1851
+ {
1852
+ type: "tuple[]",
1853
+ name: "collaterals",
1854
+ components: abi$1
1855
+ },
1856
+ {
1857
+ type: "uint256",
1858
+ name: "maturity"
1859
+ }
1860
+ ];
1861
+ const tupleAbi = [{
1862
+ type: "tuple",
1863
+ components: abi
1864
+ }];
1904
1865
  /**
1905
1866
  * Creates an obligation from the given parameters.
1906
1867
  * @constructor
@@ -1911,7 +1872,6 @@ const ObligationSchema = z$1.object({
1911
1872
  * @example
1912
1873
  * ```ts
1913
1874
  * const obligation = Obligation.from({
1914
- * chainId: 1,
1915
1875
  * loanToken: privateKeyToAccount(generatePrivateKey()).address,
1916
1876
  * collaterals: [
1917
1877
  * Collateral.from({
@@ -1931,7 +1891,6 @@ function from$15(parameters) {
1931
1891
  maturity: from$16(parameters.maturity)
1932
1892
  });
1933
1893
  return {
1934
- chainId: parsedObligation.chainId,
1935
1894
  loanToken: parsedObligation.loanToken.toLowerCase(),
1936
1895
  collaterals: parsedObligation.collaterals.sort((a, b) => a.asset.localeCompare(b.asset)),
1937
1896
  maturity: parsedObligation.maturity
@@ -1950,49 +1909,27 @@ function fromSnakeCase$2(input) {
1950
1909
  return from$15(fromSnakeCase$3(input));
1951
1910
  }
1952
1911
  /**
1953
- * Calculates the obligation id based on the smart contract's Obligation struct.
1954
- * The id is computed as keccak256(abi.encode(chainId, loanToken, collaterals, maturity)).
1912
+ * Calculates a canonical key for an obligation payload.
1913
+ * The key is computed as keccak256(abi.encode(loanToken, collaterals, maturity)).
1955
1914
  * @throws If the collaterals are not sorted alphabetically by address. {@link CollateralsAreNotSortedError}
1956
- * @param parameters - {@link id.Parameters}
1957
- * @returns The obligation id as a 32-byte hex string. {@link id.ReturnType}
1915
+ * @param parameters - {@link key.Parameters}
1916
+ * @returns The obligation key as a 32-byte hex string. {@link key.ReturnType}
1958
1917
  *
1959
1918
  * @example
1960
1919
  * ```ts
1961
1920
  * const obligation = Obligation.random();
1962
- * const id = Obligation.id(obligation);
1963
- * console.log(id); // 0x1234567890123456789012345678901234567890123456789012345678901234
1921
+ * const key = Obligation.key(obligation);
1922
+ * console.log(key); // 0x1234567890123456789012345678901234567890123456789012345678901234
1964
1923
  * ```
1965
1924
  */
1966
- function id(parameters) {
1925
+ function key(parameters) {
1967
1926
  let lastAsset = "";
1968
1927
  for (const collateral of parameters.collaterals) {
1969
1928
  const newAsset = collateral.asset.toLowerCase();
1970
1929
  if (newAsset.localeCompare(lastAsset) < 0) throw new CollateralsAreNotSortedError();
1971
1930
  lastAsset = newAsset;
1972
1931
  }
1973
- return keccak256(encodeAbiParameters([
1974
- { type: "uint256" },
1975
- { type: "address" },
1976
- {
1977
- type: "tuple[]",
1978
- components: [
1979
- {
1980
- type: "address",
1981
- name: "token"
1982
- },
1983
- {
1984
- type: "uint256",
1985
- name: "lltv"
1986
- },
1987
- {
1988
- type: "address",
1989
- name: "oracle"
1990
- }
1991
- ]
1992
- },
1993
- { type: "uint256" }
1994
- ], [
1995
- BigInt(parameters.chainId),
1932
+ return keccak256(encodeAbiParameters(abi, [
1996
1933
  parameters.loanToken.toLowerCase(),
1997
1934
  parameters.collaterals.map((c) => ({
1998
1935
  token: c.asset.toLowerCase(),
@@ -2013,7 +1950,6 @@ function id(parameters) {
2013
1950
  */
2014
1951
  function random$2() {
2015
1952
  return from$15({
2016
- chainId: 1,
2017
1953
  loanToken: address(),
2018
1954
  collaterals: [random$3()],
2019
1955
  maturity: from$16("end_of_next_quarter")
@@ -2028,7 +1964,6 @@ function random$2() {
2028
1964
  */
2029
1965
  function fromOffer$1(offer) {
2030
1966
  return from$15({
2031
- chainId: offer.chainId,
2032
1967
  loanToken: offer.loanToken,
2033
1968
  collaterals: offer.collaterals,
2034
1969
  maturity: offer.maturity
@@ -2047,6 +1982,121 @@ var CollateralsAreNotSortedError = class extends BaseError {
2047
1982
  }
2048
1983
  };
2049
1984
 
1985
+ //#endregion
1986
+ //#region src/core/Id.ts
1987
+ var Id_exports = /* @__PURE__ */ __exportAll({
1988
+ creationCode: () => creationCode,
1989
+ toId: () => toId
1990
+ });
1991
+ const CREATION_CODE_PREFIX = "0x603f380380603f5f395ff3";
1992
+ /**
1993
+ * Builds the same creation code as `IdLib.creationCode` in Solidity.
1994
+ *
1995
+ * Layout: `prefix (11 bytes) + chainId (32 bytes) + morphoV2 (20 bytes) + abi.encode(obligation)`.
1996
+ *
1997
+ * @param parameters - {@link creationCode.Parameters}
1998
+ * @returns The CREATE2 init code bytes. {@link creationCode.ReturnType}
1999
+ */
2000
+ function creationCode(parameters) {
2001
+ const encodedObligation = encodeAbiParameters(tupleAbi, [{
2002
+ loanToken: parameters.obligation.loanToken.toLowerCase(),
2003
+ collaterals: parameters.obligation.collaterals.map((collateral) => ({
2004
+ token: collateral.asset.toLowerCase(),
2005
+ lltv: collateral.lltv,
2006
+ oracle: collateral.oracle.toLowerCase()
2007
+ })),
2008
+ maturity: BigInt(parameters.obligation.maturity)
2009
+ }]);
2010
+ return concatHex([
2011
+ CREATION_CODE_PREFIX,
2012
+ numberToHex(BigInt(parameters.chainId), { size: 32 }),
2013
+ parameters.morphoV2.toLowerCase(),
2014
+ encodedObligation
2015
+ ]);
2016
+ }
2017
+ /**
2018
+ * Computes the same id as `IdLib.toId` in Solidity.
2019
+ * @param parameters - {@link toId.Parameters}
2020
+ * @returns The obligation id. {@link toId.ReturnType}
2021
+ */
2022
+ function toId(parameters) {
2023
+ return keccak256(creationCode(parameters));
2024
+ }
2025
+
2026
+ //#endregion
2027
+ //#region src/core/Liquidity.ts
2028
+ var Liquidity_exports = /* @__PURE__ */ __exportAll({
2029
+ calculateMaxDebt: () => calculateMaxDebt,
2030
+ generateAllowancePoolId: () => generateAllowancePoolId,
2031
+ generateBalancePoolId: () => generateBalancePoolId,
2032
+ generateDebtPoolId: () => generateDebtPoolId,
2033
+ generateMarketLiquidityPoolId: () => generateMarketLiquidityPoolId,
2034
+ generateObligationCollateralPoolId: () => generateObligationCollateralPoolId,
2035
+ generateUserVaultPositionPoolId: () => generateUserVaultPositionPoolId,
2036
+ generateVaultPositionPoolId: () => generateVaultPositionPoolId
2037
+ });
2038
+ /**
2039
+ * Calculate maximum debt capacity from collateral amount.
2040
+ * @param amount - Collateral amount
2041
+ * @param oraclePrice - Oracle price (scaled to 36 decimals)
2042
+ * @param lltv - Loan-to-value ratio (scaled to 18 decimals)
2043
+ * @returns Maximum debt capacity
2044
+ */
2045
+ function calculateMaxDebt(amount, oraclePrice, lltv) {
2046
+ return amount * oraclePrice / 10n ** 36n * lltv / 10n ** 18n;
2047
+ }
2048
+ /**
2049
+ * Generate pool ID for balance pools.
2050
+ */
2051
+ function generateBalancePoolId(parameters) {
2052
+ const { user, chainId, token } = parameters;
2053
+ return `${user}-${chainId.toString()}-${token}-balance`.toLowerCase();
2054
+ }
2055
+ /**
2056
+ * Generate pool ID for allowance pools.
2057
+ */
2058
+ function generateAllowancePoolId(parameters) {
2059
+ const { user, chainId, token } = parameters;
2060
+ return `${user}-${chainId.toString()}-${token}-allowance`.toLowerCase();
2061
+ }
2062
+ /**
2063
+ * Generate pool ID for obligation collateral pools.
2064
+ * Obligation collateral pools represent collateral already deposited in the obligation.
2065
+ * These pools are shared across all offers with the same obligation.
2066
+ */
2067
+ function generateObligationCollateralPoolId(parameters) {
2068
+ const { user, chainId, obligationId, token } = parameters;
2069
+ return `${user}-${chainId.toString()}-${obligationId}-${token}-obligation-collateral`.toLowerCase();
2070
+ }
2071
+ /**
2072
+ * Generate pool ID for debt pools.
2073
+ */
2074
+ function generateDebtPoolId(parameters) {
2075
+ const { user, chainId, obligationId } = parameters;
2076
+ return `${user}-${chainId.toString()}-${obligationId}-debt`.toLowerCase();
2077
+ }
2078
+ /**
2079
+ * Generate pool ID for user position in a vault.
2080
+ */
2081
+ function generateUserVaultPositionPoolId(parameters) {
2082
+ const { user, chainId, vault } = parameters;
2083
+ return `${user}-${chainId.toString()}-${vault}-user-vault-position`.toLowerCase();
2084
+ }
2085
+ /**
2086
+ * Generate pool ID for vault position in a market.
2087
+ */
2088
+ function generateVaultPositionPoolId(parameters) {
2089
+ const { vault, chainId, marketId } = parameters;
2090
+ return `${vault}-${chainId.toString()}-${marketId}-vault-position`.toLowerCase();
2091
+ }
2092
+ /**
2093
+ * Generate pool ID for market total liquidity.
2094
+ */
2095
+ function generateMarketLiquidityPoolId(parameters) {
2096
+ const { chainId, marketId } = parameters;
2097
+ return `${chainId.toString()}-${marketId}-market-liquidity`.toLowerCase();
2098
+ }
2099
+
2050
2100
  //#endregion
2051
2101
  //#region src/core/Offer.ts
2052
2102
  var Offer_exports = /* @__PURE__ */ __exportAll({
@@ -2055,17 +2105,19 @@ var Offer_exports = /* @__PURE__ */ __exportAll({
2055
2105
  Status: () => Status,
2056
2106
  consumedEvent: () => consumedEvent,
2057
2107
  decode: () => decode$1,
2058
- domain: () => domain,
2059
2108
  encode: () => encode$1,
2060
2109
  from: () => from$14,
2061
2110
  fromSnakeCase: () => fromSnakeCase$1,
2062
2111
  hash: () => hash,
2112
+ liquidateEvent: () => liquidateEvent,
2063
2113
  obligationId: () => obligationId,
2064
2114
  random: () => random$1,
2115
+ repayEvent: () => repayEvent,
2065
2116
  serialize: () => serialize,
2117
+ supplyCollateralEvent: () => supplyCollateralEvent,
2066
2118
  takeEvent: () => takeEvent,
2067
2119
  toSnakeCase: () => toSnakeCase,
2068
- types: () => types
2120
+ withdrawCollateralEvent: () => withdrawCollateralEvent
2069
2121
  });
2070
2122
  /** Internal symbol for caching the computed hash. */
2071
2123
  const HASH_CACHE = Symbol("offer.hash");
@@ -2095,7 +2147,6 @@ const OfferSchema = () => {
2095
2147
  z$1.bigint()
2096
2148
  ]).optional().default("0x0000000000000000000000000000000000000000000000000000000000000000").transform(transformBytes32),
2097
2149
  buy: z$1.boolean(),
2098
- chainId: z$1.number().min(0).max(Number.MAX_SAFE_INTEGER),
2099
2150
  loanToken: z$1.string().transform(transformAddress),
2100
2151
  collaterals: CollateralsSchema,
2101
2152
  callback: z$1.object({
@@ -2164,7 +2215,6 @@ const serialize = (offer) => ({
2164
2215
  group: offer.group,
2165
2216
  session: offer.session,
2166
2217
  buy: offer.buy,
2167
- chainId: offer.chainId,
2168
2218
  loanToken: offer.loanToken,
2169
2219
  collaterals: offer.collaterals.map((c) => ({
2170
2220
  asset: c.asset,
@@ -2185,7 +2235,6 @@ const serialize = (offer) => ({
2185
2235
  * @returns {Offer} A randomly generated Offer object.
2186
2236
  */
2187
2237
  function random$1(config) {
2188
- const chain = config?.chains ? config.chains[int(config.chains.length)] : chains$2.ethereum;
2189
2238
  const loanToken = config?.loanTokens ? config.loanTokens[int(config.loanTokens.length)] : address();
2190
2239
  const collateralCandidates = config?.collateralTokens ? config.collateralTokens.filter((a) => a !== loanToken) : [address()];
2191
2240
  collateralCandidates[int(collateralCandidates.length)];
@@ -2231,7 +2280,6 @@ function random$1(config) {
2231
2280
  group: config?.group ?? hex(32),
2232
2281
  session: config?.session ?? hex(32),
2233
2282
  buy,
2234
- chainId: chain.id,
2235
2283
  loanToken,
2236
2284
  collaterals: config?.collaterals ?? Array.from({ length: int(3) + 1 }, () => ({
2237
2285
  ...random$3(),
@@ -2251,150 +2299,36 @@ const weightedChoice = (pairs) => {
2251
2299
  return pairs[0][0];
2252
2300
  };
2253
2301
  /**
2254
- * Creates an EIP-712 domain object.
2255
- * @param chainId - The chain ID.
2256
- * @returns The EIP-712 domain object.
2257
- */
2258
- const domain = (chainId) => ({
2259
- chainId: BigInt(chainId),
2260
- verifyingContract: zeroAddress
2261
- });
2262
- /**
2263
- * The EIP-712 types for the offer.
2264
- * @warning The ordering of the types should NEVER be changed. The offer hash is computed based on the order of the types.
2265
- * @returns The EIP-712 types.
2302
+ * Computes the canonical chain-agnostic offer hash.
2303
+ * The hash is `keccak256(abi.encode(offer))` using {@link encode}.
2304
+ *
2305
+ * @param offer - Offer payload to hash.
2306
+ * @returns 32-byte offer hash.
2266
2307
  */
2267
- const types = {
2268
- EIP712Domain: [{
2269
- name: "chainId",
2270
- type: "uint256"
2271
- }, {
2272
- name: "verifyingContract",
2273
- type: "address"
2274
- }],
2275
- Offer: [
2276
- {
2277
- name: "maker",
2278
- type: "address"
2279
- },
2280
- {
2281
- name: "assets",
2282
- type: "uint256"
2283
- },
2284
- {
2285
- name: "obligationUnits",
2286
- type: "uint256"
2287
- },
2288
- {
2289
- name: "obligationShares",
2290
- type: "uint256"
2291
- },
2292
- {
2293
- name: "tick",
2294
- type: "uint256"
2295
- },
2296
- {
2297
- name: "maturity",
2298
- type: "uint256"
2299
- },
2300
- {
2301
- name: "expiry",
2302
- type: "uint256"
2303
- },
2304
- {
2305
- name: "group",
2306
- type: "bytes32"
2307
- },
2308
- {
2309
- name: "session",
2310
- type: "bytes32"
2311
- },
2312
- {
2313
- name: "buy",
2314
- type: "bool"
2315
- },
2316
- {
2317
- name: "loanToken",
2318
- type: "address"
2319
- },
2320
- {
2321
- name: "collaterals",
2322
- type: "Collateral[]"
2323
- },
2324
- {
2325
- name: "callback",
2326
- type: "Callback"
2327
- },
2328
- {
2329
- name: "receiverIfMakerIsSeller",
2330
- type: "address"
2331
- }
2332
- ],
2333
- Collateral: [
2334
- {
2335
- name: "asset",
2336
- type: "address"
2337
- },
2338
- {
2339
- name: "oracle",
2340
- type: "address"
2341
- },
2342
- {
2343
- name: "lltv",
2344
- type: "uint256"
2345
- }
2346
- ],
2347
- Callback: [{
2348
- name: "address",
2349
- type: "address"
2350
- }, {
2351
- name: "data",
2352
- type: "bytes"
2353
- }]
2354
- };
2355
2308
  function hash(offer) {
2356
2309
  const cached = offer[HASH_CACHE];
2357
2310
  if (cached) return cached;
2358
- const computed = hashTypedData({
2359
- domain: domain(offer.chainId),
2360
- message: {
2361
- maker: offer.maker.toLowerCase(),
2362
- assets: offer.assets,
2363
- obligationUnits: offer.obligationUnits,
2364
- obligationShares: offer.obligationShares,
2365
- tick: BigInt(offer.tick),
2366
- maturity: BigInt(offer.maturity),
2367
- expiry: BigInt(offer.expiry),
2368
- group: offer.group,
2369
- session: offer.session,
2370
- buy: offer.buy,
2371
- loanToken: offer.loanToken.toLowerCase(),
2372
- collaterals: offer.collaterals,
2373
- callback: {
2374
- address: offer.callback.address.toLowerCase(),
2375
- data: offer.callback.data
2376
- },
2377
- receiverIfMakerIsSeller: offer.receiverIfMakerIsSeller.toLowerCase()
2378
- },
2379
- primaryType: "Offer",
2380
- types
2381
- });
2311
+ const computed = keccak256(encode$1(offer));
2382
2312
  offer[HASH_CACHE] = computed;
2383
2313
  return computed;
2384
2314
  }
2385
2315
  /**
2386
- * Calculates the obligation id for an offer based on the smart contract's Obligation struct.
2387
- * The id is computed as keccak256(abi.encode(chainId, loanToken, collaterals (sorted by token address), maturity)).
2316
+ * Calculates the onchain obligation id for an offer.
2317
+ * The id is computed with {@link Id.toId}.
2388
2318
  * @param offer - The offer to calculate the obligation id for.
2319
+ * @param parameters - The chain context used by the onchain id function.
2389
2320
  * @returns The obligation id as a 32-byte hex string.
2390
2321
  */
2391
- function obligationId(offer) {
2392
- return id(from$15({
2393
- chainId: offer.chainId,
2394
- loanToken: offer.loanToken,
2395
- collaterals: offer.collaterals,
2396
- maturity: offer.maturity
2397
- }));
2322
+ function obligationId(offer, parameters) {
2323
+ return toId({
2324
+ chainId: parameters.chainId,
2325
+ morphoV2: parameters.morphoV2,
2326
+ obligation: from$15({
2327
+ loanToken: offer.loanToken,
2328
+ collaterals: offer.collaterals,
2329
+ maturity: offer.maturity
2330
+ })
2331
+ });
2398
2332
  }
2399
2333
  const OfferAbi = [
2400
2334
  {
@@ -2437,10 +2371,6 @@ const OfferAbi = [
2437
2371
  name: "buy",
2438
2372
  type: "bool"
2439
2373
  },
2440
- {
2441
- name: "chainId",
2442
- type: "uint256"
2443
- },
2444
2374
  {
2445
2375
  name: "loanToken",
2446
2376
  type: "address"
@@ -2495,7 +2425,6 @@ function encode$1(offer) {
2495
2425
  offer.group,
2496
2426
  offer.session,
2497
2427
  offer.buy,
2498
- BigInt(offer.chainId),
2499
2428
  offer.loanToken,
2500
2429
  BigInt(offer.start),
2501
2430
  offer.collaterals,
@@ -2521,10 +2450,9 @@ function decode$1(data) {
2521
2450
  group: decoded[7],
2522
2451
  session: decoded[8],
2523
2452
  buy: decoded[9],
2524
- chainId: Number(decoded[10]),
2525
- loanToken: decoded[11],
2526
- start: Number(decoded[12]),
2527
- collaterals: decoded[13].map((c) => {
2453
+ loanToken: decoded[10],
2454
+ start: Number(decoded[11]),
2455
+ collaterals: decoded[12].map((c) => {
2528
2456
  return from$17({
2529
2457
  asset: c.asset,
2530
2458
  oracle: c.oracle,
@@ -2532,23 +2460,145 @@ function decode$1(data) {
2532
2460
  });
2533
2461
  }),
2534
2462
  callback: {
2535
- address: decoded[14].address,
2536
- data: decoded[14].data
2463
+ address: decoded[13].address,
2464
+ data: decoded[13].data
2465
+ },
2466
+ receiverIfMakerIsSeller: decoded[14]
2467
+ });
2468
+ }
2469
+ /**
2470
+ * ABI for the Take event emitted by the Morpho V2 contract.
2471
+ */
2472
+ const takeEvent = {
2473
+ type: "event",
2474
+ name: "Take",
2475
+ inputs: [
2476
+ {
2477
+ name: "caller",
2478
+ type: "address",
2479
+ indexed: false,
2480
+ internalType: "address"
2481
+ },
2482
+ {
2483
+ name: "id",
2484
+ type: "bytes32",
2485
+ indexed: true,
2486
+ internalType: "bytes32"
2487
+ },
2488
+ {
2489
+ name: "maker",
2490
+ type: "address",
2491
+ indexed: true,
2492
+ internalType: "address"
2493
+ },
2494
+ {
2495
+ name: "taker",
2496
+ type: "address",
2497
+ indexed: true,
2498
+ internalType: "address"
2499
+ },
2500
+ {
2501
+ name: "offerIsBuy",
2502
+ type: "bool",
2503
+ indexed: false,
2504
+ internalType: "bool"
2505
+ },
2506
+ {
2507
+ name: "buyerAssets",
2508
+ type: "uint256",
2509
+ indexed: false,
2510
+ internalType: "uint256"
2511
+ },
2512
+ {
2513
+ name: "sellerAssets",
2514
+ type: "uint256",
2515
+ indexed: false,
2516
+ internalType: "uint256"
2517
+ },
2518
+ {
2519
+ name: "obligationUnits",
2520
+ type: "uint256",
2521
+ indexed: false,
2522
+ internalType: "uint256"
2523
+ },
2524
+ {
2525
+ name: "obligationShares",
2526
+ type: "uint256",
2527
+ indexed: false,
2528
+ internalType: "uint256"
2529
+ },
2530
+ {
2531
+ name: "buyerIsLender",
2532
+ type: "bool",
2533
+ indexed: false,
2534
+ internalType: "bool"
2535
+ },
2536
+ {
2537
+ name: "sellerIsBorrower",
2538
+ type: "bool",
2539
+ indexed: false,
2540
+ internalType: "bool"
2541
+ },
2542
+ {
2543
+ name: "sellerReceiver",
2544
+ type: "address",
2545
+ indexed: false,
2546
+ internalType: "address"
2547
+ },
2548
+ {
2549
+ name: "group",
2550
+ type: "bytes32",
2551
+ indexed: false,
2552
+ internalType: "bytes32"
2553
+ },
2554
+ {
2555
+ name: "consumed",
2556
+ type: "uint256",
2557
+ indexed: false,
2558
+ internalType: "uint256"
2559
+ }
2560
+ ],
2561
+ anonymous: false
2562
+ };
2563
+ /**
2564
+ * ABI for the Consume event emitted by the Obligation contract.
2565
+ */
2566
+ const consumedEvent = {
2567
+ type: "event",
2568
+ name: "Consume",
2569
+ inputs: [
2570
+ {
2571
+ name: "user",
2572
+ type: "address",
2573
+ indexed: true,
2574
+ internalType: "address"
2537
2575
  },
2538
- receiverIfMakerIsSeller: decoded[15]
2539
- });
2540
- }
2576
+ {
2577
+ name: "group",
2578
+ type: "bytes32",
2579
+ indexed: true,
2580
+ internalType: "bytes32"
2581
+ },
2582
+ {
2583
+ name: "amount",
2584
+ type: "uint256",
2585
+ indexed: false,
2586
+ internalType: "uint256"
2587
+ }
2588
+ ],
2589
+ anonymous: false
2590
+ };
2541
2591
  /**
2542
- * ABI for the Take event emitted by the Morpho V2 contract.
2592
+ * ABI for the Repay event emitted by the MorphoV2 contract.
2543
2593
  */
2544
- const takeEvent = {
2594
+ const repayEvent = {
2545
2595
  type: "event",
2546
- name: "Take",
2596
+ name: "Repay",
2547
2597
  inputs: [
2548
2598
  {
2549
2599
  name: "caller",
2550
2600
  type: "address",
2551
- indexed: false,
2601
+ indexed: true,
2552
2602
  internalType: "address"
2553
2603
  },
2554
2604
  {
@@ -2558,104 +2608,165 @@ const takeEvent = {
2558
2608
  internalType: "bytes32"
2559
2609
  },
2560
2610
  {
2561
- name: "maker",
2611
+ name: "obligationUnits",
2612
+ type: "uint256",
2613
+ indexed: false,
2614
+ internalType: "uint256"
2615
+ },
2616
+ {
2617
+ name: "onBehalf",
2562
2618
  type: "address",
2563
2619
  indexed: true,
2564
2620
  internalType: "address"
2565
- },
2621
+ }
2622
+ ],
2623
+ anonymous: false
2624
+ };
2625
+ /**
2626
+ * ABI for the Liquidate event emitted by the MorphoV2 contract.
2627
+ */
2628
+ const liquidateEvent = {
2629
+ type: "event",
2630
+ name: "Liquidate",
2631
+ inputs: [
2566
2632
  {
2567
- name: "taker",
2633
+ name: "caller",
2568
2634
  type: "address",
2569
2635
  indexed: true,
2570
2636
  internalType: "address"
2571
2637
  },
2572
2638
  {
2573
- name: "offerIsBuy",
2574
- type: "bool",
2575
- indexed: false,
2576
- internalType: "bool"
2639
+ name: "id",
2640
+ type: "bytes32",
2641
+ indexed: true,
2642
+ internalType: "bytes32"
2577
2643
  },
2578
2644
  {
2579
- name: "buyerAssets",
2580
- type: "uint256",
2645
+ name: "seizures",
2646
+ type: "tuple[]",
2581
2647
  indexed: false,
2582
- internalType: "uint256"
2648
+ internalType: "struct IMorphoV2.Seizure[]",
2649
+ components: [
2650
+ {
2651
+ name: "collateralIndex",
2652
+ type: "uint256",
2653
+ internalType: "uint256"
2654
+ },
2655
+ {
2656
+ name: "repaid",
2657
+ type: "uint256",
2658
+ internalType: "uint256"
2659
+ },
2660
+ {
2661
+ name: "seized",
2662
+ type: "uint256",
2663
+ internalType: "uint256"
2664
+ }
2665
+ ]
2583
2666
  },
2584
2667
  {
2585
- name: "sellerAssets",
2586
- type: "uint256",
2587
- indexed: false,
2588
- internalType: "uint256"
2668
+ name: "borrower",
2669
+ type: "address",
2670
+ indexed: true,
2671
+ internalType: "address"
2589
2672
  },
2590
2673
  {
2591
- name: "obligationUnits",
2674
+ name: "totalRepaid",
2592
2675
  type: "uint256",
2593
2676
  indexed: false,
2594
2677
  internalType: "uint256"
2595
2678
  },
2596
2679
  {
2597
- name: "obligationShares",
2680
+ name: "badDebt",
2598
2681
  type: "uint256",
2599
2682
  indexed: false,
2600
2683
  internalType: "uint256"
2601
- },
2602
- {
2603
- name: "buyerIsLender",
2604
- type: "bool",
2605
- indexed: false,
2606
- internalType: "bool"
2607
- },
2608
- {
2609
- name: "sellerIsBorrower",
2610
- type: "bool",
2611
- indexed: false,
2612
- internalType: "bool"
2613
- },
2684
+ }
2685
+ ],
2686
+ anonymous: false
2687
+ };
2688
+ /**
2689
+ * ABI for the SupplyCollateral event emitted by the MorphoV2 contract.
2690
+ */
2691
+ const supplyCollateralEvent = {
2692
+ type: "event",
2693
+ name: "SupplyCollateral",
2694
+ inputs: [
2614
2695
  {
2615
- name: "sellerReceiver",
2696
+ name: "caller",
2616
2697
  type: "address",
2617
2698
  indexed: false,
2618
2699
  internalType: "address"
2619
2700
  },
2620
2701
  {
2621
- name: "group",
2702
+ name: "id",
2622
2703
  type: "bytes32",
2623
- indexed: false,
2704
+ indexed: true,
2624
2705
  internalType: "bytes32"
2625
2706
  },
2626
2707
  {
2627
- name: "consumed",
2708
+ name: "collateral",
2709
+ type: "address",
2710
+ indexed: true,
2711
+ internalType: "address"
2712
+ },
2713
+ {
2714
+ name: "assets",
2628
2715
  type: "uint256",
2629
2716
  indexed: false,
2630
2717
  internalType: "uint256"
2718
+ },
2719
+ {
2720
+ name: "onBehalf",
2721
+ type: "address",
2722
+ indexed: true,
2723
+ internalType: "address"
2631
2724
  }
2632
2725
  ],
2633
2726
  anonymous: false
2634
2727
  };
2635
2728
  /**
2636
- * ABI for the Consume event emitted by the Obligation contract.
2729
+ * ABI for the WithdrawCollateral event emitted by the MorphoV2 contract.
2637
2730
  */
2638
- const consumedEvent = {
2731
+ const withdrawCollateralEvent = {
2639
2732
  type: "event",
2640
- name: "Consume",
2733
+ name: "WithdrawCollateral",
2641
2734
  inputs: [
2642
2735
  {
2643
- name: "user",
2736
+ name: "caller",
2644
2737
  type: "address",
2645
- indexed: true,
2738
+ indexed: false,
2646
2739
  internalType: "address"
2647
2740
  },
2648
2741
  {
2649
- name: "group",
2742
+ name: "id",
2650
2743
  type: "bytes32",
2651
2744
  indexed: true,
2652
2745
  internalType: "bytes32"
2653
2746
  },
2654
2747
  {
2655
- name: "amount",
2748
+ name: "collateral",
2749
+ type: "address",
2750
+ indexed: true,
2751
+ internalType: "address"
2752
+ },
2753
+ {
2754
+ name: "assets",
2656
2755
  type: "uint256",
2657
2756
  indexed: false,
2658
2757
  internalType: "uint256"
2758
+ },
2759
+ {
2760
+ name: "onBehalf",
2761
+ type: "address",
2762
+ indexed: true,
2763
+ internalType: "address"
2764
+ },
2765
+ {
2766
+ name: "receiver",
2767
+ type: "address",
2768
+ indexed: false,
2769
+ internalType: "address"
2659
2770
  }
2660
2771
  ],
2661
2772
  anonymous: false
@@ -2731,8 +2842,9 @@ function fromCollateral(parameters) {
2731
2842
  * @returns The created oracles. {@link fromOffer.ReturnType}
2732
2843
  */
2733
2844
  function fromOffer(parameters) {
2734
- const { offer, blockNumber, price = null } = parameters;
2845
+ const { chainId, offer, blockNumber, price = null } = parameters;
2735
2846
  return fromOffers({
2847
+ chainId,
2736
2848
  offers: [offer],
2737
2849
  blockNumber,
2738
2850
  price
@@ -2746,13 +2858,13 @@ function fromOffer(parameters) {
2746
2858
  * @returns The created oracles. {@link fromOffers.ReturnType}
2747
2859
  */
2748
2860
  function fromOffers(parameters) {
2749
- const { offers, blockNumber, price = null } = parameters;
2861
+ const { chainId, offers, blockNumber, price = null } = parameters;
2750
2862
  const rowsByKey = /* @__PURE__ */ new Map();
2751
2863
  for (const offer of offers) for (const collateral of offer.collaterals) {
2752
- const key = `${offer.chainId}-${collateral.oracle}`.toLowerCase();
2864
+ const key = `${chainId}-${collateral.oracle}`.toLowerCase();
2753
2865
  if (rowsByKey.has(key)) continue;
2754
2866
  rowsByKey.set(key, fromCollateral({
2755
- chainId: offer.chainId,
2867
+ chainId,
2756
2868
  collateral,
2757
2869
  blockNumber,
2758
2870
  price
@@ -2777,11 +2889,14 @@ let Conversion;
2777
2889
  //#region src/core/Position.ts
2778
2890
  var Position_exports = /* @__PURE__ */ __exportAll({
2779
2891
  Type: () => Type,
2780
- from: () => from$12
2892
+ from: () => from$12,
2893
+ positionTypeId: () => positionTypeId
2781
2894
  });
2782
2895
  let Type = /* @__PURE__ */ function(Type) {
2783
2896
  Type["ERC20"] = "erc20";
2784
2897
  Type["VAULT_V1"] = "vault_v1";
2898
+ Type["DEBT_OF"] = "debtOf";
2899
+ Type["COLLATERAL_OF"] = "collateralOf";
2785
2900
  return Type;
2786
2901
  }({});
2787
2902
  /**
@@ -2797,10 +2912,16 @@ function from$12(parameters) {
2797
2912
  user: parameters.user.toLowerCase(),
2798
2913
  type: parameters.type,
2799
2914
  balance: parameters.balance,
2800
- ...parameters.asset !== void 0 ? { asset: parameters.asset.toLowerCase() } : {},
2915
+ asset: parameters.asset.toLowerCase(),
2801
2916
  blockNumber: parameters.blockNumber
2802
2917
  };
2803
2918
  }
2919
+ /**
2920
+ * Maps a {@link Type} enum value to its 1-based integer ID used in the database.
2921
+ * @param type - The position type.
2922
+ * @returns The 1-based integer ID.
2923
+ */
2924
+ const positionTypeId = (type) => Object.values(Type).indexOf(type) + 1;
2804
2925
 
2805
2926
  //#endregion
2806
2927
  //#region src/core/Tick.ts
@@ -2939,7 +3060,7 @@ function fromSnakeCase(snake) {
2939
3060
  */
2940
3061
  function random() {
2941
3062
  return from$11({
2942
- obligationId: id(random$2()),
3063
+ obligationId: hex(32),
2943
3064
  ask: { tick: int(TICK_RANGE + 1) },
2944
3065
  bid: { tick: int(TICK_RANGE + 1) }
2945
3066
  });
@@ -3129,6 +3250,8 @@ function from$9(parameters) {
3129
3250
  from: parameters.from.toLowerCase(),
3130
3251
  to: parameters.to.toLowerCase(),
3131
3252
  value: parameters.value,
3253
+ type: parameters.type,
3254
+ asset: parameters.asset.toLowerCase(),
3132
3255
  blockNumber: parameters.blockNumber
3133
3256
  };
3134
3257
  }
@@ -3310,7 +3433,7 @@ const verifySignatureAndRecoverAddress = async (params) => {
3310
3433
  const encode = async (tree, signature, domain) => {
3311
3434
  const errorFactory = (reason) => new EncodeError(reason);
3312
3435
  const normalizedDomain = normalizeSignatureDomain(domain, errorFactory);
3313
- validateTreeForEncoding(tree, normalizedDomain);
3436
+ validateTreeForEncoding(tree);
3314
3437
  await verifySignatureAndRecoverAddress({
3315
3438
  root: tree.root,
3316
3439
  signature,
@@ -3342,14 +3465,10 @@ const encodeUnsigned = (tree) => {
3342
3465
  validateTreeForEncoding(tree);
3343
3466
  return bytesToHex(encodeUnsignedBytes(tree));
3344
3467
  };
3345
- const validateTreeForEncoding = (tree, domain) => {
3468
+ const validateTreeForEncoding = (tree) => {
3346
3469
  if (VERSION$1 > 255) throw new EncodeError(`version overflow: ${VERSION$1} exceeds 255`);
3347
3470
  const computed = from$8(tree.offers);
3348
3471
  if (tree.root !== computed.root) throw new EncodeError(`root mismatch: expected ${computed.root}, got ${tree.root}`);
3349
- if (domain) {
3350
- const mismatched = tree.offers.find((offer) => BigInt(offer.chainId) !== domain.chainId);
3351
- if (mismatched) throw new EncodeError(`chainId mismatch: expected ${domain.chainId}, got ${mismatched.chainId}`);
3352
- }
3353
3472
  };
3354
3473
  const encodeUnsignedBytes = (tree) => {
3355
3474
  const offersPayload = tree.offers.map(serialize);
@@ -3416,8 +3535,6 @@ const decode = async (encoded, domain) => {
3416
3535
  }
3417
3536
  const tree = from$8(rawOffers.map((o) => OfferSchema().parse(o)));
3418
3537
  if (root !== tree.root) throw new DecodeError(`root mismatch: expected ${tree.root}, got ${root}`);
3419
- const chainIdMismatch = tree.offers.find((offer) => BigInt(offer.chainId) !== normalizedDomain.chainId);
3420
- if (chainIdMismatch) throw new DecodeError(`chainId mismatch: expected ${normalizedDomain.chainId}, got ${chainIdMismatch.chainId}`);
3421
3538
  return {
3422
3539
  tree,
3423
3540
  signature,
@@ -3470,7 +3587,7 @@ const BrandTypeId = Symbol.for("mempool/Brand");
3470
3587
 
3471
3588
  //#endregion
3472
3589
  //#region src/database/drizzle/VERSION.ts
3473
- const VERSION = "router_v1.8";
3590
+ const VERSION = "router_v1.11";
3474
3591
 
3475
3592
  //#endregion
3476
3593
  //#region src/database/drizzle/schema.ts
@@ -3480,13 +3597,15 @@ var schema_exports = /* @__PURE__ */ __exportAll({
3480
3597
  TABLE_NAMES: () => TABLE_NAMES,
3481
3598
  VERSIONED_TABLE_NAMES: () => VERSIONED_TABLE_NAMES,
3482
3599
  callbacks: () => callbacks,
3483
- chains: () => chains$1,
3600
+ chains: () => chains,
3484
3601
  collectors: () => collectors,
3485
3602
  consumedEvents: () => consumedEvents,
3486
3603
  groups: () => groups,
3487
3604
  lots: () => lots,
3605
+ lotsPositions: () => lotsPositions,
3488
3606
  merklePaths: () => merklePaths,
3489
3607
  obligationCollateralsV2: () => obligationCollateralsV2,
3608
+ obligationIdKeys: () => obligationIdKeys,
3490
3609
  obligations: () => obligations,
3491
3610
  offers: () => offers,
3492
3611
  offersCallbacks: () => offersCallbacks,
@@ -3502,6 +3621,7 @@ var schema_exports = /* @__PURE__ */ __exportAll({
3502
3621
  const s = pgSchema(VERSION);
3503
3622
  var EnumTableName = /* @__PURE__ */ function(EnumTableName) {
3504
3623
  EnumTableName["OBLIGATIONS"] = "obligations";
3624
+ EnumTableName["OBLIGATION_ID_KEYS"] = "obligation_id_keys";
3505
3625
  EnumTableName["GROUPS"] = "groups";
3506
3626
  EnumTableName["CONSUMED_EVENTS"] = "consumed_events";
3507
3627
  EnumTableName["OBLIGATION_COLLATERALS_V2"] = "obligation_collaterals_v2";
@@ -3515,6 +3635,7 @@ var EnumTableName = /* @__PURE__ */ function(EnumTableName) {
3515
3635
  EnumTableName["COLLECTORS"] = "collectors";
3516
3636
  EnumTableName["CHAINS"] = "chains";
3517
3637
  EnumTableName["LOTS"] = "lots";
3638
+ EnumTableName["LOTS_POSITIONS"] = "lots_positions";
3518
3639
  EnumTableName["OFFSETS"] = "offsets";
3519
3640
  EnumTableName["TREES"] = "trees";
3520
3641
  EnumTableName["MERKLE_PATHS"] = "merkle_paths";
@@ -3523,11 +3644,16 @@ var EnumTableName = /* @__PURE__ */ function(EnumTableName) {
3523
3644
  const TABLE_NAMES = Object.values(EnumTableName);
3524
3645
  const VERSIONED_TABLE_NAMES = TABLE_NAMES.map((table) => `"${VERSION}"."${table}"`);
3525
3646
  const obligations = s.table(EnumTableName.OBLIGATIONS, {
3526
- obligationId: varchar("obligation_id", { length: 66 }).primaryKey(),
3527
- chainId: bigint("chain_id", { mode: "number" }).$type().notNull(),
3647
+ obligationKey: varchar("obligation_key", { length: 66 }).primaryKey(),
3528
3648
  loanToken: varchar("loan_token", { length: 42 }).notNull(),
3529
3649
  maturity: integer("maturity").notNull()
3530
3650
  });
3651
+ const obligationIdKeys = s.table(EnumTableName.OBLIGATION_ID_KEYS, {
3652
+ obligationId: varchar("obligation_id", { length: 66 }).primaryKey(),
3653
+ obligationKey: varchar("obligation_key", { length: 66 }).notNull().references(() => obligations.obligationKey, { onDelete: "cascade" }),
3654
+ chainId: bigint("chain_id", { mode: "number" }).$type().notNull(),
3655
+ morphoV2: varchar("morpho_v2", { length: 42 }).notNull()
3656
+ }, (table) => [index("obligation_id_keys_obligation_key_idx").on(table.obligationKey), index("obligation_id_keys_chain_id_idx").on(table.chainId)]);
3531
3657
  const groups = s.table(EnumTableName.GROUPS, {
3532
3658
  chainId: bigint("chain_id", { mode: "number" }).$type().notNull(),
3533
3659
  maker: varchar("maker", { length: 42 }).notNull(),
@@ -3575,24 +3701,19 @@ const consumedEvents = s.table(EnumTableName.CONSUMED_EVENTS, {
3575
3701
  index("consumed_events_block_number_idx").on(t.blockNumber)
3576
3702
  ]);
3577
3703
  const obligationCollateralsV2 = s.table(EnumTableName.OBLIGATION_COLLATERALS_V2, {
3578
- obligationId: varchar("obligation_id", { length: 66 }).notNull().references(() => obligations.obligationId, { onDelete: "cascade" }),
3704
+ obligationKey: varchar("obligation_key", { length: 66 }).notNull().references(() => obligations.obligationKey, { onDelete: "cascade" }),
3579
3705
  asset: varchar("asset", { length: 42 }).notNull(),
3580
- oracleChainId: bigint("oracle_chain_id", { mode: "number" }).$type().notNull(),
3581
3706
  oracleAddress: varchar("oracle_address", { length: 42 }).notNull(),
3582
3707
  lltv: bigint("lltv", { mode: "bigint" }).notNull(),
3708
+ collateralIndex: integer("collateral_index").notNull(),
3583
3709
  updatedAt: timestamp("updated_at").defaultNow().notNull()
3584
3710
  }, (table) => [
3585
3711
  primaryKey({
3586
- columns: [table.obligationId, table.asset],
3712
+ columns: [table.obligationKey, table.asset],
3587
3713
  name: "obligation_collaterals_v2_pk"
3588
3714
  }),
3589
- foreignKey({
3590
- columns: [table.oracleChainId, table.oracleAddress],
3591
- foreignColumns: [oracles$1.chainId, oracles$1.address],
3592
- name: "obligation_collaterals_v2_oracles_fk"
3593
- }),
3594
- index("obligation_collaterals_v2_obligation_id_idx").on(table.obligationId),
3595
- index("obligation_collaterals_v2_oracle_fk_idx").on(table.oracleChainId, table.oracleAddress)
3715
+ index("obligation_collaterals_v2_obligation_key_idx").on(table.obligationKey),
3716
+ index("obligation_collaterals_v2_oracle_address_idx").on(table.oracleAddress)
3596
3717
  ]);
3597
3718
  const oracles$1 = s.table(EnumTableName.ORACLES, {
3598
3719
  chainId: bigint("chain_id", { mode: "number" }).$type().notNull(),
@@ -3608,8 +3729,8 @@ const oracles$1 = s.table(EnumTableName.ORACLES, {
3608
3729
  name: "oracles_pk"
3609
3730
  })]);
3610
3731
  const offers = s.table(EnumTableName.OFFERS, {
3611
- hash: varchar("hash", { length: 66 }).primaryKey(),
3612
- obligationId: varchar("obligation_id", { length: 66 }).notNull().references(() => obligations.obligationId, { onDelete: "cascade" }),
3732
+ hash: varchar("hash", { length: 66 }).notNull(),
3733
+ obligationId: varchar("obligation_id", { length: 66 }).notNull().references(() => obligationIdKeys.obligationId, { onDelete: "cascade" }),
3613
3734
  assets: numeric("assets", {
3614
3735
  precision: 78,
3615
3736
  scale: 0
@@ -3637,6 +3758,10 @@ const offers = s.table(EnumTableName.OFFERS, {
3637
3758
  blockNumber: bigint("block_number", { mode: "number" }).notNull(),
3638
3759
  updatedAt: timestamp("updated_at").defaultNow().notNull()
3639
3760
  }, (table) => [
3761
+ primaryKey({
3762
+ columns: [table.hash, table.obligationId],
3763
+ name: "offers_pk"
3764
+ }),
3640
3765
  foreignKey({
3641
3766
  columns: [
3642
3767
  table.groupChainId,
@@ -3655,10 +3780,19 @@ const offers = s.table(EnumTableName.OFFERS, {
3655
3780
  index("offers_obligation_id_side_idx").on(table.obligationId, table.buy)
3656
3781
  ]);
3657
3782
  const offersCallbacks = s.table(EnumTableName.OFFERS_CALLBACKS, {
3658
- offerHash: varchar("offer_hash", { length: 66 }).notNull().references(() => offers.hash, { onDelete: "cascade" }),
3783
+ offerHash: varchar("offer_hash", { length: 66 }).notNull(),
3784
+ obligationId: varchar("obligation_id", { length: 66 }).notNull(),
3659
3785
  callbackId: varchar("callback_id", { length: 66 })
3660
- }, (table) => [primaryKey({
3661
- columns: [table.offerHash, table.callbackId],
3786
+ }, (table) => [foreignKey({
3787
+ columns: [table.offerHash, table.obligationId],
3788
+ foreignColumns: [offers.hash, offers.obligationId],
3789
+ name: "offers_callbacks_offer_fk"
3790
+ }).onDelete("cascade"), primaryKey({
3791
+ columns: [
3792
+ table.offerHash,
3793
+ table.obligationId,
3794
+ table.callbackId
3795
+ ],
3662
3796
  name: "offers_callbacks_pk"
3663
3797
  })]);
3664
3798
  const callbacks = s.table(EnumTableName.CALLBACKS, {
@@ -3666,23 +3800,25 @@ const callbacks = s.table(EnumTableName.CALLBACKS, {
3666
3800
  positionChainId: bigint("position_chain_id", { mode: "number" }).$type().notNull(),
3667
3801
  positionContract: varchar("position_contract", { length: 42 }).notNull(),
3668
3802
  positionUser: varchar("position_user", { length: 42 }).notNull(),
3803
+ positionTypeId: integer("position_type_id").notNull().references(() => positionTypes.id, { onDelete: "no action" }),
3669
3804
  amount: numeric("amount", {
3670
3805
  precision: 78,
3671
3806
  scale: 0
3672
3807
  })
3673
- }, (table) => [foreignKey({
3808
+ });
3809
+ const lotsPositions = s.table(EnumTableName.LOTS_POSITIONS, {
3810
+ chainId: bigint("chain_id", { mode: "number" }).$type().notNull(),
3811
+ contract: varchar("contract", { length: 42 }).notNull(),
3812
+ user: varchar("user", { length: 42 }).notNull(),
3813
+ positionTypeId: integer("position_type_id").notNull().references(() => positionTypes.id, { onDelete: "no action" })
3814
+ }, (table) => [primaryKey({
3674
3815
  columns: [
3675
- table.positionChainId,
3676
- table.positionContract,
3677
- table.positionUser
3678
- ],
3679
- foreignColumns: [
3680
- positions.chainId,
3681
- positions.contract,
3682
- positions.user
3816
+ table.chainId,
3817
+ table.contract,
3818
+ table.user
3683
3819
  ],
3684
- name: "callbacks_positions_fk"
3685
- }).onDelete("cascade")]);
3820
+ name: "lots_positions_pk"
3821
+ })]);
3686
3822
  const lots = s.table(EnumTableName.LOTS, {
3687
3823
  chainId: bigint("chain_id", { mode: "number" }).$type().notNull(),
3688
3824
  user: varchar("user", { length: 42 }).notNull(),
@@ -3715,11 +3851,11 @@ const lots = s.table(EnumTableName.LOTS, {
3715
3851
  table.user
3716
3852
  ],
3717
3853
  foreignColumns: [
3718
- positions.chainId,
3719
- positions.contract,
3720
- positions.user
3854
+ lotsPositions.chainId,
3855
+ lotsPositions.contract,
3856
+ lotsPositions.user
3721
3857
  ],
3722
- name: "lots_positions_fk"
3858
+ name: "lots_lots_positions_fk"
3723
3859
  }).onDelete("cascade"),
3724
3860
  foreignKey({
3725
3861
  columns: [
@@ -3761,11 +3897,11 @@ const offsets = s.table(EnumTableName.OFFSETS, {
3761
3897
  table.user
3762
3898
  ],
3763
3899
  foreignColumns: [
3764
- positions.chainId,
3765
- positions.contract,
3766
- positions.user
3900
+ lotsPositions.chainId,
3901
+ lotsPositions.contract,
3902
+ lotsPositions.user
3767
3903
  ],
3768
- name: "offsets_positions_fk"
3904
+ name: "offsets_lots_positions_fk"
3769
3905
  }).onDelete("cascade")]);
3770
3906
  const PositionTypes = s.enum("position_type", Object.values(Type));
3771
3907
  const positionTypes = s.table("position_types", {
@@ -3774,34 +3910,38 @@ const positionTypes = s.table("position_types", {
3774
3910
  });
3775
3911
  const positions = s.table(EnumTableName.POSITIONS, {
3776
3912
  chainId: bigint("chain_id", { mode: "number" }).$type().notNull(),
3777
- contract: varchar("contract", { length: 42 }).notNull(),
3913
+ contract: varchar("contract", { length: 66 }).notNull(),
3778
3914
  user: varchar("user", { length: 42 }).notNull(),
3779
3915
  positionTypeId: integer("position_type_id").notNull().references(() => positionTypes.id, { onDelete: "no action" }),
3780
3916
  balance: numeric("balance", {
3781
3917
  precision: 78,
3782
3918
  scale: 0
3783
3919
  }),
3784
- asset: varchar("asset", { length: 42 }),
3920
+ asset: varchar("asset", { length: 42 }).notNull(),
3785
3921
  blockNumber: bigint("block_number", { mode: "number" }).notNull(),
3786
3922
  updatedAt: timestamp("updated_at").defaultNow().notNull()
3787
3923
  }, (table) => [primaryKey({
3788
3924
  columns: [
3789
3925
  table.chainId,
3790
3926
  table.contract,
3791
- table.user
3927
+ table.user,
3928
+ table.positionTypeId,
3929
+ table.asset
3792
3930
  ],
3793
3931
  name: "positions_pk"
3794
3932
  })]);
3795
3933
  const transfers = s.table(EnumTableName.TRANSFERS, {
3796
3934
  eventId: varchar("event_id", { length: 128 }).primaryKey(),
3797
3935
  chainId: bigint("chain_id", { mode: "number" }).$type().notNull(),
3798
- contract: varchar("contract", { length: 42 }).notNull(),
3936
+ contract: varchar("contract", { length: 66 }).notNull(),
3799
3937
  from: varchar("from", { length: 42 }).notNull(),
3800
3938
  to: varchar("to", { length: 42 }).notNull(),
3801
3939
  value: numeric("value", {
3802
3940
  precision: 78,
3803
3941
  scale: 0
3804
3942
  }).notNull(),
3943
+ positionTypeId: integer("position_type_id").notNull().references(() => positionTypes.id, { onDelete: "no action" }),
3944
+ asset: varchar("asset", { length: 42 }).notNull(),
3805
3945
  blockNumber: bigint("block_number", { mode: "number" }).notNull(),
3806
3946
  createdAt: timestamp("created_at").defaultNow().notNull()
3807
3947
  }, (table) => [
@@ -3809,12 +3949,16 @@ const transfers = s.table(EnumTableName.TRANSFERS, {
3809
3949
  columns: [
3810
3950
  table.chainId,
3811
3951
  table.contract,
3812
- table.from
3952
+ table.from,
3953
+ table.positionTypeId,
3954
+ table.asset
3813
3955
  ],
3814
3956
  foreignColumns: [
3815
3957
  positions.chainId,
3816
3958
  positions.contract,
3817
- positions.user
3959
+ positions.user,
3960
+ positions.positionTypeId,
3961
+ positions.asset
3818
3962
  ],
3819
3963
  name: "transfers_positions_from_fk"
3820
3964
  }).onDelete("cascade"),
@@ -3822,16 +3966,21 @@ const transfers = s.table(EnumTableName.TRANSFERS, {
3822
3966
  columns: [
3823
3967
  table.chainId,
3824
3968
  table.contract,
3825
- table.to
3969
+ table.to,
3970
+ table.positionTypeId,
3971
+ table.asset
3826
3972
  ],
3827
3973
  foreignColumns: [
3828
3974
  positions.chainId,
3829
3975
  positions.contract,
3830
- positions.user
3976
+ positions.user,
3977
+ positions.positionTypeId,
3978
+ positions.asset
3831
3979
  ],
3832
3980
  name: "transfers_positions_to_fk"
3833
3981
  }).onDelete("cascade"),
3834
- index("transfers_chain_contract_user_idx").on(table.chainId, table.contract, table.from, table.to, table.blockNumber)
3982
+ index("transfers_chain_contract_user_idx").on(table.chainId, table.contract, table.from, table.to, table.blockNumber),
3983
+ index("transfers_chain_type_block_idx").on(table.chainId, table.positionTypeId, table.blockNumber)
3835
3984
  ]);
3836
3985
  const StatusCode = s.enum("status_code", Object.values(Status));
3837
3986
  const status = s.table("status", {
@@ -3839,12 +3988,20 @@ const status = s.table("status", {
3839
3988
  code: StatusCode("code").unique()
3840
3989
  });
3841
3990
  const validations = s.table("validations", {
3842
- offerHash: varchar("offer_hash", { length: 66 }).primaryKey().references(() => offers.hash, { onDelete: "cascade" }),
3991
+ offerHash: varchar("offer_hash", { length: 66 }).notNull(),
3992
+ obligationId: varchar("obligation_id", { length: 66 }).notNull(),
3843
3993
  statusId: integer("status_id").notNull().references(() => status.id, { onDelete: "no action" }),
3844
3994
  updatedAt: timestamp("updated_at").defaultNow().notNull()
3845
- });
3995
+ }, (table) => [primaryKey({
3996
+ columns: [table.offerHash, table.obligationId],
3997
+ name: "validations_pk"
3998
+ }), foreignKey({
3999
+ columns: [table.offerHash, table.obligationId],
4000
+ foreignColumns: [offers.hash, offers.obligationId],
4001
+ name: "validations_offer_fk"
4002
+ }).onDelete("cascade")]);
3846
4003
  const collectors = s.table(EnumTableName.COLLECTORS, {
3847
- chainId: bigint("chain_id", { mode: "number" }).$type().notNull().references(() => chains$1.chainId, { onDelete: "no action" }),
4004
+ chainId: bigint("chain_id", { mode: "number" }).$type().notNull().references(() => chains.chainId, { onDelete: "no action" }),
3848
4005
  name: text("name").$type().notNull(),
3849
4006
  blockNumber: bigint("block_number", { mode: "number" }).notNull(),
3850
4007
  epoch: numeric("epoch", {
@@ -3853,7 +4010,7 @@ const collectors = s.table(EnumTableName.COLLECTORS, {
3853
4010
  }).default("0").notNull(),
3854
4011
  updatedAt: timestamp("updated_at").defaultNow().notNull()
3855
4012
  }, (table) => [uniqueIndex("collectors_chain_name_idx").on(table.chainId, table.name)]);
3856
- const chains$1 = s.table(EnumTableName.CHAINS, {
4013
+ const chains = s.table(EnumTableName.CHAINS, {
3857
4014
  chainId: bigint("chain_id", { mode: "number" }).$type().notNull(),
3858
4015
  blockNumber: bigint("block_number", { mode: "number" }).notNull(),
3859
4016
  epoch: numeric("epoch", {
@@ -3868,18 +4025,352 @@ const trees = s.table(EnumTableName.TREES, {
3868
4025
  createdAt: timestamp("created_at").defaultNow().notNull()
3869
4026
  });
3870
4027
  const merklePaths = s.table(EnumTableName.MERKLE_PATHS, {
3871
- offerHash: varchar("offer_hash", { length: 66 }).primaryKey().references(() => offers.hash, { onDelete: "cascade" }),
4028
+ offerHash: varchar("offer_hash", { length: 66 }).notNull(),
4029
+ obligationId: varchar("obligation_id", { length: 66 }).notNull(),
3872
4030
  treeRoot: varchar("tree_root", { length: 66 }).notNull().references(() => trees.root, { onDelete: "cascade" }),
3873
4031
  proofNodes: text("proof_nodes").notNull(),
3874
4032
  createdAt: timestamp("created_at").defaultNow().notNull()
3875
- }, (table) => [index("merkle_paths_tree_root_idx").on(table.treeRoot)]);
4033
+ }, (table) => [
4034
+ primaryKey({
4035
+ columns: [table.offerHash, table.obligationId],
4036
+ name: "merkle_paths_pk"
4037
+ }),
4038
+ foreignKey({
4039
+ columns: [table.offerHash, table.obligationId],
4040
+ foreignColumns: [offers.hash, offers.obligationId],
4041
+ name: "merkle_paths_offer_fk"
4042
+ }).onDelete("cascade"),
4043
+ index("merkle_paths_tree_root_idx").on(table.treeRoot)
4044
+ ]);
4045
+
4046
+ //#endregion
4047
+ //#region src/indexer/collectors/CollectFunctions/processors/processCollateralSeizures.ts
4048
+ /**
4049
+ * Parse raw MorphoV2 liquidate logs and compute collateral seizures.
4050
+ *
4051
+ * Seizures only expose `(obligationId, collateralIndex)` and must be resolved
4052
+ * to collateral token addresses before creating transfer rows.
4053
+ *
4054
+ * @param parameters - The parsed event logs and chain ID.
4055
+ * @param parameters.logs - Parsed event logs from MorphoV2.
4056
+ * @param parameters.chainId - Chain ID for event attribution.
4057
+ * @returns Seizure events pending collateral address resolution.
4058
+ */
4059
+ function processCollateralSeizures(parameters) {
4060
+ const { logs, chainId } = parameters;
4061
+ const logger = getLogger();
4062
+ const seizureEvents = [];
4063
+ for (const rawLog of logs) {
4064
+ if (rawLog.blockNumber === null || rawLog.logIndex === null || rawLog.transactionHash === null) {
4065
+ logger.debug({
4066
+ chainId,
4067
+ msg: "Skipping collateral log because it is missing required fields"
4068
+ });
4069
+ continue;
4070
+ }
4071
+ if (rawLog.eventName !== liquidateEvent.name) continue;
4072
+ const args = rawLog.args;
4073
+ if (args?.id === void 0 || args?.borrower === void 0 || args?.seizures === void 0) {
4074
+ logger.debug({
4075
+ chainId,
4076
+ msg: "Skipping Liquidate log for collateral because it is missing required args"
4077
+ });
4078
+ continue;
4079
+ }
4080
+ const baseId = `${rawLog.blockNumber.toString()}-${rawLog.logIndex.toString()}-${chainId}-${rawLog.transactionHash}`;
4081
+ for (let seizureIndex = 0; seizureIndex < args.seizures.length; seizureIndex++) {
4082
+ const seizure = args.seizures[seizureIndex];
4083
+ if (seizure.seized === 0n) continue;
4084
+ seizureEvents.push({
4085
+ id: `${baseId}-collateral-liquidate-${seizureIndex}`,
4086
+ chainId,
4087
+ obligationId: args.id,
4088
+ collateralIndex: Number(seizure.collateralIndex),
4089
+ user: args.borrower,
4090
+ amount: seizure.seized,
4091
+ blockNumber: Number(rawLog.blockNumber)
4092
+ });
4093
+ }
4094
+ }
4095
+ return seizureEvents;
4096
+ }
4097
+
4098
+ //#endregion
4099
+ //#region src/indexer/collectors/CollectFunctions/processors/processCollateralTransfers.ts
4100
+ /**
4101
+ * Parse raw MorphoV2 logs and compute collateral transfers.
4102
+ *
4103
+ * A collateral position uses `contract = obligationId` and `asset = collateral token address`.
4104
+ * The 5-col PK `(chainId, contract, user, positionTypeId, asset)` distinguishes per-token positions.
4105
+ *
4106
+ * **SupplyCollateral**: Transfer from zeroAddress to onBehalf (supply)
4107
+ * **WithdrawCollateral**: Transfer from onBehalf to zeroAddress (withdrawal)
4108
+ *
4109
+ * @param parameters - The parsed event logs and chain ID.
4110
+ * @param parameters.logs - Parsed event logs from MorphoV2.
4111
+ * @param parameters.chainId - Chain ID for event attribution.
4112
+ * @returns Collateral transfers from SupplyCollateral/WithdrawCollateral.
4113
+ */
4114
+ function processCollateralTransfers(parameters) {
4115
+ const { logs, chainId } = parameters;
4116
+ const logger = getLogger();
4117
+ const transfers = [];
4118
+ for (const rawLog of logs) {
4119
+ if (rawLog.blockNumber === null || rawLog.logIndex === null || rawLog.transactionHash === null) {
4120
+ logger.debug({
4121
+ chainId,
4122
+ msg: "Skipping collateral log because it is missing required fields"
4123
+ });
4124
+ continue;
4125
+ }
4126
+ const eventName = rawLog.eventName;
4127
+ const baseId = `${rawLog.blockNumber.toString()}-${rawLog.logIndex.toString()}-${chainId}-${rawLog.transactionHash}`;
4128
+ if (eventName === supplyCollateralEvent.name) {
4129
+ const args = rawLog.args;
4130
+ if (args?.id === void 0 || args?.collateral === void 0 || args?.assets === void 0 || args?.onBehalf === void 0) {
4131
+ logger.debug({
4132
+ chainId,
4133
+ msg: "Skipping SupplyCollateral log because it is missing required args"
4134
+ });
4135
+ continue;
4136
+ }
4137
+ if (args.assets === 0n) continue;
4138
+ transfers.push(from$9({
4139
+ id: `${baseId}-collateral-supply`,
4140
+ chainId,
4141
+ contract: args.id,
4142
+ from: zeroAddress,
4143
+ to: args.onBehalf,
4144
+ value: args.assets,
4145
+ type: Type.COLLATERAL_OF,
4146
+ asset: args.collateral,
4147
+ blockNumber: Number(rawLog.blockNumber)
4148
+ }));
4149
+ continue;
4150
+ }
4151
+ if (eventName === withdrawCollateralEvent.name) {
4152
+ const args = rawLog.args;
4153
+ if (args?.id === void 0 || args?.collateral === void 0 || args?.assets === void 0 || args?.onBehalf === void 0) {
4154
+ logger.debug({
4155
+ chainId,
4156
+ msg: "Skipping WithdrawCollateral log because it is missing required args"
4157
+ });
4158
+ continue;
4159
+ }
4160
+ if (args.assets === 0n) continue;
4161
+ transfers.push(from$9({
4162
+ id: `${baseId}-collateral-withdraw`,
4163
+ chainId,
4164
+ contract: args.id,
4165
+ from: args.onBehalf,
4166
+ to: zeroAddress,
4167
+ value: args.assets,
4168
+ type: Type.COLLATERAL_OF,
4169
+ asset: args.collateral,
4170
+ blockNumber: Number(rawLog.blockNumber)
4171
+ }));
4172
+ }
4173
+ }
4174
+ return transfers;
4175
+ }
3876
4176
 
3877
4177
  //#endregion
3878
- //#region src/indexer/collectors/CollectFunctions/collectConsumedEvents.ts
4178
+ //#region src/indexer/collectors/CollectFunctions/processors/processConsumedLogs.ts
3879
4179
  const buildGroupKey = (parameters) => {
3880
4180
  return `${parameters.chainId}-${parameters.maker.toLowerCase()}-${parameters.group.toLowerCase()}`;
3881
4181
  };
3882
- async function* collectConsumedEvents(parameters) {
4182
+ /** Parse raw MorphoV2 logs and produce normalized consumed events.
4183
+ * @param parameters - The parsed event logs and chain ID.
4184
+ * @param parameters.logs - Parsed event logs from MorphoV2 (Consume and Take events).
4185
+ * @param parameters.chainId - Chain ID for event attribution.
4186
+ * @returns Flat array of consumed events.
4187
+ */
4188
+ function processConsumedLogs(parameters) {
4189
+ const { logs, chainId } = parameters;
4190
+ const logger = getLogger();
4191
+ const consumedEvents = [];
4192
+ for (const rawLog of logs) {
4193
+ if (rawLog.blockNumber === null || rawLog.logIndex === null || rawLog.transactionHash === null) {
4194
+ logger.debug({
4195
+ chainId,
4196
+ msg: "Skipping log because it is missing required fields"
4197
+ });
4198
+ continue;
4199
+ }
4200
+ const eventName = rawLog.eventName;
4201
+ if (eventName === consumedEvent.name) {
4202
+ const consumeArgs = rawLog.args;
4203
+ if (consumeArgs?.user === void 0 || consumeArgs?.group === void 0 || consumeArgs?.amount === void 0) {
4204
+ logger.debug({
4205
+ chainId,
4206
+ msg: "Skipping Consume log because it is missing required args"
4207
+ });
4208
+ continue;
4209
+ }
4210
+ consumedEvents.push({
4211
+ kind: "consume",
4212
+ id: `${rawLog.blockNumber.toString()}-${rawLog.logIndex.toString()}-${chainId}-${rawLog.transactionHash}`,
4213
+ chainId,
4214
+ maker: consumeArgs.user,
4215
+ group: consumeArgs.group,
4216
+ amount: consumeArgs.amount,
4217
+ blockNumber: Number(rawLog.blockNumber)
4218
+ });
4219
+ continue;
4220
+ }
4221
+ if (eventName === takeEvent.name) {
4222
+ const takeArgs = rawLog.args;
4223
+ if (takeArgs?.maker === void 0 || takeArgs?.group === void 0 || takeArgs?.consumed === void 0) {
4224
+ logger.debug({
4225
+ chainId,
4226
+ msg: "Skipping Take log because it is missing required args for consumed"
4227
+ });
4228
+ continue;
4229
+ }
4230
+ consumedEvents.push({
4231
+ kind: "take",
4232
+ id: `${rawLog.blockNumber.toString()}-${rawLog.logIndex.toString()}-${chainId}-${rawLog.transactionHash}`,
4233
+ chainId,
4234
+ maker: takeArgs.maker,
4235
+ group: takeArgs.group,
4236
+ consumed: takeArgs.consumed,
4237
+ blockNumber: Number(rawLog.blockNumber)
4238
+ });
4239
+ }
4240
+ }
4241
+ return consumedEvents;
4242
+ }
4243
+
4244
+ //#endregion
4245
+ //#region src/indexer/collectors/CollectFunctions/processors/processDebtTransfers.ts
4246
+ /**
4247
+ * Parse raw MorphoV2 logs and compute debt transfers.
4248
+ *
4249
+ * A debt is modeled as a negative position: borrowing = transfer FROM user TO zeroAddress,
4250
+ * repayment = FROM zeroAddress TO user. The contract field is the obligationId.
4251
+ *
4252
+ * Applies the 4-case Take matrix, Repay, and Liquidate attribution rules:
4253
+ *
4254
+ * **Take event** — Buyer = maker if offerIsBuy, taker otherwise. Seller = the other.
4255
+ * | buyerIsLender | sellerIsBorrower | Buyer transfer | Seller transfer |
4256
+ * |---------------|------------------|------------------------------|------------------------------|
4257
+ * | true | true | none | from: seller → to: 0x0 |
4258
+ * | true | false | none | none |
4259
+ * | false | true | from: 0x0 → to: buyer | from: seller → to: 0x0 |
4260
+ * | false | false | from: 0x0 → to: buyer | none |
4261
+ *
4262
+ * **Repay**: from: 0x0 → to: onBehalf
4263
+ * **Liquidate**: from: 0x0 → to: borrower (value = totalRepaid + badDebt)
4264
+ *
4265
+ * @param parameters - The parsed event logs and chain ID.
4266
+ * @param parameters.logs - Parsed event logs from MorphoV2 (Take, Repay, Liquidate events).
4267
+ * @param parameters.chainId - Chain ID for event attribution.
4268
+ * @returns Transfer events ready for DB insertion.
4269
+ */
4270
+ function processDebtTransfers(parameters) {
4271
+ const { logs, chainId } = parameters;
4272
+ const logger = getLogger();
4273
+ const transfers = [];
4274
+ for (const rawLog of logs) {
4275
+ if (rawLog.blockNumber === null || rawLog.logIndex === null || rawLog.transactionHash === null) {
4276
+ logger.debug({
4277
+ chainId,
4278
+ msg: "Skipping debt log because it is missing required fields"
4279
+ });
4280
+ continue;
4281
+ }
4282
+ const eventName = rawLog.eventName;
4283
+ const baseId = `${rawLog.blockNumber.toString()}-${rawLog.logIndex.toString()}-${chainId}-${rawLog.transactionHash}`;
4284
+ if (eventName === takeEvent.name) {
4285
+ const args = rawLog.args;
4286
+ 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) {
4287
+ logger.debug({
4288
+ chainId,
4289
+ msg: "Skipping Take log because it is missing required args for debt"
4290
+ });
4291
+ continue;
4292
+ }
4293
+ if (args.obligationUnits === 0n) continue;
4294
+ const buyer = args.offerIsBuy ? args.maker : args.taker;
4295
+ const seller = args.offerIsBuy ? args.taker : args.maker;
4296
+ const blockNumber = Number(rawLog.blockNumber);
4297
+ if (!args.buyerIsLender) transfers.push(from$9({
4298
+ id: `${baseId}-debt-buyer`,
4299
+ chainId,
4300
+ contract: args.id,
4301
+ from: zeroAddress,
4302
+ to: buyer,
4303
+ value: args.obligationUnits,
4304
+ type: Type.DEBT_OF,
4305
+ asset: zeroAddress,
4306
+ blockNumber
4307
+ }));
4308
+ if (args.sellerIsBorrower) transfers.push(from$9({
4309
+ id: `${baseId}-debt-seller`,
4310
+ chainId,
4311
+ contract: args.id,
4312
+ from: seller,
4313
+ to: zeroAddress,
4314
+ value: args.obligationUnits,
4315
+ type: Type.DEBT_OF,
4316
+ asset: zeroAddress,
4317
+ blockNumber
4318
+ }));
4319
+ continue;
4320
+ }
4321
+ if (eventName === repayEvent.name) {
4322
+ const args = rawLog.args;
4323
+ if (args?.id === void 0 || args?.obligationUnits === void 0 || args?.onBehalf === void 0) {
4324
+ logger.debug({
4325
+ chainId,
4326
+ msg: "Skipping Repay log because it is missing required args"
4327
+ });
4328
+ continue;
4329
+ }
4330
+ if (args.obligationUnits === 0n) continue;
4331
+ transfers.push(from$9({
4332
+ id: `${baseId}-debt-repay`,
4333
+ chainId,
4334
+ contract: args.id,
4335
+ from: zeroAddress,
4336
+ to: args.onBehalf,
4337
+ value: args.obligationUnits,
4338
+ type: Type.DEBT_OF,
4339
+ asset: zeroAddress,
4340
+ blockNumber: Number(rawLog.blockNumber)
4341
+ }));
4342
+ continue;
4343
+ }
4344
+ if (eventName === liquidateEvent.name) {
4345
+ const args = rawLog.args;
4346
+ if (args?.id === void 0 || args?.borrower === void 0 || args?.totalRepaid === void 0 || args?.badDebt === void 0) {
4347
+ logger.debug({
4348
+ chainId,
4349
+ msg: "Skipping Liquidate log because it is missing required args"
4350
+ });
4351
+ continue;
4352
+ }
4353
+ const totalReduction = args.totalRepaid + args.badDebt;
4354
+ if (totalReduction === 0n) continue;
4355
+ transfers.push(from$9({
4356
+ id: `${baseId}-debt-liquidate`,
4357
+ chainId,
4358
+ contract: args.id,
4359
+ from: zeroAddress,
4360
+ to: args.borrower,
4361
+ value: totalReduction,
4362
+ type: Type.DEBT_OF,
4363
+ asset: zeroAddress,
4364
+ blockNumber: Number(rawLog.blockNumber)
4365
+ }));
4366
+ }
4367
+ }
4368
+ return transfers;
4369
+ }
4370
+
4371
+ //#endregion
4372
+ //#region src/indexer/collectors/CollectFunctions/collectMorphoV2.ts
4373
+ async function* collectMorphoV2(parameters) {
3883
4374
  let { db, collector, client, lastBlockNumber: blockNumber, epoch, options: { maxBatchSize = 1e3, blockWindow } = {} } = parameters;
3884
4375
  const logger = getLogger();
3885
4376
  let startBlock = blockNumber;
@@ -3898,179 +4389,63 @@ async function* collectConsumedEvents(parameters) {
3898
4389
  });
3899
4390
  for await (const { logs, blockNumber: lastStreamBlockNumber } of stream) {
3900
4391
  const parsedLogs = parseEventLogs({
3901
- abi: [consumedEvent, takeEvent],
4392
+ abi: [
4393
+ consumedEvent,
4394
+ takeEvent,
4395
+ repayEvent,
4396
+ liquidateEvent,
4397
+ supplyCollateralEvent,
4398
+ withdrawCollateralEvent
4399
+ ],
3902
4400
  logs,
3903
4401
  strict: false
3904
4402
  });
3905
- const normalizedLogs = [];
3906
- const groups$3 = /* @__PURE__ */ new Map();
3907
- const eventIds = /* @__PURE__ */ new Set();
3908
- const recordLog = (log) => {
3909
- normalizedLogs.push(log);
3910
- eventIds.add(log.id);
3911
- const groupKey = buildGroupKey({
3912
- chainId: log.chainId,
3913
- maker: log.maker,
3914
- group: log.group
3915
- });
3916
- if (!groups$3.has(groupKey)) groups$3.set(groupKey, {
3917
- chainId: log.chainId,
3918
- maker: log.maker.toLowerCase(),
3919
- group: log.group.toLowerCase()
3920
- });
3921
- };
3922
- for (const rawLog of parsedLogs) {
3923
- if (rawLog.blockNumber === null || rawLog.logIndex === null || rawLog.transactionHash === null) {
3924
- logger.debug({
3925
- collector,
3926
- chainId: client.chain.id,
3927
- msg: "Skipping log because it is missing required fields"
3928
- });
3929
- continue;
3930
- }
3931
- if (rawLog.eventName === consumedEvent.name) {
3932
- const consumeArgs = rawLog.args;
3933
- if (consumeArgs.user === void 0 || consumeArgs.group === void 0 || consumeArgs.amount === void 0) {
3934
- logger.debug({
3935
- collector,
3936
- chainId: client.chain.id,
3937
- msg: "Skipping Consume log because it is missing required args"
3938
- });
3939
- continue;
3940
- }
3941
- recordLog({
3942
- kind: "consume",
3943
- id: `${rawLog.blockNumber.toString()}-${rawLog.logIndex.toString()}-${client.chain.id}-${rawLog.transactionHash}`,
3944
- chainId: client.chain.id,
3945
- maker: consumeArgs.user,
3946
- group: consumeArgs.group,
3947
- amount: consumeArgs.amount,
3948
- blockNumber: Number(rawLog.blockNumber)
3949
- });
3950
- continue;
3951
- }
3952
- if (rawLog.eventName === takeEvent.name) {
3953
- const takeArgs = rawLog.args;
3954
- if (takeArgs.maker === void 0 || takeArgs.group === void 0 || takeArgs.consumed === void 0) {
3955
- logger.debug({
3956
- collector,
3957
- chainId: client.chain.id,
3958
- msg: "Skipping Take log because it is missing required args"
3959
- });
3960
- continue;
3961
- }
3962
- recordLog({
3963
- kind: "take",
3964
- id: `${rawLog.blockNumber.toString()}-${rawLog.logIndex.toString()}-${client.chain.id}-${rawLog.transactionHash}`,
3965
- chainId: client.chain.id,
3966
- maker: takeArgs.maker,
3967
- group: takeArgs.group,
3968
- consumed: takeArgs.consumed,
3969
- blockNumber: Number(rawLog.blockNumber)
3970
- });
3971
- }
3972
- }
4403
+ const consumedEvents = processConsumedLogs({
4404
+ logs: parsedLogs,
4405
+ chainId: client.chain.id
4406
+ });
4407
+ const debtTransfers = processDebtTransfers({
4408
+ logs: parsedLogs,
4409
+ chainId: client.chain.id
4410
+ });
4411
+ const collateralTransfers = processCollateralTransfers({
4412
+ logs: parsedLogs,
4413
+ chainId: client.chain.id
4414
+ });
4415
+ const seizureEvents = processCollateralSeizures({
4416
+ logs: parsedLogs,
4417
+ chainId: client.chain.id
4418
+ });
3973
4419
  await db.transaction(async (dbTx) => {
3974
- const existingEventIds = /* @__PURE__ */ new Set();
3975
- if (eventIds.size > 0) {
3976
- const ids = Array.from(eventIds);
3977
- for (let index = 0; index < ids.length; index += 500) {
3978
- const slice = ids.slice(index, index + 500);
3979
- const { rows } = await dbTx.execute(sql`
3980
- SELECT event_id
3981
- FROM ${consumedEvents}
3982
- WHERE event_id IN (${sql.join(slice.map((id) => sql`${id}`), sql`,`)});
3983
- `);
3984
- for (const row of rows) existingEventIds.add(row.event_id);
3985
- }
3986
- }
3987
- const consumedByGroup = /* @__PURE__ */ new Map();
3988
- if (groups$3.size > 0) {
3989
- const groupList = Array.from(groups$3.values());
3990
- for (let index = 0; index < groupList.length; index += 500) {
3991
- const slice = groupList.slice(index, index + 500);
3992
- const { rows } = await dbTx.execute(sql`
3993
- WITH targets(chain_id, maker, "group") AS (
3994
- VALUES ${sql.join(slice.map((group) => sql`(${group.chainId}::bigint, ${group.maker.toLowerCase()}::varchar(42), ${group.group.toLowerCase()}::varchar(66))`), sql`,`)}
3995
- )
3996
- SELECT
3997
- targets.chain_id,
3998
- targets.maker,
3999
- targets."group",
4000
- COALESCE(g.consumed, 0)::numeric AS consumed
4001
- FROM targets
4002
- LEFT JOIN ${groups} g
4003
- ON g.chain_id = targets.chain_id
4004
- AND g.maker = targets.maker
4005
- AND g."group" = targets."group";
4006
- `);
4007
- for (const row of rows) {
4008
- const groupKey = buildGroupKey({
4009
- chainId: Number(row.chain_id),
4010
- maker: row.maker,
4011
- group: row.group
4012
- });
4013
- consumedByGroup.set(groupKey, BigInt(row.consumed ?? "0"));
4014
- }
4015
- }
4016
- }
4017
- const events = [];
4018
- for (const log of normalizedLogs) {
4019
- if (existingEventIds.has(log.id)) continue;
4020
- const groupKey = buildGroupKey({
4021
- chainId: log.chainId,
4022
- maker: log.maker,
4023
- group: log.group
4024
- });
4025
- const previousConsumed = consumedByGroup.get(groupKey) ?? 0n;
4026
- if (log.kind === "consume") {
4027
- events.push({
4028
- id: log.id,
4029
- chainId: log.chainId,
4030
- maker: log.maker,
4031
- group: log.group,
4032
- amount: log.amount,
4033
- blockNumber: log.blockNumber
4034
- });
4035
- consumedByGroup.set(groupKey, previousConsumed + log.amount);
4036
- continue;
4037
- }
4038
- const delta = log.consumed - previousConsumed;
4039
- if (delta <= 0n) {
4040
- logger.debug({
4041
- collector,
4042
- chainId: client.chain.id,
4043
- msg: "Skipping Take log because consumed did not increase",
4044
- previous_consumed: previousConsumed.toString(),
4045
- consumed: log.consumed.toString()
4046
- });
4047
- continue;
4048
- }
4049
- events.push({
4050
- id: log.id,
4051
- chainId: log.chainId,
4052
- maker: log.maker,
4053
- group: log.group,
4054
- amount: delta,
4055
- blockNumber: log.blockNumber
4056
- });
4057
- consumedByGroup.set(groupKey, log.consumed);
4420
+ const [resolvedSeizureTransfers, resolvedConsumedEvents] = await Promise.all([resolveSeizureTransfers({
4421
+ dbTx,
4422
+ seizureEvents
4423
+ }), resolveConsumedEvents({
4424
+ dbTx,
4425
+ consumedEvents,
4426
+ chainId: client.chain.id,
4427
+ collector,
4428
+ logger
4429
+ })]);
4430
+ if (debtTransfers.length > 0) {
4431
+ await dbTx.positions.upsert(transfersToPositions(debtTransfers));
4432
+ await dbTx.transfers.create(debtTransfers);
4058
4433
  }
4059
- try {
4060
- await dbTx.consumed.create(events);
4061
- if (events.length > 0) logger.info({
4062
- msg: `Events indexed`,
4063
- collector,
4064
- count: events.length,
4065
- chain_id: client.chain.id,
4066
- block_range: [startBlock, lastStreamBlockNumber]
4067
- });
4068
- } catch (err) {
4069
- logger.error({
4070
- err,
4071
- msg: "Failed to process consumed events"
4072
- });
4434
+ const allCollateralTransfers = [...resolvedSeizureTransfers, ...collateralTransfers];
4435
+ if (allCollateralTransfers.length > 0) {
4436
+ await dbTx.positions.upsert(transfersToPositions(allCollateralTransfers));
4437
+ await dbTx.transfers.create(allCollateralTransfers);
4073
4438
  }
4439
+ await dbTx.consumed.create(resolvedConsumedEvents);
4440
+ if (resolvedConsumedEvents.length > 0) logger.info({
4441
+ msg: "Events indexed",
4442
+ collector,
4443
+ consumed_count: resolvedConsumedEvents.length,
4444
+ debt_transfer_count: debtTransfers.length,
4445
+ collateral_transfer_count: allCollateralTransfers.length,
4446
+ chain_id: client.chain.id,
4447
+ block_range: [startBlock, lastStreamBlockNumber]
4448
+ });
4074
4449
  blockNumber = lastStreamBlockNumber;
4075
4450
  try {
4076
4451
  await dbTx.blocks.advanceCollector({
@@ -4086,15 +4461,27 @@ async function* collectConsumedEvents(parameters) {
4086
4461
  chainId: client.chain.id
4087
4462
  });
4088
4463
  blockNumber = ancestor.blockNumber;
4089
- const deleted = await dbTx.consumed.delete({
4464
+ const deletedConsumed = await dbTx.consumed.delete({
4090
4465
  chainId: client.chain.id,
4091
4466
  blockNumberGte: blockNumber + 1
4092
4467
  });
4468
+ const deletedDebtTransfers = await dbTx.transfers.delete({
4469
+ chainId: client.chain.id,
4470
+ blockNumberGte: blockNumber + 1,
4471
+ positionTypeId: positionTypeId(Type.DEBT_OF)
4472
+ });
4473
+ const deletedCollateralTransfers = await dbTx.transfers.delete({
4474
+ chainId: client.chain.id,
4475
+ blockNumberGte: blockNumber + 1,
4476
+ positionTypeId: positionTypeId(Type.COLLATERAL_OF)
4477
+ });
4093
4478
  logger.info({
4094
4479
  collector,
4095
4480
  chain_id: client.chain.id,
4096
- msg: `Reorg detected, events deleted`,
4097
- count: deleted,
4481
+ msg: "Reorg detected, events deleted",
4482
+ consumed_deleted: deletedConsumed,
4483
+ debt_transfers_deleted: deletedDebtTransfers,
4484
+ collateral_transfers_deleted: deletedCollateralTransfers,
4098
4485
  block_number: blockNumber
4099
4486
  });
4100
4487
  await dbTx.blocks.advanceCollector({
@@ -4105,7 +4492,7 @@ async function* collectConsumedEvents(parameters) {
4105
4492
  });
4106
4493
  reorgDetected = true;
4107
4494
  } catch (err) {
4108
- const msg = "Failed to delete consumed events when handling reorg.";
4495
+ const msg = "Failed to delete events when handling reorg.";
4109
4496
  logger.error({
4110
4497
  collector,
4111
4498
  chainId: client.chain.id,
@@ -4120,10 +4507,210 @@ async function* collectConsumedEvents(parameters) {
4120
4507
  yield blockNumber;
4121
4508
  startBlock = blockNumber;
4122
4509
  }
4510
+ if (!reorgDetected) await db.transaction(async (dbTx) => {
4511
+ const collectorState = await dbTx.blocks.getCollector({
4512
+ collectorName: collector,
4513
+ chainId: client.chain.id
4514
+ });
4515
+ const deletedConsumed = await dbTx.consumed.delete({
4516
+ chainId: client.chain.id,
4517
+ blockNumberGte: collectorState.blockNumber + 1
4518
+ });
4519
+ const deletedDebtTransfers = await dbTx.transfers.delete({
4520
+ chainId: client.chain.id,
4521
+ blockNumberGte: collectorState.blockNumber + 1,
4522
+ positionTypeId: positionTypeId(Type.DEBT_OF)
4523
+ });
4524
+ const deletedCollateralTransfers = await dbTx.transfers.delete({
4525
+ chainId: client.chain.id,
4526
+ blockNumberGte: collectorState.blockNumber + 1,
4527
+ positionTypeId: positionTypeId(Type.COLLATERAL_OF)
4528
+ });
4529
+ if (deletedConsumed > 0 || deletedDebtTransfers > 0 || deletedCollateralTransfers > 0) logger.info({
4530
+ collector,
4531
+ chain_id: client.chain.id,
4532
+ msg: "Reorg detected, events deleted",
4533
+ consumed_deleted: deletedConsumed,
4534
+ debt_transfers_deleted: deletedDebtTransfers,
4535
+ collateral_transfers_deleted: deletedCollateralTransfers,
4536
+ block_number: collectorState.blockNumber
4537
+ });
4538
+ });
4539
+ }
4540
+ async function resolveSeizureTransfers(parameters) {
4541
+ const { dbTx, seizureEvents } = parameters;
4542
+ if (seizureEvents.length === 0) return [];
4543
+ const uniqueObligationIds = [...new Set(seizureEvents.map((event) => event.obligationId.toLowerCase()))];
4544
+ const rows = await dbTx.select({
4545
+ obligationId: obligationIdKeys.obligationId,
4546
+ collateralIndex: obligationCollateralsV2.collateralIndex,
4547
+ asset: obligationCollateralsV2.asset
4548
+ }).from(obligationIdKeys).innerJoin(obligationCollateralsV2, eq(obligationIdKeys.obligationKey, obligationCollateralsV2.obligationKey)).where(inArray(obligationIdKeys.obligationId, uniqueObligationIds));
4549
+ const resolutionMap = /* @__PURE__ */ new Map();
4550
+ for (const row of rows) resolutionMap.set(`${row.obligationId.toLowerCase()}-${row.collateralIndex}`, row.asset);
4551
+ const resolvedSeizureTransfers = [];
4552
+ for (const seizure of seizureEvents) {
4553
+ const key = `${seizure.obligationId.toLowerCase()}-${seizure.collateralIndex}`;
4554
+ const asset = resolutionMap.get(key);
4555
+ if (asset === void 0) throw new Error(`Unresolvable Liquidate seizure: obligationId=${seizure.obligationId}, collateralIndex=${seizure.collateralIndex}, chainId=${seizure.chainId}, blockNumber=${seizure.blockNumber}`);
4556
+ resolvedSeizureTransfers.push(from$9({
4557
+ id: seizure.id,
4558
+ chainId: seizure.chainId,
4559
+ contract: seizure.obligationId,
4560
+ from: seizure.user,
4561
+ to: zeroAddress,
4562
+ value: seizure.amount,
4563
+ type: Type.COLLATERAL_OF,
4564
+ asset,
4565
+ blockNumber: seizure.blockNumber
4566
+ }));
4567
+ }
4568
+ return resolvedSeizureTransfers;
4569
+ }
4570
+ async function resolveConsumedEvents(parameters) {
4571
+ const { dbTx, consumedEvents, chainId, collector, logger } = parameters;
4572
+ if (consumedEvents.length === 0) return [];
4573
+ const [existingConsumedIds, consumedByGroup] = await Promise.all([getExistingConsumedIds({
4574
+ dbTx,
4575
+ consumedEvents
4576
+ }), getConsumedByGroup({
4577
+ dbTx,
4578
+ consumedEvents
4579
+ })]);
4580
+ const resolvedEvents = [];
4581
+ for (const log of consumedEvents) {
4582
+ if (existingConsumedIds.has(log.id)) continue;
4583
+ const groupKey = buildGroupKey({
4584
+ chainId: log.chainId,
4585
+ maker: log.maker,
4586
+ group: log.group
4587
+ });
4588
+ const previousConsumed = consumedByGroup.get(groupKey) ?? 0n;
4589
+ if (log.kind === "consume") {
4590
+ resolvedEvents.push({
4591
+ id: log.id,
4592
+ chainId: log.chainId,
4593
+ maker: log.maker,
4594
+ group: log.group,
4595
+ amount: log.amount,
4596
+ blockNumber: log.blockNumber
4597
+ });
4598
+ consumedByGroup.set(groupKey, previousConsumed + log.amount);
4599
+ continue;
4600
+ }
4601
+ const delta = log.consumed - previousConsumed;
4602
+ if (delta <= 0n) {
4603
+ logger.debug({
4604
+ collector,
4605
+ chainId,
4606
+ msg: "Skipping Take log because consumed did not increase",
4607
+ previous_consumed: previousConsumed.toString(),
4608
+ consumed: log.consumed.toString()
4609
+ });
4610
+ continue;
4611
+ }
4612
+ resolvedEvents.push({
4613
+ id: log.id,
4614
+ chainId: log.chainId,
4615
+ maker: log.maker,
4616
+ group: log.group,
4617
+ amount: delta,
4618
+ blockNumber: log.blockNumber
4619
+ });
4620
+ consumedByGroup.set(groupKey, log.consumed);
4621
+ }
4622
+ return resolvedEvents;
4623
+ }
4624
+ async function getExistingConsumedIds(parameters) {
4625
+ const { dbTx, consumedEvents: consumedEvents$1 } = parameters;
4626
+ const existingConsumedIds = /* @__PURE__ */ new Set();
4627
+ const ids = Array.from(new Set(consumedEvents$1.map((event) => event.id)));
4628
+ for (let index = 0; index < ids.length; index += 500) {
4629
+ const slice = ids.slice(index, index + 500);
4630
+ const { rows } = await dbTx.execute(sql`
4631
+ SELECT event_id
4632
+ FROM ${consumedEvents}
4633
+ WHERE event_id IN (${sql.join(slice.map((id) => sql`${id}`), sql`,`)});
4634
+ `);
4635
+ for (const row of rows) existingConsumedIds.add(row.event_id);
4636
+ }
4637
+ return existingConsumedIds;
4638
+ }
4639
+ async function getConsumedByGroup(parameters) {
4640
+ const { dbTx, consumedEvents } = parameters;
4641
+ const groups$3 = /* @__PURE__ */ new Map();
4642
+ for (const event of consumedEvents) {
4643
+ const key = buildGroupKey({
4644
+ chainId: event.chainId,
4645
+ maker: event.maker,
4646
+ group: event.group
4647
+ });
4648
+ if (!groups$3.has(key)) groups$3.set(key, {
4649
+ chainId: event.chainId,
4650
+ maker: event.maker,
4651
+ group: event.group
4652
+ });
4653
+ }
4654
+ const consumedByGroup = /* @__PURE__ */ new Map();
4655
+ const groupList = Array.from(groups$3.values());
4656
+ for (let index = 0; index < groupList.length; index += 500) {
4657
+ const slice = groupList.slice(index, index + 500);
4658
+ const { rows } = await dbTx.execute(sql`
4659
+ WITH targets(chain_id, maker, "group") AS (
4660
+ VALUES ${sql.join(slice.map((group) => sql`(${group.chainId}::bigint, ${group.maker.toLowerCase()}::varchar(42), ${group.group.toLowerCase()}::varchar(66))`), sql`,`)}
4661
+ )
4662
+ SELECT
4663
+ targets.chain_id,
4664
+ targets.maker,
4665
+ targets."group",
4666
+ COALESCE(g.consumed, 0)::numeric AS consumed
4667
+ FROM targets
4668
+ LEFT JOIN ${groups} g
4669
+ ON g.chain_id = targets.chain_id
4670
+ AND g.maker = targets.maker
4671
+ AND g."group" = targets."group";
4672
+ `);
4673
+ for (const row of rows) {
4674
+ const groupKey = buildGroupKey({
4675
+ chainId: Number(row.chain_id),
4676
+ maker: row.maker,
4677
+ group: row.group
4678
+ });
4679
+ consumedByGroup.set(groupKey, BigInt(row.consumed ?? "0"));
4680
+ }
4681
+ }
4682
+ return consumedByGroup;
4683
+ }
4684
+ function transfersToPositions(transfers) {
4685
+ if (transfers.length === 0) return [];
4686
+ const transferType = transfers[0].type;
4687
+ if (transfers.some((transfer) => transfer.type !== transferType)) throw new Error("Cannot map transfers with mixed position types to positions.");
4688
+ const positionKeys = /* @__PURE__ */ new Map();
4689
+ for (const transfer of transfers) for (const user of [transfer.from, transfer.to]) {
4690
+ const key = `${transfer.chainId}-${transfer.contract}-${user}-${transfer.asset}`.toLowerCase();
4691
+ const existing = positionKeys.get(key);
4692
+ if (!existing || transfer.blockNumber < existing.blockNumber) positionKeys.set(key, {
4693
+ chainId: transfer.chainId,
4694
+ contract: transfer.contract,
4695
+ user,
4696
+ asset: transfer.asset,
4697
+ blockNumber: transfer.blockNumber
4698
+ });
4699
+ }
4700
+ return Array.from(positionKeys.values()).map((position) => from$12({
4701
+ chainId: position.chainId,
4702
+ contract: position.contract,
4703
+ user: position.user,
4704
+ type: transferType,
4705
+ balance: 0n,
4706
+ asset: position.asset,
4707
+ blockNumber: position.blockNumber
4708
+ }));
4123
4709
  }
4124
4710
 
4125
4711
  //#endregion
4126
4712
  //#region src/indexer/collectors/CollectFunctions/collectOffers.ts
4713
+ const ERC20_TYPE_ID = Object.values(Type).indexOf(Type.ERC20) + 1;
4127
4714
  async function* collectOffersV2(parameters) {
4128
4715
  let { db, collector, client, lastBlockNumber: blockNumber, gatekeeper, options: { maxBatchSize = 1e3, blockWindow } = {} } = parameters;
4129
4716
  const logger = getLogger();
@@ -4137,9 +4724,10 @@ async function* collectOffersV2(parameters) {
4137
4724
  });
4138
4725
  throw new Error(msg);
4139
4726
  }
4727
+ const morphoV2 = client.chain.custom.morpho.address;
4140
4728
  const signatureDomain = {
4141
4729
  chainId: client.chain.id,
4142
- verifyingContract: client.chain.custom.morpho.address
4730
+ verifyingContract: morphoV2
4143
4731
  };
4144
4732
  const { blockNumber: latestBlockNumberChain } = await db.blocks.getChain(client.chain.id);
4145
4733
  const stream = streamLogs({
@@ -4202,10 +4790,14 @@ async function* collectOffersV2(parameters) {
4202
4790
  await db.transaction(async (dbTx) => {
4203
4791
  const { epoch, blockNumber: latestBlockNumber } = await dbTx.blocks.getChain(client.chain.id);
4204
4792
  const treesToInsert = [];
4793
+ const pathsToInsert = [];
4205
4794
  let totalValidOffers = 0;
4206
4795
  const offersWithBlock = [];
4207
4796
  for (const { tree, signature, blockNumber: treeBlockNumber } of decodedTrees) try {
4208
- const allowedResults = await gatekeeper.isAllowed(tree.offers);
4797
+ const allowedResults = await gatekeeper.isAllowed({
4798
+ offers: tree.offers,
4799
+ chainId: client.chain.id
4800
+ });
4209
4801
  const hasBlockWindowViolation = treeBlockNumber > latestBlockNumber;
4210
4802
  if (!(allowedResults.issues.length === 0 && allowedResults.valid.length === tree.offers.length) || hasBlockWindowViolation) {
4211
4803
  if (allowedResults.issues.length > 0) {
@@ -4224,14 +4816,35 @@ async function* collectOffersV2(parameters) {
4224
4816
  continue;
4225
4817
  }
4226
4818
  treesToInsert.push({
4227
- tree,
4819
+ root: tree.root,
4228
4820
  signature
4229
4821
  });
4230
4822
  totalValidOffers += tree.offers.length;
4231
- offersWithBlock.push(...tree.offers.map((offer) => ({
4232
- offer,
4233
- blockNumber: treeBlockNumber
4234
- })));
4823
+ const obligationIdsByOfferHash = /* @__PURE__ */ new Map();
4824
+ for (const offer of tree.offers) {
4825
+ const offerHash = hash(offer).toLowerCase();
4826
+ const obligationId$1 = obligationId(offer, {
4827
+ chainId: client.chain.id,
4828
+ morphoV2
4829
+ }).toLowerCase();
4830
+ offersWithBlock.push({
4831
+ offer,
4832
+ blockNumber: treeBlockNumber,
4833
+ obligationId: obligationId$1
4834
+ });
4835
+ obligationIdsByOfferHash.set(offerHash, obligationId$1);
4836
+ }
4837
+ for (const proof of proofs(tree)) {
4838
+ const offerHash = hash(proof.offer).toLowerCase();
4839
+ const obligationId = obligationIdsByOfferHash.get(offerHash);
4840
+ if (obligationId === void 0) continue;
4841
+ pathsToInsert.push({
4842
+ offerHash,
4843
+ obligationId,
4844
+ treeRoot: tree.root,
4845
+ proof: proof.path
4846
+ });
4847
+ }
4235
4848
  } catch (err) {
4236
4849
  const error = err instanceof Error ? err : new Error(String(err));
4237
4850
  logger.error({
@@ -4241,19 +4854,23 @@ async function* collectOffersV2(parameters) {
4241
4854
  });
4242
4855
  throw new Error("Gatekeeper validation failed", { cause: error });
4243
4856
  }
4244
- const dependencies = buildOfferDependencies(offersWithBlock);
4857
+ const dependencies = buildOfferDependencies({
4858
+ offers: offersWithBlock,
4859
+ chainId: client.chain.id,
4860
+ morphoV2
4861
+ });
4245
4862
  await dbTx.oracles.upsert(dependencies.oracles);
4246
4863
  await dbTx.obligations.create(dependencies.obligations);
4247
4864
  await dbTx.groups.create(dependencies.groups);
4248
- const insertedHashes = await dbTx.offers.create(dependencies.offerBatches);
4249
- if (treesToInsert.length > 0) await dbTx.trees.create(treesToInsert);
4250
- const insertedOffers = filterInsertedOffers({
4251
- offers: offersWithBlock,
4252
- hashes: insertedHashes
4253
- });
4865
+ const insertedReferences = await dbTx.offers.create(dependencies.offerBatches);
4866
+ if (treesToInsert.length > 0) await dbTx.trees.upsert(treesToInsert);
4867
+ if (pathsToInsert.length > 0) await dbTx.trees.upsertPaths(pathsToInsert);
4254
4868
  const { callbacks, positions, lots } = decodeCallbacks({
4255
- chainId: client.chain.id,
4256
- offers: insertedOffers
4869
+ offers: filterInsertedOffers({
4870
+ offers: offersWithBlock,
4871
+ references: insertedReferences
4872
+ }),
4873
+ chainId: client.chain.id
4257
4874
  });
4258
4875
  if (positions.length > 0) await dbTx.positions.upsert(positions);
4259
4876
  if (callbacks.length > 0) await dbTx.callbacks.upsert(callbacks);
@@ -4316,7 +4933,7 @@ async function* collectOffersV2(parameters) {
4316
4933
  }
4317
4934
  }
4318
4935
  function decodeCallbacks(parameters) {
4319
- const { offers } = parameters;
4936
+ const { offers, chainId } = parameters;
4320
4937
  if (offers.length === 0) return {
4321
4938
  callbacks: [],
4322
4939
  positions: [],
@@ -4325,12 +4942,12 @@ function decodeCallbacks(parameters) {
4325
4942
  const callbacks = [];
4326
4943
  const positions = [];
4327
4944
  const lots = [];
4328
- for (const { offer, blockNumber: offerBlockNumber } of offers) {
4945
+ for (const { offer, blockNumber: offerBlockNumber, obligationId } of offers) {
4329
4946
  if (!offer.buy) continue;
4330
4947
  if (!isEmptyCallback(offer)) continue;
4331
4948
  const loanToken = offer.loanToken.toLowerCase();
4332
4949
  positions.push(from$12({
4333
- chainId: offer.chainId,
4950
+ chainId,
4334
4951
  contract: loanToken,
4335
4952
  user: offer.maker,
4336
4953
  type: Type.ERC20,
@@ -4338,20 +4955,23 @@ function decodeCallbacks(parameters) {
4338
4955
  blockNumber: offerBlockNumber
4339
4956
  }));
4340
4957
  lots.push({
4341
- positionChainId: offer.chainId,
4958
+ positionChainId: chainId,
4342
4959
  positionContract: loanToken,
4343
4960
  positionUser: offer.maker,
4961
+ positionTypeId: ERC20_TYPE_ID,
4344
4962
  group: offer.group,
4345
- obligationId: obligationId(offer),
4963
+ obligationId,
4346
4964
  size: offer.assets
4347
4965
  });
4348
4966
  callbacks.push({
4349
4967
  offerHash: hash(offer),
4968
+ obligationId,
4350
4969
  callbacks: [{
4351
- chainId: offer.chainId,
4970
+ chainId,
4352
4971
  contract: loanToken,
4353
4972
  user: offer.maker,
4354
- amount: offer.assets
4973
+ amount: offer.assets,
4974
+ positionTypeId: ERC20_TYPE_ID
4355
4975
  }]
4356
4976
  });
4357
4977
  }
@@ -4361,34 +4981,42 @@ function decodeCallbacks(parameters) {
4361
4981
  lots
4362
4982
  };
4363
4983
  }
4364
- function buildOfferDependencies(offers) {
4984
+ function buildOfferDependencies(parameters) {
4985
+ const { offers, chainId, morphoV2 } = parameters;
4365
4986
  const obligationsById = /* @__PURE__ */ new Map();
4366
4987
  const oraclesByKey = /* @__PURE__ */ new Map();
4367
4988
  const groupsByKey = /* @__PURE__ */ new Map();
4368
4989
  const offersByBlock = /* @__PURE__ */ new Map();
4369
- for (const { offer, blockNumber } of offers) {
4990
+ for (const { offer, blockNumber, obligationId } of offers) {
4370
4991
  const list = offersByBlock.get(blockNumber) ?? [];
4371
- list.push(offer);
4992
+ list.push({
4993
+ offer,
4994
+ obligationId,
4995
+ chainId
4996
+ });
4372
4997
  offersByBlock.set(blockNumber, list);
4373
- const obligationId$1 = obligationId(offer);
4374
- if (!obligationsById.get(obligationId$1)) obligationsById.set(obligationId$1, from$15({
4375
- chainId: offer.chainId,
4376
- loanToken: offer.loanToken,
4377
- maturity: offer.maturity,
4378
- collaterals: offer.collaterals
4379
- }));
4998
+ if (!obligationsById.get(obligationId)) obligationsById.set(obligationId, {
4999
+ obligationId,
5000
+ chainId,
5001
+ morphoV2,
5002
+ obligation: from$15({
5003
+ loanToken: offer.loanToken,
5004
+ maturity: offer.maturity,
5005
+ collaterals: offer.collaterals
5006
+ })
5007
+ });
4380
5008
  for (const collateral of offer.collaterals) {
4381
- const oracleKey = `${offer.chainId}-${collateral.oracle}`.toLowerCase();
5009
+ const oracleKey = `${chainId}-${collateral.oracle}`.toLowerCase();
4382
5010
  if (!oraclesByKey.has(oracleKey)) oraclesByKey.set(oracleKey, from$13({
4383
- chainId: offer.chainId,
5011
+ chainId,
4384
5012
  address: collateral.oracle,
4385
5013
  price: null,
4386
5014
  blockNumber
4387
5015
  }));
4388
5016
  }
4389
- const groupKey = `${offer.chainId}-${offer.maker}-${offer.group}`.toLowerCase();
5017
+ const groupKey = `${chainId}-${offer.maker}-${offer.group}`.toLowerCase();
4390
5018
  if (!groupsByKey.has(groupKey)) groupsByKey.set(groupKey, {
4391
- chainId: offer.chainId,
5019
+ chainId,
4392
5020
  maker: offer.maker,
4393
5021
  group: offer.group,
4394
5022
  blockNumber
@@ -4405,15 +5033,22 @@ function buildOfferDependencies(offers) {
4405
5033
  };
4406
5034
  }
4407
5035
  function filterInsertedOffers(parameters) {
4408
- if (parameters.hashes.length === 0) return [];
4409
- const inserted = new Set(parameters.hashes.map((hash) => hash.toLowerCase()));
5036
+ if (parameters.references.length === 0) return [];
5037
+ const keyOf = (input) => `${input.hash.toLowerCase()}:${input.obligationId.toLowerCase()}`;
5038
+ const inserted = new Set(parameters.references.map((offer) => keyOf({
5039
+ hash: offer.hash,
5040
+ obligationId: offer.obligationId
5041
+ })));
4410
5042
  const seen = /* @__PURE__ */ new Set();
4411
5043
  const filtered = [];
4412
5044
  for (const entry of parameters.offers) {
4413
- const hash$3 = hash(entry.offer).toLowerCase();
4414
- if (!inserted.has(hash$3)) continue;
4415
- if (seen.has(hash$3)) continue;
4416
- seen.add(hash$3);
5045
+ const key = keyOf({
5046
+ hash: hash(entry.offer),
5047
+ obligationId: entry.obligationId
5048
+ });
5049
+ if (!inserted.has(key)) continue;
5050
+ if (seen.has(key)) continue;
5051
+ seen.add(key);
4417
5052
  filtered.push(entry);
4418
5053
  }
4419
5054
  return filtered;
@@ -4528,7 +5163,7 @@ async function snapshotVaultPositions(parameters) {
4528
5163
  convertToAssets: (shares) => {
4529
5164
  const contract = contracts.get(position.contract.toLowerCase());
4530
5165
  if (!contract) return;
4531
- if (contract.decimalsOffset === void 0 || contract.totalAssets === void 0 || contract.totalSupply === void 0) return;
5166
+ if (contract.decimalsOffset === void 0 || contract.totalAssets === void 0 || contract.totalSupply === void 0 || contract.asset === void 0) return;
4532
5167
  try {
4533
5168
  position.balance = convertToAssets({
4534
5169
  shares,
@@ -4683,12 +5318,15 @@ async function* collectPositions(parameters) {
4683
5318
  from: log.args.from,
4684
5319
  to: log.args.to,
4685
5320
  value: log.args.value,
5321
+ type: Type.ERC20,
5322
+ asset: log.address,
4686
5323
  blockNumber: Number(log.blockNumber)
4687
5324
  }));
4688
5325
  }
4689
5326
  const { positions } = await db.positions.get({
4690
5327
  chainId: client.chain.id,
4691
- filled: false
5328
+ filled: false,
5329
+ type: Type.ERC20
4692
5330
  });
4693
5331
  const newPositions = [];
4694
5332
  try {
@@ -4811,7 +5449,8 @@ async function* collectPositions(parameters) {
4811
5449
  blockNumber = ancestor.blockNumber;
4812
5450
  const emptied = await dbTx.positions.setEmptyAfter({
4813
5451
  chainId: client.chain.id,
4814
- blockNumber: blockNumber + 1
5452
+ blockNumber: blockNumber + 1,
5453
+ type: Type.ERC20
4815
5454
  });
4816
5455
  logger.info({
4817
5456
  msg: "Reorg detected, positions set to empty",
@@ -5038,10 +5677,10 @@ function createBuilder(parameters) {
5038
5677
  }
5039
5678
  }));
5040
5679
  },
5041
- buildConsumedEventsCollector: ({ options: { maxBatchSize = 1e3 } = {} } = {}) => {
5042
- return createCollector("consumed_events", (p) => collectConsumedEvents({
5680
+ buildMorphoV2Collector: ({ options: { maxBatchSize = 1e3 } = {} } = {}) => {
5681
+ return createCollector("morpho_v2", (p) => collectMorphoV2({
5043
5682
  ...p,
5044
- collector: "consumed_events",
5683
+ collector: "morpho_v2",
5045
5684
  options: {
5046
5685
  maxBatchSize,
5047
5686
  blockWindow
@@ -5090,7 +5729,7 @@ const from$7 = (parameters) => {
5090
5729
  });
5091
5730
  return {
5092
5731
  offersCollector: collectorBuilder.buildOffersCollector({ options: { maxBatchSize } }),
5093
- consumedEventsCollector: collectorBuilder.buildConsumedEventsCollector({ options: { maxBatchSize } }),
5732
+ morphoV2Collector: collectorBuilder.buildMorphoV2Collector({ options: { maxBatchSize } }),
5094
5733
  pricesCollector: collectorBuilder.buildPricesCollector({ options: {
5095
5734
  maxBatchSize,
5096
5735
  retryAttempts,
@@ -5112,7 +5751,7 @@ var Indexer_exports = /* @__PURE__ */ __exportAll({
5112
5751
  });
5113
5752
  function from$6(config) {
5114
5753
  const { client, gatekeeper, db, interval = 1e4, maxBatchSize = 1e3, maxBlockNumber, blockWindow, retryAttempts, retryDelayMs } = config;
5115
- const { offersCollector, consumedEventsCollector, positionsCollector, pricesCollector } = from$7({
5754
+ const { offersCollector, morphoV2Collector, positionsCollector, pricesCollector } = from$7({
5116
5755
  client,
5117
5756
  db,
5118
5757
  gatekeeper,
@@ -5127,7 +5766,7 @@ function from$6(config) {
5127
5766
  client,
5128
5767
  collectors: [
5129
5768
  offersCollector,
5130
- consumedEventsCollector,
5769
+ morphoV2Collector,
5131
5770
  positionsCollector,
5132
5771
  pricesCollector
5133
5772
  ]
@@ -5332,12 +5971,13 @@ var ObligationResponse_exports = /* @__PURE__ */ __exportAll({ from: () => from$
5332
5971
  * @constructor
5333
5972
  * @param obligation - {@link Obligation}
5334
5973
  * @param quote - {@link Quote}
5974
+ * @param chainId - The chain id used to compute `id`.
5335
5975
  * @returns The created `ObligationResponse`. {@link ObligationResponse}
5336
5976
  */
5337
- function from$4(obligation, quote) {
5977
+ function from$4(obligation, quote, chainId) {
5338
5978
  return {
5339
5979
  id: quote.obligationId,
5340
- chain_id: obligation.chainId,
5980
+ chain_id: chainId,
5341
5981
  loan_token: obligation.loanToken,
5342
5982
  collaterals: obligation.collaterals.map((c) => ({
5343
5983
  token: c.asset,
@@ -5404,12 +6044,7 @@ function from$3(input) {
5404
6044
  receiver_if_maker_is_seller: input.receiverIfMakerIsSeller
5405
6045
  },
5406
6046
  offer_hash: input.hash,
5407
- obligation_id: id({
5408
- chainId,
5409
- loanToken: input.loanToken,
5410
- collaterals: [...input.collaterals],
5411
- maturity: input.maturity
5412
- }),
6047
+ obligation_id: input.obligationId,
5413
6048
  chain_id: chainId,
5414
6049
  consumed: input.consumed.toString(),
5415
6050
  takeable: input.takeable.toString(),
@@ -6036,10 +6671,6 @@ __decorate([ApiProperty({
6036
6671
  type: "boolean",
6037
6672
  example: validateOfferExample.buy
6038
6673
  })], ValidateOfferRequest.prototype, "buy", void 0);
6039
- __decorate([ApiProperty({
6040
- type: "number",
6041
- example: validateOfferExample.chain_id
6042
- })], ValidateOfferRequest.prototype, "chain_id", void 0);
6043
6674
  __decorate([ApiProperty({
6044
6675
  type: "string",
6045
6676
  example: validateOfferExample.loan_token
@@ -6057,6 +6688,11 @@ __decorate([ApiProperty({
6057
6688
  example: validateOfferExample.receiver_if_maker_is_seller
6058
6689
  })], ValidateOfferRequest.prototype, "receiver_if_maker_is_seller", void 0);
6059
6690
  var ValidateOffersRequest = class {};
6691
+ __decorate([ApiProperty({
6692
+ type: "number",
6693
+ description: "Chain id used for chain-scoped validation rules.",
6694
+ example: validateOfferExample.chain_id
6695
+ })], ValidateOffersRequest.prototype, "chain_id", void 0);
6060
6696
  __decorate([ApiProperty({
6061
6697
  type: () => [ValidateOfferRequest],
6062
6698
  description: "Array of offers in snake_case format. Required, non-empty.",
@@ -6823,6 +7459,12 @@ function isValidBase64urlJson(val) {
6823
7459
  function isValidOfferHashCursor(val) {
6824
7460
  return /^0x[a-f0-9]{64}$/i.test(val);
6825
7461
  }
7462
+ function isValidOfferCursor(val) {
7463
+ const [hash, obligationId, ...rest] = val.split(":");
7464
+ if (rest.length !== 0) return false;
7465
+ if (!hash || !obligationId) return false;
7466
+ return isValidOfferHashCursor(hash) && isValidOfferHashCursor(obligationId);
7467
+ }
6826
7468
  const csvArray = (schema) => z$1.preprocess((value) => {
6827
7469
  if (value === void 0) return void 0;
6828
7470
  if (Array.isArray(value)) {
@@ -6886,7 +7528,7 @@ const GetConfigContractsQueryParams = z$1.object({
6886
7528
  });
6887
7529
  const GetOffersQueryParams = PaginationQueryParams.omit({ cursor: true }).extend({
6888
7530
  cursor: z$1.string().optional().meta({
6889
- description: "Pagination cursor. Use offer hash (0x...) for maker queries, base64url for obligation queries.",
7531
+ description: "Pagination cursor. Use offer hash:obligation_id for maker queries, base64url for obligation queries.",
6890
7532
  example: "eyJzaWRlIjoic2VsbCIsImN1cnJlbnRQcmljZSI6IjEwMDAwMDAwMDAwMDAwMDAwMDAiLCJibG9ja051bWJlciI6MSwiYXNzZXRzIjoiMTAwMDAwMDAwMDAwMDAwMDAwMCIsImhhc2giOiIweGRmZDY4NTllM2UwODJkMTkzODlhMWFlYzFiZGFkN2U4ZDkyZDk2YjFhYTc5NDBkYTkxYTMxMjVkMzFlM2JlNWIiLCJ0b3RhbFJldHVybmVkIjoxMCwibm93IjoxNjAwMDAwMDAwfQ"
6891
7533
  }),
6892
7534
  side: z$1.enum(["buy", "sell"]).optional().meta({
@@ -6913,10 +7555,10 @@ const GetOffersQueryParams = PaginationQueryParams.omit({ cursor: true }).extend
6913
7555
  return;
6914
7556
  }
6915
7557
  if (hasMaker) {
6916
- if (val.cursor !== void 0 && !isValidOfferHashCursor(val.cursor)) ctx.addIssue({
7558
+ if (val.cursor !== void 0 && !isValidOfferCursor(val.cursor)) ctx.addIssue({
6917
7559
  code: "custom",
6918
7560
  path: ["cursor"],
6919
- message: "Cursor must be a 32-byte hex offer hash when filtering by maker"
7561
+ message: "Cursor must be in the offer hash:obligation_id format when filtering by maker"
6920
7562
  });
6921
7563
  return;
6922
7564
  }
@@ -7026,7 +7668,13 @@ const GetBookParams = z$1.object({
7026
7668
  example: "buy"
7027
7669
  })
7028
7670
  });
7029
- const ValidateOffersBody = z$1.object({ offers: z$1.array(z$1.unknown()).min(1, { message: "'offers' must contain at least 1 offer" }) }).strict();
7671
+ const ValidateOffersBody = z$1.object({
7672
+ chain_id: z$1.number().int().positive("chain_id must be a positive integer").meta({
7673
+ description: "Chain id used for chain-scoped validation rules.",
7674
+ example: 1
7675
+ }),
7676
+ offers: z$1.array(z$1.unknown()).min(1, { message: "'offers' must contain at least 1 offer" })
7677
+ }).strict();
7030
7678
  const GetUserPositionsParams = z$1.object({
7031
7679
  ...PaginationQueryParams.shape,
7032
7680
  user_address: z$1.string().regex(/^0x[a-fA-F0-9]{40}$/, { error: "User address must be a valid 20-byte address" }).transform((val) => val.toLowerCase()).meta({
@@ -7165,19 +7813,19 @@ async function getConfigContracts(query, chainRegistry) {
7165
7813
  });
7166
7814
  let cursorContract = null;
7167
7815
  if (cursor) try {
7168
- cursorContract = parseCursor$1(cursor);
7816
+ cursorContract = parseCursor$3(cursor);
7169
7817
  } catch (err) {
7170
7818
  return failure(err);
7171
7819
  }
7172
7820
  const startIndex = cursorContract ? findStartIndex$1(contracts, cursorContract) : 0;
7173
7821
  const page = contracts.slice(startIndex, startIndex + limit);
7174
- const nextCursor = startIndex + limit < contracts.length && page.length > 0 ? formatCursor$1(page.at(-1)) : null;
7822
+ const nextCursor = startIndex + limit < contracts.length && page.length > 0 ? formatCursor$3(page.at(-1)) : null;
7175
7823
  return success({
7176
7824
  data: page,
7177
7825
  cursor: nextCursor
7178
7826
  });
7179
7827
  }
7180
- function parseCursor$1(cursor) {
7828
+ function parseCursor$3(cursor) {
7181
7829
  const [chain, address] = cursor.split(":", 2);
7182
7830
  if (!chain || !address) throw new BadRequestError$1("Cursor must be in the format chain_id:0x...");
7183
7831
  return {
@@ -7185,7 +7833,7 @@ function parseCursor$1(cursor) {
7185
7833
  address: address.toLowerCase()
7186
7834
  };
7187
7835
  }
7188
- function formatCursor$1(contract) {
7836
+ function formatCursor$3(contract) {
7189
7837
  return `${contract.chain_id}:${contract.address.toLowerCase()}`;
7190
7838
  }
7191
7839
  function findStartIndex$1(contracts, cursor) {
@@ -7428,7 +8076,7 @@ async function getConfigRules(query, chains) {
7428
8076
  const checksum = buildConfigRulesChecksum(filteredRules);
7429
8077
  let cursorRule = null;
7430
8078
  if (cursor) try {
7431
- cursorRule = parseCursor(cursor);
8079
+ cursorRule = parseCursor$2(cursor);
7432
8080
  } catch (err) {
7433
8081
  return failure(err);
7434
8082
  }
@@ -7436,7 +8084,7 @@ async function getConfigRules(query, chains) {
7436
8084
  if (cursorRule && chainFilter && !chainFilter.has(cursorRule.chain_id)) return failure(new BadRequestError$1("Cursor chain_id must match requested chains"));
7437
8085
  const startIndex = cursorRule ? findStartIndex(filteredRules, cursorRule) : 0;
7438
8086
  const page = filteredRules.slice(startIndex, startIndex + limit);
7439
- const nextCursor = startIndex + limit < filteredRules.length && page.length > 0 ? formatCursor(page.at(-1)) : null;
8087
+ const nextCursor = startIndex + limit < filteredRules.length && page.length > 0 ? formatCursor$2(page.at(-1)) : null;
7440
8088
  const response = success({
7441
8089
  data: page,
7442
8090
  cursor: nextCursor
@@ -7444,14 +8092,14 @@ async function getConfigRules(query, chains) {
7444
8092
  response.body.meta.checksum = checksum;
7445
8093
  return response;
7446
8094
  }
7447
- function formatCursor(rule) {
8095
+ function formatCursor$2(rule) {
7448
8096
  if (rule.type === "maturity") return `maturity:${rule.chain_id}:${rule.timestamp}:${rule.name}`;
7449
8097
  if (rule.type === "callback") return `callback:${rule.chain_id}:${rule.callback_type}:${rule.address.toLowerCase()}`;
7450
8098
  if (rule.type === "oracle") return `oracle:${rule.chain_id}:${rule.address.toLowerCase()}`;
7451
8099
  if (rule.type === "collateral_token") return `collateral_token:${rule.chain_id}:${rule.address.toLowerCase()}`;
7452
8100
  return `loan_token:${rule.chain_id}:${rule.address.toLowerCase()}`;
7453
8101
  }
7454
- function parseCursor(cursor) {
8102
+ function parseCursor$2(cursor) {
7455
8103
  const [type, chain, ...rest] = cursor.split(":");
7456
8104
  if (!type || !chain || rest.length === 0) throw new BadRequestError$1("Cursor must be in the format type:chain_id:<value>");
7457
8105
  if (!isConfigRuleType(type)) throw new BadRequestError$1("Cursor has an invalid rule type");
@@ -7744,23 +8392,22 @@ function create$16(parameters) {
7744
8392
  const loanTokenFilter = loanTokens !== void 0 && loanTokens.length > 0 ? sql`(${sql.join(loanTokens.map((token) => sql`LOWER(${obligations.loanToken}) = ${token.toLowerCase()}`), sql` OR `)})` : void 0;
7745
8393
  const collateralFilter = collateralTokens !== void 0 && collateralTokens.length > 0 ? sql`EXISTS (
7746
8394
  SELECT 1 FROM ${obligationCollateralsV2} oc
7747
- WHERE oc.obligation_id = ${obligations.obligationId}
8395
+ WHERE oc.obligation_key = ${obligations.obligationKey}
7748
8396
  AND (${sql.join(collateralTokens.map((token) => sql`LOWER(oc.asset) = ${token.toLowerCase()}`), sql` OR `)})
7749
8397
  )` : void 0;
7750
- const bestAskTick = db.select({ askTick: offers.tick }).from(offers).innerJoin(groups, and(eq(offers.groupChainId, groups.chainId), eq(offers.groupMaker, groups.maker), eq(offers.group, groups.group))).leftJoin(validations, eq(offers.hash, validations.offerHash)).leftJoin(status, eq(validations.statusId, status.id)).where(and(eq(offers.obligationId, obligations.obligationId), eq(offers.buy, false), gte(offers.expiry, now$3), gte(offers.maturity, now$3), lte(offers.start, now$3), sql`(${status.code} IS NULL OR ${status.code} = ${Status.VALID})`)).orderBy(desc(offers.tick)).limit(1).as("best_ask_tick");
7751
- const bestBidTick = db.select({ bidTick: offers.tick }).from(offers).innerJoin(groups, and(eq(offers.groupChainId, groups.chainId), eq(offers.groupMaker, groups.maker), eq(offers.group, groups.group))).leftJoin(validations, eq(offers.hash, validations.offerHash)).leftJoin(status, eq(validations.statusId, status.id)).where(and(eq(offers.obligationId, obligations.obligationId), eq(offers.buy, true), gte(offers.expiry, now$3), gte(offers.maturity, now$3), lte(offers.start, now$3), sql`(${status.code} IS NULL OR ${status.code} = ${Status.VALID})`)).orderBy(asc(offers.tick)).limit(1).as("best_bid_tick");
8398
+ const bestAskTick = db.select({ askTick: offers.tick }).from(offers).innerJoin(groups, and(eq(offers.groupChainId, groups.chainId), eq(offers.groupMaker, groups.maker), eq(offers.group, groups.group))).leftJoin(validations, and(eq(offers.hash, validations.offerHash), eq(offers.obligationId, validations.obligationId))).leftJoin(status, eq(validations.statusId, status.id)).where(and(eq(offers.obligationId, obligationIdKeys.obligationId), eq(offers.buy, false), gte(offers.expiry, now$3), gte(offers.maturity, now$3), lte(offers.start, now$3), sql`(${status.code} IS NULL OR ${status.code} = ${Status.VALID})`)).orderBy(desc(offers.tick)).limit(1).as("best_ask_tick");
8399
+ const bestBidTick = db.select({ bidTick: offers.tick }).from(offers).innerJoin(groups, and(eq(offers.groupChainId, groups.chainId), eq(offers.groupMaker, groups.maker), eq(offers.group, groups.group))).leftJoin(validations, and(eq(offers.hash, validations.offerHash), eq(offers.obligationId, validations.obligationId))).leftJoin(status, eq(validations.statusId, status.id)).where(and(eq(offers.obligationId, obligationIdKeys.obligationId), eq(offers.buy, true), gte(offers.expiry, now$3), gte(offers.maturity, now$3), lte(offers.start, now$3), sql`(${status.code} IS NULL OR ${status.code} = ${Status.VALID})`)).orderBy(asc(offers.tick)).limit(1).as("best_bid_tick");
7752
8400
  const obligationsWithQuotes = db.select({
7753
- obligationId: obligations.obligationId,
7754
- chainId: obligations.chainId,
8401
+ obligationId: obligationIdKeys.obligationId,
8402
+ chainId: obligationIdKeys.chainId,
7755
8403
  loanToken: obligations.loanToken,
7756
- collaterals: sql`ARRAY_AGG(jsonb_build_object('asset', ${obligationCollateralsV2.asset}, 'oracle', ${oracles$1.address}, 'lltv', ${obligationCollateralsV2.lltv}))`.as("collaterals"),
8404
+ collaterals: sql`ARRAY_AGG(jsonb_build_object('asset', ${obligationCollateralsV2.asset}, 'oracle', ${obligationCollateralsV2.oracleAddress}, 'lltv', ${obligationCollateralsV2.lltv}))`.as("collaterals"),
7757
8405
  maturity: obligations.maturity,
7758
8406
  askTick: sql`MAX(${bestAskTick.askTick})`.as("ask_tick"),
7759
8407
  bidTick: sql`MAX(${bestBidTick.bidTick})`.as("bid_tick"),
7760
8408
  ask: sql`COALESCE(MAX(${bestAskTick.askTick}) + 1, 0)`.as("ask"),
7761
8409
  bid: sql`COALESCE(MAX(${bestBidTick.bidTick}) + 1, 0)`.as("bid")
7762
- }).from(obligations).innerJoin(obligationCollateralsV2, eq(obligations.obligationId, obligationCollateralsV2.obligationId)).innerJoin(oracles$1, sql`${obligationCollateralsV2.oracleChainId} = ${oracles$1.chainId}
7763
- AND ${obligationCollateralsV2.oracleAddress} = ${oracles$1.address}`).leftJoinLateral(bestAskTick, sql`true`).leftJoinLateral(bestBidTick, sql`true`).groupBy(obligations.obligationId).where(and(ids !== void 0 && ids.length > 0 ? inArray(obligations.obligationId, ids) : void 0, chainIds !== void 0 && chainIds.length > 0 ? inArray(obligations.chainId, chainIds) : void 0, loanTokenFilter, maturities !== void 0 && maturities.length > 0 ? inArray(obligations.maturity, maturities) : gte(obligations.maturity, now$3), collateralFilter)).as("obligations_with_quotes");
8410
+ }).from(obligationIdKeys).innerJoin(obligations, eq(obligationIdKeys.obligationKey, obligations.obligationKey)).innerJoin(obligationCollateralsV2, eq(obligations.obligationKey, obligationCollateralsV2.obligationKey)).leftJoinLateral(bestAskTick, sql`true`).leftJoinLateral(bestBidTick, sql`true`).groupBy(obligationIdKeys.obligationId, obligationIdKeys.chainId, obligations.loanToken, obligations.maturity).where(and(ids !== void 0 && ids.length > 0 ? inArray(obligationIdKeys.obligationId, ids) : void 0, chainIds !== void 0 && chainIds.length > 0 ? inArray(obligationIdKeys.chainId, chainIds) : void 0, loanTokenFilter, maturities !== void 0 && maturities.length > 0 ? inArray(obligations.maturity, maturities) : gte(obligations.maturity, now$3), collateralFilter)).as("obligations_with_quotes");
7764
8411
  const sortColumns = {
7765
8412
  id: obligationsWithQuotes.obligationId,
7766
8413
  ask: obligationsWithQuotes.ask,
@@ -7780,22 +8427,25 @@ function create$16(parameters) {
7780
8427
  }).from(obligationsWithQuotes).where(buildCursorFilter(sortColumns, sort, cursorValues)).orderBy(...buildOrderBy(sortColumns, sort)).limit(limit + 1);
7781
8428
  const hasMore = rows.length > limit;
7782
8429
  const listedRows = (hasMore ? rows.slice(0, limit) : rows).map((row) => {
7783
- return {
7784
- obligation: from$15({
7785
- chainId: row.chainId,
7786
- loanToken: row.loanToken,
7787
- collaterals: row.collaterals.sort((left, right) => left.asset.localeCompare(right.asset)).map((collateral) => from$17({
7788
- asset: collateral.asset,
7789
- oracle: collateral.oracle,
7790
- lltv: from$18(BigInt(collateral.lltv))
7791
- })),
7792
- maturity: row.maturity
7793
- }),
7794
- quote: from$11({
7795
- obligationId: row.obligationId,
7796
- ask: { tick: row.askTick },
7797
- bid: { tick: row.bidTick }
7798
- }),
8430
+ const obligation = from$15({
8431
+ loanToken: row.loanToken,
8432
+ collaterals: row.collaterals.sort((left, right) => left.asset.localeCompare(right.asset)).map((collateral) => from$17({
8433
+ asset: collateral.asset,
8434
+ oracle: collateral.oracle,
8435
+ lltv: from$18(BigInt(collateral.lltv))
8436
+ })),
8437
+ maturity: row.maturity
8438
+ });
8439
+ const quote = from$11({
8440
+ obligationId: row.obligationId,
8441
+ ask: { tick: row.askTick },
8442
+ bid: { tick: row.bidTick }
8443
+ });
8444
+ return {
8445
+ obligationId: row.obligationId,
8446
+ chainId: row.chainId,
8447
+ obligation,
8448
+ quote,
7799
8449
  cursorValues: {
7800
8450
  id: row.obligationId,
7801
8451
  ask: toBigInt(row.ask),
@@ -7813,6 +8463,8 @@ function create$16(parameters) {
7813
8463
  }) : null;
7814
8464
  return {
7815
8465
  obligations: listedRows.map((row) => ({
8466
+ obligationId: row.obligationId,
8467
+ chainId: row.chainId,
7816
8468
  obligation: row.obligation,
7817
8469
  quote: row.quote
7818
8470
  })),
@@ -7950,7 +8602,7 @@ async function getObligation(params, db) {
7950
8602
  if (listing.obligations.length === 0) return failure(new NotFoundError("Obligation not found"));
7951
8603
  const obligation = listing.obligations[0];
7952
8604
  return success({
7953
- data: from$4(obligation.obligation, obligation.quote),
8605
+ data: from$4(obligation.obligation, obligation.quote, obligation.chainId),
7954
8606
  cursor: null
7955
8607
  });
7956
8608
  } catch (err) {
@@ -7991,7 +8643,7 @@ async function getObligations$1(queryParameters, db) {
7991
8643
  limit: query.limit
7992
8644
  });
7993
8645
  return success({
7994
- data: listing.obligations.map((item) => from$4(item.obligation, item.quote)),
8646
+ data: listing.obligations.map((item) => from$4(item.obligation, item.quote, item.chainId)),
7995
8647
  cursor: listing.nextCursor
7996
8648
  });
7997
8649
  } catch (err) {
@@ -8029,10 +8681,10 @@ function create$15(config) {
8029
8681
  return {
8030
8682
  create: async (batches) => {
8031
8683
  if (batches.length === 0) return [];
8032
- const offersRows = batches.flatMap(({ blockNumber, offers }) => offers.map((offer) => ({
8684
+ const offersRows = batches.flatMap(({ blockNumber, offers }) => offers.map(({ offer, obligationId, chainId }) => ({
8033
8685
  ...serialize(offer),
8034
- obligationId: obligationId(offer),
8035
- groupChainId: offer.chainId,
8686
+ obligationId: obligationId.toLowerCase(),
8687
+ groupChainId: chainId,
8036
8688
  groupMaker: offer.maker.toLowerCase(),
8037
8689
  callbackAddress: offer.callback.address.toLowerCase(),
8038
8690
  callbackData: offer.callback.data,
@@ -8042,48 +8694,67 @@ function create$15(config) {
8042
8694
  if (offersRows.length === 0) return [];
8043
8695
  try {
8044
8696
  return await db.transaction(async (dbTx) => {
8045
- const selectExisting = async (hashes) => {
8046
- if (hashes.length === 0) return /* @__PURE__ */ new Set();
8697
+ const keyOf = (input) => `${input.hash.toLowerCase()}:${input.obligationId.toLowerCase()}`;
8698
+ const selectExisting = async (offers$1) => {
8699
+ if (offers$1.length === 0) return /* @__PURE__ */ new Set();
8700
+ const expected = new Set(offers$1.map((offer) => keyOf({
8701
+ hash: offer.hash,
8702
+ obligationId: offer.obligationId
8703
+ })));
8047
8704
  const existing = /* @__PURE__ */ new Set();
8705
+ const hashes = [...new Set(offers$1.map((offer) => offer.hash.toLowerCase()))];
8048
8706
  for (const batch of batch$1(hashes, DEFAULT_BATCH_SIZE$1)) {
8049
- const rows = await dbTx.select({ hash: offers.hash }).from(offers).where(inArray(offers.hash, batch));
8050
- for (const row of rows) existing.add(String(row.hash).toLowerCase());
8707
+ const rows = await dbTx.select({
8708
+ hash: offers.hash,
8709
+ obligationId: offers.obligationId
8710
+ }).from(offers).where(inArray(offers.hash, batch));
8711
+ for (const row of rows) {
8712
+ const key = keyOf({
8713
+ hash: String(row.hash),
8714
+ obligationId: String(row.obligationId)
8715
+ });
8716
+ if (expected.has(key)) existing.add(key);
8717
+ }
8051
8718
  }
8052
8719
  return existing;
8053
8720
  };
8054
8721
  const inserted = [];
8055
8722
  for (const batch of batch$1(offersRows, DEFAULT_BATCH_SIZE$1)) {
8056
8723
  const rows = await dbTx.insert(offers).values(batch).onConflictDoNothing().returning();
8057
- inserted.push(...rows.map((row) => row.hash));
8724
+ inserted.push(...rows.map((row) => ({
8725
+ hash: row.hash,
8726
+ obligationId: row.obligationId
8727
+ })));
8058
8728
  }
8059
8729
  const existing = await selectExisting(inserted);
8060
- return inserted.filter((hash) => existing.has(hash));
8730
+ return inserted.filter((offer) => existing.has(keyOf({
8731
+ hash: offer.hash,
8732
+ obligationId: offer.obligationId
8733
+ })));
8061
8734
  });
8062
8735
  } catch (err) {
8063
8736
  const error = err instanceof Error ? err : new Error(String(err));
8064
- throw new Error("Offers.create failed. Ensure obligations and groups exist before inserting offers.", { cause: error });
8737
+ throw new Error("Offers.create failed. Ensure obligation id keys and groups exist before inserting offers.", { cause: error });
8065
8738
  }
8066
8739
  },
8067
8740
  get: async (parameters) => {
8068
8741
  const limit = parameters?.limit ?? DEFAULT_LIMIT$3;
8069
- const cursor = parameters?.cursor;
8742
+ const rawCursor = parameters?.cursor;
8070
8743
  const maker = parameters?.maker;
8071
- if (cursor !== null && cursor !== void 0) {
8072
- if (!cursor.startsWith("0x") || cursor.length !== 66) throw new Error("Invalid cursor format");
8073
- }
8744
+ const cursor = rawCursor !== null && rawCursor !== void 0 ? parseCursor$1(rawCursor) : void 0;
8074
8745
  const collateralsLateral = db.select({ collaterals: sql`COALESCE(
8075
8746
  jsonb_agg(
8076
8747
  jsonb_build_object(
8077
8748
  'asset', ${obligationCollateralsV2.asset},
8078
- 'oracle', ${oracles$1.address},
8749
+ 'oracle', ${obligationCollateralsV2.oracleAddress},
8079
8750
  'lltv', ${obligationCollateralsV2.lltv}
8080
8751
  )
8081
8752
  ),
8082
8753
  '[]'::jsonb
8083
- )`.as("collaterals") }).from(obligationCollateralsV2).innerJoin(oracles$1, sql`${obligationCollateralsV2.oracleChainId} = ${oracles$1.chainId}
8084
- AND ${obligationCollateralsV2.oracleAddress} = ${oracles$1.address}`).where(eq(obligationCollateralsV2.obligationId, offers.obligationId)).as("collaterals_lateral");
8754
+ )`.as("collaterals") }).from(obligationCollateralsV2).where(eq(obligationCollateralsV2.obligationKey, obligationIdKeys.obligationKey)).as("collaterals_lateral");
8085
8755
  const rows = (await db.select({
8086
8756
  hash: offers.hash,
8757
+ obligationId: offers.obligationId,
8087
8758
  maker: offers.groupMaker,
8088
8759
  assets: offers.assets,
8089
8760
  obligationUnits: offers.obligationUnits,
@@ -8095,17 +8766,18 @@ function create$15(config) {
8095
8766
  group: offers.group,
8096
8767
  session: offers.session,
8097
8768
  buy: offers.buy,
8098
- chainId: obligations.chainId,
8769
+ chainId: obligationIdKeys.chainId,
8099
8770
  loanToken: obligations.loanToken,
8100
8771
  callbackAddress: offers.callbackAddress,
8101
8772
  callbackData: offers.callbackData,
8102
8773
  receiverIfMakerIsSeller: offers.receiverIfMakerIsSeller,
8103
8774
  collaterals: collateralsLateral.collaterals,
8104
8775
  blockNumber: offers.blockNumber
8105
- }).from(offers).innerJoin(obligations, eq(offers.obligationId, obligations.obligationId)).innerJoinLateral(collateralsLateral, sql`true`).where(and(cursor !== null && cursor !== void 0 ? gt(offers.hash, cursor) : void 0, maker !== void 0 ? eq(offers.groupMaker, maker.toLowerCase()) : void 0)).orderBy(asc(offers.hash)).limit(limit)).map((row) => {
8776
+ }).from(offers).innerJoin(obligationIdKeys, eq(offers.obligationId, obligationIdKeys.obligationId)).innerJoin(obligations, eq(obligationIdKeys.obligationKey, obligations.obligationKey)).innerJoinLateral(collateralsLateral, sql`true`).where(and(cursor !== void 0 ? or(gt(offers.hash, cursor.hash), and(eq(offers.hash, cursor.hash), gt(offers.obligationId, cursor.obligationId))) : void 0, maker !== void 0 ? eq(offers.groupMaker, maker.toLowerCase()) : void 0)).orderBy(asc(offers.hash), asc(offers.obligationId)).limit(limit)).map((row) => {
8106
8777
  const receiverIfMakerIsSeller = (row.receiverIfMakerIsSeller ?? row.maker).toLowerCase();
8107
8778
  return {
8108
8779
  hash: row.hash,
8780
+ obligationId: row.obligationId,
8109
8781
  maker: row.maker,
8110
8782
  assets: BigInt(row.assets),
8111
8783
  obligationUnits: BigInt(row.obligationUnits),
@@ -8137,7 +8809,10 @@ function create$15(config) {
8137
8809
  });
8138
8810
  return {
8139
8811
  rows,
8140
- nextCursor: rows.length === limit ? rows[rows.length - 1].hash : null
8812
+ nextCursor: rows.length === limit ? formatCursor$1({
8813
+ hash: rows[rows.length - 1].hash,
8814
+ obligationId: rows[rows.length - 1].obligationId
8815
+ }) : null
8141
8816
  };
8142
8817
  },
8143
8818
  delete: async (parameters) => {
@@ -8160,7 +8835,7 @@ function create$15(config) {
8160
8835
  const query = ({ side }) => db.selectDistinctOn([offers.obligationId], {
8161
8836
  obligationId: offers.obligationId,
8162
8837
  tick: offers.tick
8163
- }).from(offers).innerJoin(groups, and(eq(offers.groupChainId, groups.chainId), eq(offers.groupMaker, groups.maker), eq(offers.group, groups.group))).leftJoin(validations, eq(offers.hash, validations.offerHash)).leftJoin(status, eq(validations.statusId, status.id)).where(and(inArray(offers.obligationId, obligationIds), eq(offers.buy, side === "buy"), gte(offers.expiry, now$2), gte(offers.maturity, now$2), lte(offers.start, now$2), sql`(${status.code} IS NULL OR ${status.code} = ${Status.VALID})`)).orderBy(offers.obligationId, side === "buy" ? sql`${offers.tick} ASC` : sql`${offers.tick} DESC`);
8838
+ }).from(offers).innerJoin(groups, and(eq(offers.groupChainId, groups.chainId), eq(offers.groupMaker, groups.maker), eq(offers.group, groups.group))).leftJoin(validations, and(eq(offers.hash, validations.offerHash), eq(offers.obligationId, validations.obligationId))).leftJoin(status, eq(validations.statusId, status.id)).where(and(inArray(offers.obligationId, obligationIds), eq(offers.buy, side === "buy"), gte(offers.expiry, now$2), gte(offers.maturity, now$2), lte(offers.start, now$2), sql`(${status.code} IS NULL OR ${status.code} = ${Status.VALID})`)).orderBy(offers.obligationId, side === "buy" ? sql`${offers.tick} ASC` : sql`${offers.tick} DESC`);
8164
8839
  const [bestBuys, bestSells] = await Promise.all([query({ side: "buy" }), query({ side: "sell" })]);
8165
8840
  const quotes = /* @__PURE__ */ new Map();
8166
8841
  for (const row of bestSells) quotes.set(row.obligationId, {
@@ -8190,6 +8865,134 @@ function create$15(config) {
8190
8865
  }
8191
8866
  };
8192
8867
  }
8868
+ const HEX_32$2 = /^0x[a-fA-F0-9]{64}$/;
8869
+ function parseCursor$1(cursor) {
8870
+ const [hash, obligationId] = cursor.split(":");
8871
+ if (!hash || !obligationId || !HEX_32$2.test(hash) || !HEX_32$2.test(obligationId)) throw new Error("Invalid cursor format");
8872
+ return {
8873
+ hash: hash.toLowerCase(),
8874
+ obligationId: obligationId.toLowerCase()
8875
+ };
8876
+ }
8877
+ function formatCursor$1(input) {
8878
+ return `${input.hash.toLowerCase()}:${input.obligationId.toLowerCase()}`;
8879
+ }
8880
+
8881
+ //#endregion
8882
+ //#region src/database/domains/Trees.ts
8883
+ /**
8884
+ * Creates a Trees domain instance for managing merkle tree metadata.
8885
+ *
8886
+ * @param config - Configuration with database instance
8887
+ * @returns TreesDomain instance
8888
+ */
8889
+ function create$14(config) {
8890
+ const db = config.db;
8891
+ return {
8892
+ upsert: async (trees$1) => {
8893
+ if (trees$1.length === 0) return [];
8894
+ try {
8895
+ return await db.transaction(async (dbTx) => {
8896
+ const roots = [];
8897
+ for (const { root, signature } of trees$1) {
8898
+ const normalizedRoot = root.toLowerCase();
8899
+ const normalizedSignature = signature.toLowerCase();
8900
+ roots.push(normalizedRoot);
8901
+ await dbTx.insert(trees).values({
8902
+ root: normalizedRoot,
8903
+ rootSignature: normalizedSignature
8904
+ }).onConflictDoUpdate({
8905
+ target: [trees.root],
8906
+ set: {
8907
+ rootSignature: normalizedSignature,
8908
+ createdAt: sql`NOW()`
8909
+ }
8910
+ });
8911
+ }
8912
+ return roots;
8913
+ });
8914
+ } catch (err) {
8915
+ const error = err instanceof Error ? err : new Error(String(err));
8916
+ throw new Error("Trees.upsert failed. Ensure obligations and offers exist before upserting roots.", { cause: error });
8917
+ }
8918
+ },
8919
+ upsertPaths: async (paths) => {
8920
+ if (paths.length === 0) return;
8921
+ const rows = paths.map((path) => ({
8922
+ offerHash: path.offerHash.toLowerCase(),
8923
+ obligationId: path.obligationId.toLowerCase(),
8924
+ treeRoot: path.treeRoot.toLowerCase(),
8925
+ proofNodes: concatenateProofs(path.proof)
8926
+ }));
8927
+ try {
8928
+ for (const batch of batch$1(rows, DEFAULT_BATCH_SIZE$1)) await db.insert(merklePaths).values(batch).onConflictDoUpdate({
8929
+ target: [merklePaths.offerHash, merklePaths.obligationId],
8930
+ set: {
8931
+ treeRoot: sql`excluded.tree_root`,
8932
+ proofNodes: sql`excluded.proof_nodes`,
8933
+ createdAt: sql`NOW()`
8934
+ }
8935
+ });
8936
+ } catch (err) {
8937
+ const error = err instanceof Error ? err : new Error(String(err));
8938
+ throw new Error("Trees.upsertPaths failed. Ensure offers and roots exist before inserting merkle paths.", { cause: error });
8939
+ }
8940
+ },
8941
+ getAttestations: async (references) => {
8942
+ if (references.length === 0) return /* @__PURE__ */ new Map();
8943
+ const normalizedReferences = references.map((reference) => ({
8944
+ offerHash: reference.offerHash.toLowerCase(),
8945
+ obligationId: reference.obligationId.toLowerCase()
8946
+ }));
8947
+ const hashes = [...new Set(normalizedReferences.map((reference) => reference.offerHash))];
8948
+ const obligationIds = [...new Set(normalizedReferences.map((reference) => reference.obligationId))];
8949
+ const results = await db.select({
8950
+ offerHash: merklePaths.offerHash,
8951
+ obligationId: merklePaths.obligationId,
8952
+ treeRoot: merklePaths.treeRoot,
8953
+ proofNodes: merklePaths.proofNodes,
8954
+ rootSignature: trees.rootSignature
8955
+ }).from(merklePaths).innerJoin(trees, eq(merklePaths.treeRoot, trees.root)).where(sql`${inArray(merklePaths.offerHash, hashes)} AND ${inArray(merklePaths.obligationId, obligationIds)}`);
8956
+ const expectedKeys = new Set(normalizedReferences.map(toAttestationKey));
8957
+ const attestationMap = /* @__PURE__ */ new Map();
8958
+ for (const row of results) {
8959
+ const key = toAttestationKey({
8960
+ offerHash: row.offerHash,
8961
+ obligationId: row.obligationId
8962
+ });
8963
+ if (!expectedKeys.has(key)) continue;
8964
+ attestationMap.set(key, {
8965
+ root: row.treeRoot.toLowerCase(),
8966
+ signature: row.rootSignature.toLowerCase(),
8967
+ proof: splitProofs(row.proofNodes)
8968
+ });
8969
+ }
8970
+ return attestationMap;
8971
+ }
8972
+ };
8973
+ }
8974
+ /**
8975
+ * Concatenates an array of 32-byte hex hashes into a single hex string.
8976
+ * Empty arrays return "0x".
8977
+ */
8978
+ function concatenateProofs(proofs) {
8979
+ if (proofs.length === 0) return "0x";
8980
+ return `0x${proofs.map((proof) => proof.toLowerCase().slice(2)).join("")}`;
8981
+ }
8982
+ /**
8983
+ * Splits a concatenated hex string back into an array of 32-byte hex hashes.
8984
+ * Returns empty array for "0x" or empty string.
8985
+ */
8986
+ function splitProofs(concatenated) {
8987
+ if (!concatenated || concatenated === "0x" || concatenated.length <= 2) return [];
8988
+ const hex = concatenated.slice(2);
8989
+ const proofs = [];
8990
+ for (let i = 0; i < hex.length; i += 64) proofs.push(`0x${hex.slice(i, i + 64).toLowerCase()}`);
8991
+ return proofs;
8992
+ }
8993
+ function toAttestationKey(offer) {
8994
+ return `${offer.offerHash.toLowerCase()}:${offer.obligationId.toLowerCase()}`;
8995
+ }
8193
8996
 
8194
8997
  //#endregion
8195
8998
  //#region src/api/Controllers/getOffers.ts
@@ -8201,23 +9004,20 @@ function create$15(config) {
8201
9004
  */
8202
9005
  async function getOffersQuery(db, parameters) {
8203
9006
  const limit = parameters?.limit ?? DEFAULT_LIMIT$3;
8204
- const cursor = parameters?.cursor;
9007
+ const rawCursor = parameters?.cursor;
8205
9008
  const maker = parameters?.maker;
8206
- if (cursor !== null && cursor !== void 0) {
8207
- if (!cursor.startsWith("0x") || cursor.length !== 66) throw new Error("Invalid cursor format");
8208
- }
9009
+ const cursor = maker && rawCursor !== void 0 && rawCursor !== null ? parseMakerCursor(rawCursor) : void 0;
8209
9010
  const now = Math.floor((Date.now() - 1) / 1e3);
8210
9011
  const collateralsLateral = db.select({ collaterals: sql`COALESCE(
8211
9012
  jsonb_agg(
8212
9013
  jsonb_build_object(
8213
9014
  'asset', ${obligationCollateralsV2.asset},
8214
- 'oracle', ${oracles$1.address},
9015
+ 'oracle', ${obligationCollateralsV2.oracleAddress},
8215
9016
  'lltv', ${obligationCollateralsV2.lltv}
8216
9017
  )
8217
9018
  ),
8218
9019
  '[]'::jsonb
8219
- )`.as("collaterals") }).from(obligationCollateralsV2).innerJoin(oracles$1, sql`${obligationCollateralsV2.oracleChainId} = ${oracles$1.chainId}
8220
- AND ${obligationCollateralsV2.oracleAddress} = ${oracles$1.address}`).where(eq(obligationCollateralsV2.obligationId, offers.obligationId)).as("collaterals_lateral");
9020
+ )`.as("collaterals") }).from(obligationCollateralsV2).where(eq(obligationCollateralsV2.obligationKey, obligationIdKeys.obligationKey)).as("collaterals_lateral");
8221
9021
  const lotBalanceExpr = sql`GREATEST(0, LEAST(
8222
9022
  COALESCE(${positions.balance}, 0)::numeric
8223
9023
  + COALESCE((
@@ -8262,6 +9062,7 @@ async function getOffersQuery(db, parameters) {
8262
9062
  AND LOWER(${lots.user}) = LOWER(${callbacks.positionUser})
8263
9063
  AND LOWER(${lots.group}) = LOWER(${offers.group})
8264
9064
  WHERE ${offersCallbacks.offerHash} = ${offers.hash}
9065
+ AND ${offersCallbacks.obligationId} = ${offers.obligationId}
8265
9066
  ORDER BY
8266
9067
  ${callbacks.positionChainId},
8267
9068
  LOWER(${callbacks.positionContract}),
@@ -8271,6 +9072,7 @@ async function getOffersQuery(db, parameters) {
8271
9072
  ), 0)`;
8272
9073
  const rows = (await db.select({
8273
9074
  hash: offers.hash,
9075
+ obligationId: offers.obligationId,
8274
9076
  maker: offers.groupMaker,
8275
9077
  assets: offers.assets,
8276
9078
  obligationUnits: offers.obligationUnits,
@@ -8283,7 +9085,7 @@ async function getOffersQuery(db, parameters) {
8283
9085
  group: offers.group,
8284
9086
  session: offers.session,
8285
9087
  buy: offers.buy,
8286
- chainId: obligations.chainId,
9088
+ chainId: obligationIdKeys.chainId,
8287
9089
  loanToken: obligations.loanToken,
8288
9090
  callbackAddress: offers.callbackAddress,
8289
9091
  callbackData: offers.callbackData,
@@ -8300,7 +9102,7 @@ async function getOffersQuery(db, parameters) {
8300
9102
  )
8301
9103
  END
8302
9104
  ))`.as("takeable")
8303
- }).from(offers).innerJoin(obligations, eq(offers.obligationId, obligations.obligationId)).innerJoin(groups, and(eq(offers.groupChainId, groups.chainId), eq(offers.groupMaker, groups.maker), eq(offers.group, groups.group))).innerJoinLateral(collateralsLateral, sql`true`).where(and(cursor !== null && cursor !== void 0 ? gt(offers.hash, cursor) : void 0, maker !== void 0 ? eq(offers.groupMaker, maker.toLowerCase()) : void 0, gte(offers.expiry, now), gte(offers.maturity, now), maker === void 0 ? sql`GREATEST(0,
9105
+ }).from(offers).innerJoin(obligationIdKeys, eq(offers.obligationId, obligationIdKeys.obligationId)).innerJoin(obligations, eq(obligationIdKeys.obligationKey, obligations.obligationKey)).innerJoin(groups, and(eq(offers.groupChainId, groups.chainId), eq(offers.groupMaker, groups.maker), eq(offers.group, groups.group))).innerJoinLateral(collateralsLateral, sql`true`).where(and(cursor !== void 0 ? cursor.obligationId === void 0 ? gt(offers.hash, cursor.hash) : or(gt(offers.hash, cursor.hash), and(eq(offers.hash, cursor.hash), gt(offers.obligationId, cursor.obligationId))) : void 0, maker !== void 0 ? eq(offers.groupMaker, maker.toLowerCase()) : void 0, gte(offers.expiry, now), gte(offers.maturity, now), maker === void 0 ? sql`GREATEST(0,
8304
9106
  CASE WHEN ${offers.buy} = false
8305
9107
  THEN ${offers.assets}::numeric - ${groups.consumed}::numeric
8306
9108
  ELSE LEAST(
@@ -8308,10 +9110,11 @@ async function getOffersQuery(db, parameters) {
8308
9110
  ${availableExpr}::numeric
8309
9111
  )
8310
9112
  END
8311
- ) > 0` : void 0)).orderBy(asc(offers.hash)).limit(limit)).map((row) => {
9113
+ ) > 0` : void 0)).orderBy(asc(offers.hash), asc(offers.obligationId)).limit(limit)).map((row) => {
8312
9114
  const receiverIfMakerIsSeller = (row.receiverIfMakerIsSeller ?? row.maker).toLowerCase();
8313
9115
  return {
8314
9116
  hash: row.hash,
9117
+ obligationId: row.obligationId,
8315
9118
  maker: row.maker,
8316
9119
  assets: BigInt(row.assets),
8317
9120
  obligationUnits: BigInt(row.obligationUnits),
@@ -8343,7 +9146,7 @@ async function getOffersQuery(db, parameters) {
8343
9146
  });
8344
9147
  return {
8345
9148
  rows,
8346
- nextCursor: rows.length === limit ? rows[rows.length - 1].hash : null
9149
+ nextCursor: rows.length === limit ? maker === void 0 ? rows[rows.length - 1].hash : formatOfferCursor(rows[rows.length - 1]) : null
8347
9150
  };
8348
9151
  }
8349
9152
  async function getOffers$1(queryParameters, db) {
@@ -8362,12 +9165,18 @@ async function getOffers$1(queryParameters, db) {
8362
9165
  cursor: query.cursor,
8363
9166
  limit: query.limit
8364
9167
  });
8365
- const hashes = rows.map((row) => row.hash);
8366
- const attestationMap = await db.trees.getAttestations(hashes);
9168
+ const offers = rows.map((row) => ({
9169
+ offerHash: row.hash,
9170
+ obligationId: row.obligationId
9171
+ }));
9172
+ const attestationMap = await db.trees.getAttestations(offers);
8367
9173
  return success({
8368
9174
  data: rows.map((row) => {
8369
- const hash$2 = hash(row);
8370
- const attestation = attestationMap.get(hash$2);
9175
+ const key = toAttestationKey({
9176
+ offerHash: row.hash,
9177
+ obligationId: row.obligationId
9178
+ });
9179
+ const attestation = attestationMap.get(key);
8371
9180
  return from$3({
8372
9181
  ...row,
8373
9182
  ...attestation
@@ -8385,6 +9194,19 @@ async function getOffers$1(queryParameters, db) {
8385
9194
  return failure(err);
8386
9195
  }
8387
9196
  }
9197
+ function parseMakerCursor(cursor) {
9198
+ const [rawHash, rawObligationId, ...tail] = cursor.split(":");
9199
+ if (tail.length > 0) throw new Error("Invalid cursor format");
9200
+ if (!rawHash || !rawObligationId || !HEX_32$1.test(rawHash) || !HEX_32$1.test(rawObligationId)) throw new Error("Invalid cursor format");
9201
+ return {
9202
+ hash: rawHash.toLowerCase(),
9203
+ obligationId: rawObligationId.toLowerCase()
9204
+ };
9205
+ }
9206
+ function formatOfferCursor(row) {
9207
+ return `${row.hash.toLowerCase()}:${row.obligationId.toLowerCase()}`;
9208
+ }
9209
+ const HEX_32$1 = /^0x[a-fA-F0-9]{64}$/;
8388
9210
 
8389
9211
  //#endregion
8390
9212
  //#region src/api/Controllers/getUserPositions.ts
@@ -8426,7 +9248,8 @@ async function validateOffers(body, gatekeeper) {
8426
9248
  const logger = getLogger();
8427
9249
  const result = safeParse("validate_offers", body, (issue) => issue.message);
8428
9250
  if (!result.success) return failure(new BadRequestError$1(result.error.issues[0]?.message ?? "Invalid request body"));
8429
- const { offers: rawOffers } = result.data;
9251
+ const { offers: rawOffers, chain_id: rawChainId } = result.data;
9252
+ const chainId = rawChainId;
8430
9253
  const parsedOffers = [];
8431
9254
  const offerIndexByHash = /* @__PURE__ */ new Map();
8432
9255
  for (let i = 0; i < rawOffers.length; i++) {
@@ -8445,7 +9268,10 @@ async function validateOffers(body, gatekeeper) {
8445
9268
  }
8446
9269
  }
8447
9270
  try {
8448
- const { issues } = await gatekeeper.isAllowed(parsedOffers);
9271
+ const { issues } = await gatekeeper.isAllowed({
9272
+ offers: parsedOffers,
9273
+ chainId
9274
+ });
8449
9275
  if (issues.length > 0) {
8450
9276
  const mappedIssues = issues.map((issue) => {
8451
9277
  const index = offerIndexByHash.get(hash(issue.item));
@@ -8506,14 +9332,14 @@ var Controllers_exports = /* @__PURE__ */ __exportAll({
8506
9332
  //#region src/api/Api.ts
8507
9333
  function from$1(config) {
8508
9334
  const { db, gatekeeper, port, chainRegistry } = config;
8509
- return create$14({
9335
+ return create$13({
8510
9336
  port,
8511
9337
  db,
8512
9338
  gatekeeper,
8513
9339
  chainRegistry
8514
9340
  });
8515
9341
  }
8516
- function create$14(params) {
9342
+ function create$13(params) {
8517
9343
  return { serve: () => serve$1(params) };
8518
9344
  }
8519
9345
  /**
@@ -8640,7 +9466,7 @@ var RouterApi_exports = /* @__PURE__ */ __exportAll({
8640
9466
  RouterStatusResponse: () => RouterStatusResponse,
8641
9467
  UsersController: () => UsersController,
8642
9468
  ValidateController: () => ValidateController,
8643
- create: () => create$14,
9469
+ create: () => create$13,
8644
9470
  from: () => from$1,
8645
9471
  parse: () => parse,
8646
9472
  safeParse: () => safeParse
@@ -8723,7 +9549,6 @@ async function getOffers(apiClient, parameters) {
8723
9549
  group: offerData.group,
8724
9550
  session: offerData.session,
8725
9551
  buy: offerData.buy,
8726
- chain_id: item.chain_id,
8727
9552
  loan_token: offerData.obligation.loan_token,
8728
9553
  collaterals: offerData.obligation.collaterals.map((collateral) => ({
8729
9554
  asset: collateral.token,
@@ -8771,7 +9596,6 @@ async function getObligations(apiClient, parameters) {
8771
9596
  }
8772
9597
  const obligations = data?.data.map((item) => {
8773
9598
  const obligation = fromSnakeCase$2({
8774
- chain_id: item.chain_id,
8775
9599
  loan_token: item.loan_token,
8776
9600
  collaterals: item.collaterals.map((collateral) => ({
8777
9601
  asset: collateral.token,
@@ -8780,16 +9604,17 @@ async function getObligations(apiClient, parameters) {
8780
9604
  })),
8781
9605
  maturity: from$16(item.maturity)
8782
9606
  });
8783
- const { obligationId: _, ...returned } = {
8784
- id: () => id(obligation),
9607
+ const { obligationId: _, ...quote } = from$11({
9608
+ obligationId: item.id,
9609
+ ask: { tick: item.ask.tick },
9610
+ bid: { tick: item.bid.tick }
9611
+ });
9612
+ return {
9613
+ id: item.id,
9614
+ chainId: item.chain_id,
8785
9615
  ...obligation,
8786
- ...from$11({
8787
- obligationId: item.id,
8788
- ask: { tick: item.ask.tick },
8789
- bid: { tick: item.bid.tick }
8790
- })
9616
+ ...quote
8791
9617
  };
8792
- return returned;
8793
9618
  }) ?? [];
8794
9619
  return {
8795
9620
  cursor: data?.cursor ?? null,
@@ -8836,13 +9661,15 @@ var drizzle_exports = /* @__PURE__ */ __exportAll({
8836
9661
  VERSION: () => VERSION,
8837
9662
  VERSIONED_TABLE_NAMES: () => VERSIONED_TABLE_NAMES,
8838
9663
  callbacks: () => callbacks,
8839
- chains: () => chains$1,
9664
+ chains: () => chains,
8840
9665
  collectors: () => collectors,
8841
9666
  consumedEvents: () => consumedEvents,
8842
9667
  groups: () => groups,
8843
9668
  lots: () => lots,
9669
+ lotsPositions: () => lotsPositions,
8844
9670
  merklePaths: () => merklePaths,
8845
9671
  obligationCollateralsV2: () => obligationCollateralsV2,
9672
+ obligationIdKeys: () => obligationIdKeys,
8846
9673
  obligations: () => obligations,
8847
9674
  offers: () => offers,
8848
9675
  offersCallbacks: () => offersCallbacks,
@@ -8859,14 +9686,14 @@ var drizzle_exports = /* @__PURE__ */ __exportAll({
8859
9686
  //#endregion
8860
9687
  //#region src/database/domains/Blocks.ts
8861
9688
  /** Postgres implementation. */
8862
- const create$13 = (config) => {
9689
+ const create$12 = (config) => {
8863
9690
  const { db, chainRegistry } = config;
8864
9691
  const getChain = async (chainId) => {
8865
9692
  const rows = await db.select({
8866
- chainId: chains$1.chainId,
8867
- blockNumber: chains$1.blockNumber,
8868
- epoch: chains$1.epoch
8869
- }).from(chains$1).where(eq(chains$1.chainId, chainId)).limit(1);
9693
+ chainId: chains.chainId,
9694
+ blockNumber: chains.blockNumber,
9695
+ epoch: chains.epoch
9696
+ }).from(chains).where(eq(chains.chainId, chainId)).limit(1);
8870
9697
  if (rows.length === 0) throw new Error(`Chain state not initialized for chain ${chainId}. Call blocks.init first.`);
8871
9698
  const row = rows[0];
8872
9699
  return {
@@ -8894,11 +9721,11 @@ const create$13 = (config) => {
8894
9721
  };
8895
9722
  const getChains = async (parameters) => {
8896
9723
  return (await db.select({
8897
- chainId: chains$1.chainId,
8898
- blockNumber: chains$1.blockNumber,
8899
- epoch: chains$1.epoch,
8900
- updatedAt: chains$1.updatedAt
8901
- }).from(chains$1).where(parameters?.chainId !== void 0 ? eq(chains$1.chainId, parameters.chainId) : sql`TRUE`).orderBy(asc(chains$1.chainId))).map((row) => ({
9724
+ chainId: chains.chainId,
9725
+ blockNumber: chains.blockNumber,
9726
+ epoch: chains.epoch,
9727
+ updatedAt: chains.updatedAt
9728
+ }).from(chains).where(parameters?.chainId !== void 0 ? eq(chains.chainId, parameters.chainId) : sql`TRUE`).orderBy(asc(chains.chainId))).map((row) => ({
8902
9729
  chainId: row.chainId,
8903
9730
  blockNumber: Number(row.blockNumber),
8904
9731
  epoch: BigInt(row.epoch),
@@ -8924,14 +9751,14 @@ const create$13 = (config) => {
8924
9751
  const name = parameters.collectorName.toLowerCase();
8925
9752
  const deploymentBlock = chainRegistry.getById(parameters.chainId)?.custom?.mempool.blockCreated ?? 0;
8926
9753
  const { chain, collector } = await db.transaction(async (dbTx) => {
8927
- const chainRows = await dbTx.insert(chains$1).values({
9754
+ const chainRows = await dbTx.insert(chains).values({
8928
9755
  chainId: parameters.chainId,
8929
9756
  blockNumber: deploymentBlock
8930
9757
  }).onConflictDoUpdate({
8931
- target: chains$1.chainId,
9758
+ target: chains.chainId,
8932
9759
  set: {
8933
- blockNumber: sql`${chains$1.blockNumber}`,
8934
- epoch: sql`${chains$1.epoch}`
9760
+ blockNumber: sql`${chains.blockNumber}`,
9761
+ epoch: sql`${chains.epoch}`
8935
9762
  }
8936
9763
  }).returning();
8937
9764
  if (chainRows.length === 0) throw new Error(`Failed to initialize chain state for chain ${parameters.chainId}`);
@@ -8971,28 +9798,28 @@ const create$13 = (config) => {
8971
9798
  const advanceChain = async (parameters) => {
8972
9799
  if (parameters.blockNumber < 0) throw new Error("Block number cannot be negative");
8973
9800
  if (parameters.epoch < 0n) throw new Error("Epoch cannot be negative");
8974
- if ((await db.update(chains$1).set({
9801
+ if ((await db.update(chains).set({
8975
9802
  blockNumber: parameters.blockNumber,
8976
9803
  epoch: parameters.epoch.toString(),
8977
9804
  updatedAt: sql`now()`
8978
- }).where(eq(chains$1.chainId, parameters.chainId)).returning()).length === 0) throw new Error(`Chain state not initialized for chain ${parameters.chainId}. Call blocks.init first.`);
9805
+ }).where(eq(chains.chainId, parameters.chainId)).returning()).length === 0) throw new Error(`Chain state not initialized for chain ${parameters.chainId}. Call blocks.init first.`);
8979
9806
  };
8980
9807
  const advanceCollector = async (parameters) => {
8981
9808
  if (parameters.blockNumber < 0) throw new Error("Block number cannot be negative");
8982
9809
  if (parameters.epoch < 0n) throw new Error("Epoch cannot be negative");
8983
9810
  const name = parameters.collectorName.toLowerCase();
8984
9811
  const chain = db.select({
8985
- chainId: chains$1.chainId,
8986
- currentEpoch: chains$1.epoch,
8987
- currentBlockNumber: chains$1.blockNumber
8988
- }).from(chains$1).where(eq(chains$1.chainId, parameters.chainId)).as("chain");
9812
+ chainId: chains.chainId,
9813
+ currentEpoch: chains.epoch,
9814
+ currentBlockNumber: chains.blockNumber
9815
+ }).from(chains).where(eq(chains.chainId, parameters.chainId)).as("chain");
8989
9816
  const hasReorgHappened = (await db.select().from(collectors).leftJoin(chain, eq(collectors.chainId, chain.chainId)).where(and(eq(collectors.chainId, parameters.chainId), eq(collectors.name, name), gt(chain.currentEpoch, collectors.epoch), eq(chain.currentEpoch, parameters.epoch.toString()), gte(collectors.blockNumber, parameters.blockNumber))).limit(1)).length > 0;
8990
9817
  if ((await db.update(collectors).set({
8991
9818
  blockNumber: parameters.blockNumber,
8992
9819
  epoch: parameters.epoch.toString(),
8993
9820
  updatedAt: sql`now()`
8994
9821
  }).from(chain).where(and(eq(collectors.chainId, parameters.chainId), eq(collectors.name, name), eq(chain.currentEpoch, parameters.epoch.toString()), gte(chain.currentBlockNumber, parameters.blockNumber), ...hasReorgHappened ? [] : [lte(collectors.blockNumber, parameters.blockNumber)])).returning()).length > 0) return;
8995
- if ((await db.select({ chainId: chains$1.chainId }).from(chains$1).where(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.`);
9822
+ if ((await db.select({ chainId: chains.chainId }).from(chains).where(eq(chains.chainId, parameters.chainId)).limit(1)).length === 0) throw new Error(`Chain state not initialized for chain ${parameters.chainId}. Call blocks.init first.`);
8996
9823
  if ((await db.select({ chainId: collectors.chainId }).from(collectors).where(and(eq(collectors.chainId, parameters.chainId), 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.`);
8997
9824
  throw new Error(`A chain reorg has happened on chain ${parameters.chainId}, update of the collector ${name} is aborted`);
8998
9825
  };
@@ -9040,7 +9867,7 @@ const create$13 = (config) => {
9040
9867
  //#region src/database/domains/Book.ts
9041
9868
  const DEFAULT_LIMIT$2 = 100;
9042
9869
  const MAX_TOTAL_OFFERS = 500;
9043
- function create$12(config) {
9870
+ function create$11(config) {
9044
9871
  const db = config.db;
9045
9872
  const logger = getLogger();
9046
9873
  const getOffers = async (parameters) => {
@@ -9126,18 +9953,17 @@ async function _getOffers(db, params) {
9126
9953
  const { obligationId, side, now, priceSortDirection, cursor, limit } = params;
9127
9954
  const raw = await db.execute(sql`
9128
9955
  WITH collats AS MATERIALIZED (
9129
- SELECT oc.obligation_id,
9956
+ SELECT oia.obligation_id,
9130
9957
  COALESCE(jsonb_agg(jsonb_build_object(
9131
9958
  'asset', oc.asset,
9132
- 'oracle', oracle.address,
9959
+ 'oracle', oc.oracle_address,
9133
9960
  'lltv', oc.lltv
9134
9961
  ) ORDER BY oc.asset), '[]'::jsonb) AS collaterals
9135
- FROM ${obligationCollateralsV2} oc
9136
- JOIN ${oracles$1} oracle
9137
- ON oracle.chain_id = oc.oracle_chain_id
9138
- AND oracle.address = oc.oracle_address
9139
- WHERE oc.obligation_id = ${obligationId}
9140
- GROUP BY oc.obligation_id
9962
+ FROM ${obligationIdKeys} oia
9963
+ JOIN ${obligationCollateralsV2} oc
9964
+ ON oc.obligation_key = oia.obligation_key
9965
+ WHERE oia.obligation_id = ${obligationId}
9966
+ GROUP BY oia.obligation_id
9141
9967
  ),
9142
9968
  winners AS (
9143
9969
  SELECT DISTINCT ON (o.group_chain_id, o.group_maker, o."group_group")
@@ -9145,6 +9971,7 @@ async function _getOffers(db, params) {
9145
9971
  FROM ${offers} o
9146
9972
  LEFT JOIN ${validations} v
9147
9973
  ON v.offer_hash = o.hash
9974
+ AND v.obligation_id = o.obligation_id
9148
9975
  LEFT JOIN ${status} s
9149
9976
  ON s.id = v.status_id
9150
9977
  WHERE o.obligation_id = ${obligationId}
@@ -9171,8 +9998,10 @@ async function _getOffers(db, params) {
9171
9998
  ON g.chain_id = w.group_chain_id
9172
9999
  AND g.maker = w.group_maker
9173
10000
  AND g."group" = w."group_group"
10001
+ JOIN ${obligationIdKeys} oia
10002
+ ON oia.obligation_id = w.obligation_id
9174
10003
  JOIN ${obligations} obl
9175
- ON obl.obligation_id = w.obligation_id
10004
+ ON obl.obligation_key = oia.obligation_key
9176
10005
  ),
9177
10006
  paged AS (
9178
10007
  SELECT e.*
@@ -9271,7 +10100,9 @@ async function _getOffers(db, params) {
9271
10100
  END
9272
10101
  )) AS lot_balance
9273
10102
  FROM paged p
9274
- LEFT JOIN ${offersCallbacks} oc ON oc.offer_hash = p.hash
10103
+ LEFT JOIN ${offersCallbacks} oc
10104
+ ON oc.offer_hash = p.hash
10105
+ AND oc.obligation_id = p.obligation_id
9275
10106
  LEFT JOIN ${callbacks} c ON c.id = oc.callback_id
9276
10107
  LEFT JOIN ${lots} l
9277
10108
  ON l.chain_id = c.position_chain_id
@@ -9356,11 +10187,13 @@ async function _getOffers(db, params) {
9356
10187
  AND NOT EXISTS (
9357
10188
  SELECT 1 FROM ${offersCallbacks} oc2
9358
10189
  WHERE oc2.offer_hash = p.hash
10190
+ AND oc2.obligation_id = p.obligation_id
9359
10191
  )
9360
10192
  )
9361
10193
  -- Final SELECT with inline takeable computation
9362
10194
  SELECT
9363
10195
  oc.hash,
10196
+ oc.obligation_id,
9364
10197
  oc.group_maker,
9365
10198
  oc.assets,
9366
10199
  oc.obligation_units,
@@ -9409,6 +10242,7 @@ async function _getOffers(db, params) {
9409
10242
  const receiverIfMakerIsSeller = (row.receiver_if_maker_is_seller ?? row.group_maker).toLowerCase();
9410
10243
  return {
9411
10244
  hash: row.hash,
10245
+ obligationId: row.obligation_id,
9412
10246
  maker: row.group_maker,
9413
10247
  assets: BigInt(row.assets),
9414
10248
  obligationUnits: BigInt(row.obligation_units ?? 0),
@@ -9508,7 +10342,7 @@ let LevelCursor;
9508
10342
  * @param db - Database core instance.
9509
10343
  * @returns Callbacks domain. {@link CallbacksDomain}
9510
10344
  */
9511
- function create$11(db) {
10345
+ function create$10(db) {
9512
10346
  return {
9513
10347
  upsert: async (inputs) => {
9514
10348
  if (inputs.length === 0) return;
@@ -9522,18 +10356,21 @@ function create$11(db) {
9522
10356
  idCache.set(preimage, id);
9523
10357
  return id;
9524
10358
  };
9525
- for (const { offerHash, callbacks } of inputs) {
10359
+ for (const { offerHash, obligationId, callbacks } of inputs) {
9526
10360
  const normalizedOfferHash = offerHash.toLowerCase();
10361
+ const normalizedObligationId = obligationId.toLowerCase();
9527
10362
  for (const callback of callbacks) {
9528
10363
  const normalized = {
9529
10364
  chainId: callback.chainId,
9530
10365
  contract: callback.contract.toLowerCase(),
9531
10366
  user: callback.user.toLowerCase(),
9532
- amount: callback.amount
10367
+ amount: callback.amount,
10368
+ positionTypeId: callback.positionTypeId
9533
10369
  };
9534
10370
  const id = callbackId(normalized);
9535
10371
  offersCallbacksRows.push({
9536
10372
  offerHash: normalizedOfferHash,
10373
+ obligationId: normalizedObligationId,
9537
10374
  callbackId: id
9538
10375
  });
9539
10376
  if (seenCallbackIds.has(id)) continue;
@@ -9543,6 +10380,7 @@ function create$11(db) {
9543
10380
  positionChainId: normalized.chainId,
9544
10381
  positionContract: normalized.contract,
9545
10382
  positionUser: normalized.user,
10383
+ positionTypeId: normalized.positionTypeId,
9546
10384
  amount: normalized.amount.toString()
9547
10385
  });
9548
10386
  }
@@ -9563,7 +10401,7 @@ function create$11(db) {
9563
10401
 
9564
10402
  //#endregion
9565
10403
  //#region src/database/domains/Consumed.ts
9566
- function create$10(db) {
10404
+ function create$9(db) {
9567
10405
  return {
9568
10406
  create: async (events) => {
9569
10407
  if (events.length === 0) return;
@@ -9611,7 +10449,7 @@ function create$10(db) {
9611
10449
  * @param db - Database core instance.
9612
10450
  * @returns Groups domain. {@link GroupsDomain}
9613
10451
  */
9614
- function create$9(db) {
10452
+ function create$8(db) {
9615
10453
  return { create: async (groups$1) => {
9616
10454
  if (groups$1.length === 0) return;
9617
10455
  const rows = groups$1.map((group) => ({
@@ -9627,7 +10465,7 @@ function create$9(db) {
9627
10465
 
9628
10466
  //#endregion
9629
10467
  //#region src/database/domains/Lots.ts
9630
- function create$8(db) {
10468
+ function create$7(db) {
9631
10469
  return {
9632
10470
  get: async (parameters) => {
9633
10471
  const { chainId, user, contract, group, obligationId } = parameters ?? {};
@@ -9655,6 +10493,17 @@ function create$8(db) {
9655
10493
  const existing = lotsByKey.get(key);
9656
10494
  if (!existing || offer.size > existing.size) lotsByKey.set(key, offer);
9657
10495
  }
10496
+ const positionsByKey = /* @__PURE__ */ new Map();
10497
+ for (const offer of lotsByKey.values()) {
10498
+ const posKey = `${offer.positionChainId}-${offer.positionContract}-${offer.positionUser}`.toLowerCase();
10499
+ if (!positionsByKey.has(posKey)) positionsByKey.set(posKey, {
10500
+ chainId: offer.positionChainId,
10501
+ contract: offer.positionContract.toLowerCase(),
10502
+ user: offer.positionUser.toLowerCase(),
10503
+ positionTypeId: offer.positionTypeId
10504
+ });
10505
+ }
10506
+ for (const row of positionsByKey.values()) await db.insert(lotsPositions).values(row).onConflictDoNothing();
9658
10507
  for (const offer of lotsByKey.values()) if ((await db.select().from(lots).where(and(eq(lots.chainId, offer.positionChainId), eq(lots.contract, offer.positionContract.toLowerCase()), eq(lots.user, offer.positionUser.toLowerCase()), eq(lots.group, offer.group.toLowerCase()), eq(lots.obligationId, offer.obligationId.toLowerCase()))).limit(1)).length === 0) {
9659
10508
  const maxUpperResult = await db.select({ maxUpper: sql`COALESCE(MAX(${lots.upper}::numeric), 0)` }).from(lots).where(and(eq(lots.chainId, offer.positionChainId), eq(lots.contract, offer.positionContract.toLowerCase()), eq(lots.user, offer.positionUser.toLowerCase()), eq(lots.obligationId, offer.obligationId.toLowerCase())));
9660
10509
  const newLower = BigInt(maxUpperResult[0]?.maxUpper ?? "0");
@@ -9680,58 +10529,72 @@ function create$8(db) {
9680
10529
  * @param db - Database core instance.
9681
10530
  * @returns Obligations domain. {@link ObligationsDomain}
9682
10531
  */
9683
- function create$7(db) {
10532
+ function create$6(db) {
9684
10533
  return {
9685
10534
  get: async (parameters) => {
9686
10535
  const chainIds = parameters?.chainId;
9687
10536
  const now$1 = now();
9688
10537
  return (await db.select({
9689
- chainId: obligations.chainId,
10538
+ obligationId: obligationIdKeys.obligationId,
10539
+ chainId: obligationIdKeys.chainId,
10540
+ morphoV2: obligationIdKeys.morphoV2,
9690
10541
  loanToken: obligations.loanToken,
9691
- collaterals: sql`ARRAY_AGG(jsonb_build_object('asset', ${obligationCollateralsV2.asset}, 'oracle', ${oracles$1.address}, 'lltv', ${obligationCollateralsV2.lltv}))`.as("collaterals"),
10542
+ collaterals: sql`ARRAY_AGG(jsonb_build_object('asset', ${obligationCollateralsV2.asset}, 'oracle', ${obligationCollateralsV2.oracleAddress}, 'lltv', ${obligationCollateralsV2.lltv}))`.as("collaterals"),
9692
10543
  maturity: obligations.maturity
9693
- }).from(obligations).innerJoin(obligationCollateralsV2, eq(obligations.obligationId, obligationCollateralsV2.obligationId)).innerJoin(oracles$1, sql`${obligationCollateralsV2.oracleChainId} = ${oracles$1.chainId}
9694
- AND ${obligationCollateralsV2.oracleAddress} = ${oracles$1.address}`).groupBy(obligations.obligationId).where(and(chainIds !== void 0 && chainIds.length > 0 ? inArray(obligations.chainId, chainIds) : void 0, gte(obligations.maturity, now$1))).orderBy(asc(obligations.obligationId))).map((row) => from$15({
10544
+ }).from(obligationIdKeys).innerJoin(obligations, eq(obligationIdKeys.obligationKey, obligations.obligationKey)).innerJoin(obligationCollateralsV2, eq(obligations.obligationKey, obligationCollateralsV2.obligationKey)).groupBy(obligationIdKeys.obligationId, obligationIdKeys.chainId, obligationIdKeys.morphoV2, obligations.loanToken, obligations.maturity).where(and(chainIds !== void 0 && chainIds.length > 0 ? inArray(obligationIdKeys.chainId, chainIds) : void 0, gte(obligations.maturity, now$1))).orderBy(asc(obligationIdKeys.obligationId))).map((row) => ({
10545
+ obligationId: row.obligationId,
9695
10546
  chainId: row.chainId,
9696
- loanToken: row.loanToken,
9697
- collaterals: row.collaterals.sort((left, right) => left.asset.localeCompare(right.asset)).map((collateral) => from$17({
9698
- asset: collateral.asset,
9699
- oracle: collateral.oracle,
9700
- lltv: from$18(BigInt(collateral.lltv))
9701
- })),
9702
- maturity: row.maturity
10547
+ morphoV2: row.morphoV2,
10548
+ obligation: from$15({
10549
+ loanToken: row.loanToken,
10550
+ collaterals: row.collaterals.sort((left, right) => left.asset.localeCompare(right.asset)).map((collateral) => from$17({
10551
+ asset: collateral.asset,
10552
+ oracle: collateral.oracle,
10553
+ lltv: from$18(BigInt(collateral.lltv))
10554
+ })),
10555
+ maturity: row.maturity
10556
+ })
9703
10557
  }));
9704
10558
  },
9705
10559
  create: async (obligations$1) => {
9706
10560
  if (obligations$1.length === 0) return;
9707
- const obligationsById = /* @__PURE__ */ new Map();
9708
- for (const obligation of obligations$1) {
9709
- const id$1 = id(obligation).toLowerCase();
9710
- if (!obligationsById.get(id$1)) obligationsById.set(id$1, obligation);
10561
+ const obligationsByKey = /* @__PURE__ */ new Map();
10562
+ const obligationIdKeysById = /* @__PURE__ */ new Map();
10563
+ for (const input of obligations$1) {
10564
+ const obligationKey = key(input.obligation).toLowerCase();
10565
+ if (!obligationsByKey.has(obligationKey)) obligationsByKey.set(obligationKey, input.obligation);
10566
+ const obligationId = input.obligationId.toLowerCase();
10567
+ if (!obligationIdKeysById.has(obligationId)) obligationIdKeysById.set(obligationId, input);
9711
10568
  }
9712
10569
  try {
9713
10570
  await db.transaction(async (dbTx) => {
9714
- const obligationRows = obligations$1.map((obligation) => ({
9715
- obligationId: id(obligation),
9716
- chainId: obligation.chainId,
9717
- loanToken: obligation.loanToken.toLowerCase(),
9718
- maturity: obligation.maturity
10571
+ const obligationRows = Array.from(obligationsByKey.entries()).map(([obligationKey, item]) => ({
10572
+ obligationKey,
10573
+ loanToken: item.loanToken.toLowerCase(),
10574
+ maturity: item.maturity
9719
10575
  }));
9720
10576
  for (const batch of batch$1(obligationRows, DEFAULT_BATCH_SIZE$1)) await dbTx.insert(obligations).values(batch).onConflictDoNothing();
9721
- const collateralRows = obligations$1.flatMap((obligation) => {
9722
- return obligation.collaterals.map((collateral) => ({
9723
- obligationId: id(obligation),
10577
+ const obligationIdKeyRows = Array.from(obligationIdKeysById.entries()).map(([obligationId, input]) => ({
10578
+ obligationId,
10579
+ obligationKey: key(input.obligation).toLowerCase(),
10580
+ chainId: input.chainId,
10581
+ morphoV2: input.morphoV2.toLowerCase()
10582
+ }));
10583
+ for (const batch of batch$1(obligationIdKeyRows, DEFAULT_BATCH_SIZE$1)) await dbTx.insert(obligationIdKeys).values(batch).onConflictDoNothing();
10584
+ const collateralRows = Array.from(obligationsByKey.entries()).flatMap(([obligationKey, item]) => {
10585
+ return [...item.collaterals].sort((a, b) => a.asset.toLowerCase().localeCompare(b.asset.toLowerCase())).map((collateral, collateralIndex) => ({
10586
+ obligationKey,
9724
10587
  asset: collateral.asset.toLowerCase(),
9725
- oracleChainId: obligation.chainId,
9726
10588
  oracleAddress: collateral.oracle.toLowerCase(),
9727
- lltv: collateral.lltv
10589
+ lltv: collateral.lltv,
10590
+ collateralIndex
9728
10591
  }));
9729
10592
  });
9730
10593
  for (const batch of batch$1(collateralRows, DEFAULT_BATCH_SIZE$1)) await dbTx.insert(obligationCollateralsV2).values(batch).onConflictDoNothing();
9731
10594
  });
9732
10595
  } catch (err) {
9733
10596
  const error = err instanceof Error ? err : new Error(String(err));
9734
- throw new Error("Obligations.create failed. Ensure oracles exist before inserting obligations.", { cause: error });
10597
+ throw new Error("Obligations.create failed. Ensure obligation id keys are valid before inserting offers.", { cause: error });
9735
10598
  }
9736
10599
  }
9737
10600
  };
@@ -9739,7 +10602,7 @@ function create$7(db) {
9739
10602
 
9740
10603
  //#endregion
9741
10604
  //#region src/database/domains/Offsets.ts
9742
- function create$6(db) {
10605
+ function create$5(db) {
9743
10606
  return { get: async (parameters) => {
9744
10607
  const { chainId, user, contract, group, obligationId } = parameters ?? {};
9745
10608
  const conditions = [];
@@ -9761,7 +10624,7 @@ function create$6(db) {
9761
10624
 
9762
10625
  //#endregion
9763
10626
  //#region src/database/domains/Oracles.ts
9764
- function create$5(db) {
10627
+ function create$4(db) {
9765
10628
  return {
9766
10629
  get: async ({ chainId }) => {
9767
10630
  return (await db.select({
@@ -9804,19 +10667,20 @@ function create$5(db) {
9804
10667
  //#endregion
9805
10668
  //#region src/database/domains/Positions.ts
9806
10669
  const DEFAULT_LIMIT$1 = 100;
9807
- const create$4 = (db) => {
10670
+ const create$3 = (db) => {
9808
10671
  return {
9809
10672
  upsert: async (positions$1) => {
9810
10673
  const positionsMap = /* @__PURE__ */ new Map();
9811
10674
  for (const p of positions$1) {
9812
- const key = `${p.chainId}-${p.contract}-${p.user}`.toLowerCase();
9813
- const zeroKey = `${p.chainId}-${p.contract}-${zeroAddress}`.toLowerCase();
10675
+ const key = `${p.chainId}-${p.contract}-${p.user}-${p.asset}`.toLowerCase();
10676
+ const zeroKey = `${p.chainId}-${p.contract}-${zeroAddress}-${p.asset}`.toLowerCase();
9814
10677
  if (!positionsMap.has(zeroKey)) positionsMap.set(zeroKey, {
9815
10678
  chainId: p.chainId,
9816
10679
  contract: p.contract,
9817
10680
  user: zeroAddress,
9818
10681
  balance: 0n,
9819
10682
  type: p.type,
10683
+ asset: p.asset,
9820
10684
  blockNumber: p.blockNumber
9821
10685
  });
9822
10686
  if (!positionsMap.has(key)) {
@@ -9827,14 +10691,14 @@ const create$4 = (db) => {
9827
10691
  }
9828
10692
  if (positionsMap.size === 0) return 0;
9829
10693
  const rows = Array.from(positionsMap.values()).map((p) => {
9830
- const positionTypeId = Object.values(Type).indexOf(p.type) + 1;
10694
+ const positionTypeId$1 = positionTypeId(p.type);
9831
10695
  return {
9832
10696
  chainId: p.chainId,
9833
10697
  contract: p.contract.toLowerCase(),
9834
10698
  user: p.user.toLowerCase(),
9835
- positionTypeId,
10699
+ positionTypeId: positionTypeId$1,
9836
10700
  ...p.balance !== void 0 ? { balance: p.balance.toString() } : {},
9837
- ...p.asset !== void 0 ? { asset: p.asset.toLowerCase() } : {},
10701
+ asset: p.asset.toLowerCase(),
9838
10702
  blockNumber: p.blockNumber
9839
10703
  };
9840
10704
  });
@@ -9844,11 +10708,12 @@ const create$4 = (db) => {
9844
10708
  target: [
9845
10709
  positions.chainId,
9846
10710
  positions.contract,
9847
- positions.user
10711
+ positions.user,
10712
+ positions.positionTypeId,
10713
+ positions.asset
9848
10714
  ],
9849
10715
  set: {
9850
10716
  balance: sql`EXCLUDED.balance`,
9851
- asset: sql`COALESCE(EXCLUDED.asset, ${positions.asset})`,
9852
10717
  blockNumber: sql`EXCLUDED.block_number`,
9853
10718
  updatedAt: sql`NOW()`
9854
10719
  },
@@ -9867,20 +10732,23 @@ const create$4 = (db) => {
9867
10732
  cursor = {
9868
10733
  chainId: parsed.chainId,
9869
10734
  contract: parsed.contract,
9870
- user: parsed.user
10735
+ user: parsed.user,
10736
+ positionTypeId: parsed.positionTypeId ?? 0,
10737
+ asset: parsed.asset ?? ""
9871
10738
  };
9872
10739
  }
9873
- const positions$2 = await db.select().from(positions).where(and(ne(positions.user, zeroAddress), filled === void 0 ? sql`true` : sql`${positions.balance} IS ${filled === true ? sql`NOT` : sql``} NULL`, type !== void 0 ? eq(positions.positionTypeId, Object.values(Type).indexOf(type) + 1) : sql`true`, chainId !== void 0 ? eq(positions.chainId, chainId) : sql`true`, (() => {
10740
+ const positions$2 = await db.select().from(positions).where(and(ne(positions.user, zeroAddress), filled === void 0 ? sql`true` : sql`${positions.balance} IS ${filled === true ? sql`NOT` : sql``} NULL`, type !== void 0 ? eq(positions.positionTypeId, positionTypeId(type)) : sql`true`, chainId !== void 0 ? eq(positions.chainId, chainId) : sql`true`, (() => {
9874
10741
  if (cursor === null || cursor === void 0) return sql`true`;
9875
10742
  return sql`
9876
- (${chainId === void 0 ? sql`"chain_id", ` : sql``}"contract", "user") > (${chainId === void 0 ? sql`${cursor.chainId}, ` : sql``}${cursor.contract}, ${cursor.user})
10743
+ (${chainId === void 0 ? sql`"chain_id", ` : sql``}"contract", "user", "position_type_id", "asset") > (${chainId === void 0 ? sql`${cursor.chainId}, ` : sql``}${cursor.contract}, ${cursor.user}, ${cursor.positionTypeId}, ${cursor.asset})
9877
10744
  `;
9878
- })())).orderBy(asc(positions.chainId), asc(positions.contract), asc(positions.user), asc(positions.blockNumber)).limit(limit);
10745
+ })())).orderBy(asc(positions.chainId), asc(positions.contract), asc(positions.user), asc(positions.positionTypeId), asc(positions.asset)).limit(limit);
9879
10746
  const nextCursor = positions$2.length === limit ? Buffer.from(JSON.stringify({
9880
10747
  chainId: positions$2[positions$2.length - 1].chainId.toString(),
9881
10748
  contract: positions$2[positions$2.length - 1].contract,
9882
10749
  user: positions$2[positions$2.length - 1].user,
9883
- blockNumber: positions$2[positions$2.length - 1].blockNumber.toString()
10750
+ positionTypeId: positions$2[positions$2.length - 1].positionTypeId,
10751
+ asset: positions$2[positions$2.length - 1].asset
9884
10752
  })).toString("base64url") : null;
9885
10753
  return {
9886
10754
  positions: positions$2.map((p) => ({
@@ -9889,14 +10757,14 @@ const create$4 = (db) => {
9889
10757
  user: p.user,
9890
10758
  type: Object.values(Type)[p.positionTypeId - 1],
9891
10759
  balance: p.balance !== null ? BigInt(p.balance) : void 0,
9892
- ...p.asset !== null ? { asset: p.asset } : {},
10760
+ asset: p.asset,
9893
10761
  blockNumber: p.blockNumber
9894
10762
  })),
9895
10763
  nextCursor
9896
10764
  };
9897
10765
  },
9898
10766
  getByUser: async (parameters) => {
9899
- const { user, limit = DEFAULT_LIMIT$1, cursor: encodedCursor } = parameters;
10767
+ const { user, type, limit = DEFAULT_LIMIT$1, cursor: encodedCursor } = parameters;
9900
10768
  let cursor = null;
9901
10769
  if (encodedCursor !== null && encodedCursor !== void 0) {
9902
10770
  const parsed = JSON.parse(Buffer.from(encodedCursor, "base64url").toString("utf8"));
@@ -9904,6 +10772,8 @@ const create$4 = (db) => {
9904
10772
  cursor = {
9905
10773
  chainId: parsed.chainId,
9906
10774
  contract: parsed.contract,
10775
+ positionTypeId: parsed.positionTypeId ?? 0,
10776
+ asset: parsed.asset ?? "",
9907
10777
  obligationId: parsed.obligationId ?? null
9908
10778
  };
9909
10779
  }
@@ -9991,6 +10861,8 @@ const create$4 = (db) => {
9991
10861
  p.contract,
9992
10862
  p."user",
9993
10863
  p.block_number,
10864
+ p.position_type_id,
10865
+ p.asset,
9994
10866
  po.obligation_id,
9995
10867
  COALESCE(po.reserved_balance, '0') AS reserved_balance
9996
10868
  FROM ${positions} p
@@ -10000,14 +10872,18 @@ const create$4 = (db) => {
10000
10872
  AND LOWER(po."user") = LOWER(p."user")
10001
10873
  WHERE LOWER(p."user") = LOWER(${user})
10002
10874
  AND p."user" != ${zeroAddress}
10003
- ${cursor !== null ? sql`AND (p.chain_id, p.contract, COALESCE(po.obligation_id, '')) > (${cursor.chainId}, ${cursor.contract}, ${cursor.obligationId ?? ""})` : sql``}
10004
- ORDER BY p.chain_id ASC, p.contract ASC, po.obligation_id ASC NULLS FIRST
10875
+ ${type !== void 0 ? sql`AND p.position_type_id = ${positionTypeId(type)}` : sql``}
10876
+ ${cursor !== null ? 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 ?? ""})` : sql``}
10877
+ ORDER BY p.chain_id ASC, p.contract ASC, p.position_type_id ASC, p.asset ASC, po.obligation_id ASC NULLS FIRST
10005
10878
  LIMIT ${limit}
10006
10879
  `);
10007
- const nextCursor = raw.rows.length === limit ? Buffer.from(JSON.stringify({
10008
- chainId: raw.rows[raw.rows.length - 1].chain_id.toString(),
10009
- contract: raw.rows[raw.rows.length - 1].contract,
10010
- obligationId: raw.rows[raw.rows.length - 1].obligation_id
10880
+ const lastRow = raw.rows[raw.rows.length - 1];
10881
+ const nextCursor = raw.rows.length === limit && lastRow !== void 0 ? Buffer.from(JSON.stringify({
10882
+ chainId: lastRow.chain_id.toString(),
10883
+ contract: lastRow.contract,
10884
+ positionTypeId: lastRow.position_type_id,
10885
+ asset: lastRow.asset,
10886
+ obligationId: lastRow.obligation_id
10011
10887
  })).toString("base64url") : null;
10012
10888
  return {
10013
10889
  positions: raw.rows.map((row) => ({
@@ -10022,21 +10898,23 @@ const create$4 = (db) => {
10022
10898
  };
10023
10899
  },
10024
10900
  setEmptyAfter: async (parameters) => {
10025
- const { chainId, blockNumber } = parameters;
10901
+ const { chainId, blockNumber, type } = parameters;
10902
+ const typeId = positionTypeId(type);
10026
10903
  return await db.transaction(async (tx) => {
10027
10904
  const updatedPositions = await tx.update(positions).set({
10028
10905
  balance: null,
10029
10906
  blockNumber,
10030
10907
  updatedAt: sql`NOW()`
10031
- }).where(and(eq(positions.chainId, chainId), sql`${positions.blockNumber} >= ${blockNumber}`)).returning();
10908
+ }).where(and(eq(positions.chainId, chainId), sql`${positions.blockNumber} >= ${blockNumber}`, eq(positions.positionTypeId, typeId))).returning();
10032
10909
  if (updatedPositions.length === 0) return 0;
10033
10910
  await tx.execute(sql`
10034
10911
  DELETE FROM ${transfers} t
10035
- USING (VALUES ${sql.join(updatedPositions.map((u) => sql`(${u.chainId}::bigint, ${u.contract}::varchar(42), ${u.user}::varchar(42))`), sql`,`)}) AS s(chain_id, "contract", "user")
10912
+ USING (VALUES ${sql.join(updatedPositions.map((u) => sql`(${u.chainId}::bigint, ${u.contract}::varchar(66), ${u.user}::varchar(42))`), sql`,`)}) AS s(chain_id, "contract", "user")
10036
10913
  WHERE
10037
10914
  t.chain_id = s.chain_id
10038
10915
  AND t."contract" = s."contract"
10039
10916
  AND (t."from" = s."user" OR t."to" = s."user")
10917
+ AND t.position_type_id = ${typeId}
10040
10918
  `);
10041
10919
  return updatedPositions.length;
10042
10920
  });
@@ -10046,33 +10924,35 @@ const create$4 = (db) => {
10046
10924
 
10047
10925
  //#endregion
10048
10926
  //#region src/database/domains/Transfers.ts
10049
- const create$3 = (db) => ({ create: async (transfers$1) => {
10050
- if (transfers$1.length === 0) return 0;
10051
- return await db.transaction(async (dbTx) => {
10052
- let totalInserted = 0;
10053
- for (const transfersBatch of batch$1(transfers$1, DEFAULT_BATCH_SIZE$1)) {
10054
- const { rows: inserted } = await dbTx.execute(sql`
10055
- INSERT INTO ${transfers} (event_id, chain_id, "contract", "from", "to", "value", block_number)
10056
- SELECT * FROM (VALUES ${sql.join(transfersBatch.map((transfer) => 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)`), sql`,`)}) AS v(event_id, chain_id, "contract", "from", "to", "value", block_number)
10927
+ const create$2 = (db) => ({
10928
+ create: async (transfers$1) => {
10929
+ if (transfers$1.length === 0) return 0;
10930
+ const typeId = positionTypeId(transfers$1[0].type);
10931
+ return await db.transaction(async (dbTx) => {
10932
+ let totalInserted = 0;
10933
+ for (const transfersBatch of batch$1(transfers$1, DEFAULT_BATCH_SIZE$1)) {
10934
+ const { rows: inserted } = await dbTx.execute(sql`
10935
+ INSERT INTO ${transfers} (event_id, chain_id, "contract", "from", "to", "value", position_type_id, asset, block_number)
10936
+ SELECT * FROM (VALUES ${sql.join(transfersBatch.map((transfer) => 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)`), sql`,`)}) AS v(event_id, chain_id, "contract", "from", "to", "value", position_type_id, asset, block_number)
10057
10937
  WHERE
10058
10938
  EXISTS (
10059
10939
  SELECT 1 FROM ${positions} p
10060
- WHERE p.chain_id = v.chain_id AND p."contract" = v."contract" AND p."user" = v."from" AND p.balance IS NOT NULL
10940
+ 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
10061
10941
  )
10062
10942
  AND
10063
10943
  EXISTS (
10064
10944
  SELECT 1 FROM ${positions} p
10065
- WHERE p.chain_id = v.chain_id AND p."contract" = v."contract" AND p."user" = v."to" AND p.balance IS NOT NULL
10945
+ 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
10066
10946
  )
10067
10947
  ON CONFLICT DO NOTHING
10068
10948
  RETURNING *;
10069
10949
  `);
10070
- if (inserted.length === 0) continue;
10071
- await dbTx.execute(sql`
10950
+ if (inserted.length === 0) continue;
10951
+ await dbTx.execute(sql`
10072
10952
  WITH inserted AS (
10073
10953
  VALUES ${sql.join(inserted.map((t) => {
10074
- return 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)`;
10075
- }), sql`,`)}
10954
+ return 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)`;
10955
+ }), sql`,`)}
10076
10956
  ),
10077
10957
  new_transfers AS (
10078
10958
  SELECT
@@ -10081,7 +10961,8 @@ const create$3 = (db) => ({ create: async (transfers$1) => {
10081
10961
  column3 AS "from",
10082
10962
  column4 AS "to",
10083
10963
  column5 AS "value",
10084
- column6 AS block_number
10964
+ column6 AS asset,
10965
+ column7 AS block_number
10085
10966
  FROM inserted
10086
10967
  ),
10087
10968
  diffs AS (
@@ -10090,6 +10971,7 @@ const create$3 = (db) => ({ create: async (transfers$1) => {
10090
10971
  nt."contract",
10091
10972
  nt."from" AS "user",
10092
10973
  -nt."value" AS delta,
10974
+ nt.asset,
10093
10975
  nt.block_number
10094
10976
  FROM new_transfers nt
10095
10977
  UNION ALL
@@ -10098,6 +10980,7 @@ const create$3 = (db) => ({ create: async (transfers$1) => {
10098
10980
  nt."contract",
10099
10981
  nt."to" AS "user",
10100
10982
  nt."value" AS delta,
10983
+ nt.asset,
10101
10984
  nt.block_number
10102
10985
  FROM new_transfers nt
10103
10986
  ),
@@ -10107,12 +10990,15 @@ const create$3 = (db) => ({ create: async (transfers$1) => {
10107
10990
  d."contract",
10108
10991
  d."user",
10109
10992
  d.delta,
10993
+ d.asset,
10110
10994
  d.block_number
10111
10995
  FROM diffs d
10112
10996
  JOIN ${positions} p
10113
10997
  ON p.chain_id = d.chain_id
10114
10998
  AND p."contract" = d."contract"
10115
10999
  AND p."user" = d."user"
11000
+ AND p.position_type_id = ${typeId}
11001
+ AND p.asset = d.asset
10116
11002
  -- Only keep per-event diffs that are at or after the current position block
10117
11003
  WHERE d.block_number >= p.block_number
10118
11004
  ),
@@ -10121,10 +11007,11 @@ const create$3 = (db) => ({ create: async (transfers$1) => {
10121
11007
  chain_id,
10122
11008
  "contract",
10123
11009
  "user",
11010
+ asset,
10124
11011
  SUM(delta) AS sum_delta,
10125
11012
  MAX(block_number) AS max_block
10126
11013
  FROM valid_diffs
10127
- GROUP BY 1,2,3
11014
+ GROUP BY 1,2,3,4
10128
11015
  )
10129
11016
  UPDATE ${positions} AS p
10130
11017
  SET
@@ -10136,101 +11023,109 @@ const create$3 = (db) => ({ create: async (transfers$1) => {
10136
11023
  p.chain_id = a.chain_id
10137
11024
  AND p."contract" = a."contract"
10138
11025
  AND p."user" = a."user"
11026
+ AND p.asset = a.asset
11027
+ AND p.position_type_id = ${typeId}
10139
11028
  `);
10140
- totalInserted += inserted.length;
10141
- }
10142
- return totalInserted;
10143
- });
10144
- } });
10145
-
10146
- //#endregion
10147
- //#region src/database/domains/Trees.ts
10148
- /**
10149
- * Creates a Trees domain instance for managing merkle tree metadata.
10150
- *
10151
- * @param config - Configuration with database instance
10152
- * @returns TreesDomain instance
10153
- */
10154
- function create$2(config) {
10155
- const db = config.db;
10156
- return {
10157
- create: async (trees$1) => {
10158
- if (trees$1.length === 0) return [];
10159
- try {
10160
- return await db.transaction(async (dbTx) => {
10161
- const roots = [];
10162
- for (const { tree, signature } of trees$1) {
10163
- const root = tree.root.toLowerCase();
10164
- roots.push(root);
10165
- await dbTx.insert(trees).values({
10166
- root,
10167
- rootSignature: signature.toLowerCase()
10168
- }).onConflictDoUpdate({
10169
- target: [trees.root],
10170
- set: {
10171
- rootSignature: signature.toLowerCase(),
10172
- createdAt: sql`NOW()`
10173
- }
10174
- });
10175
- const pathRows = proofs(tree).map((proof) => ({
10176
- offerHash: hash(proof.offer).toLowerCase(),
10177
- treeRoot: root,
10178
- proofNodes: concatenateProofs(proof.path)
10179
- }));
10180
- for (const batch of batch$1(pathRows, DEFAULT_BATCH_SIZE$1)) await dbTx.insert(merklePaths).values(batch).onConflictDoUpdate({
10181
- target: [merklePaths.offerHash],
10182
- set: {
10183
- treeRoot: sql`excluded.tree_root`,
10184
- proofNodes: sql`excluded.proof_nodes`,
10185
- createdAt: sql`NOW()`
10186
- }
10187
- });
10188
- }
10189
- return roots;
10190
- });
10191
- } catch (err) {
10192
- const error = err instanceof Error ? err : new Error(String(err));
10193
- throw new Error("Trees.create failed. Ensure offers exist before inserting merkle paths.", { cause: error });
11029
+ totalInserted += inserted.length;
10194
11030
  }
10195
- },
10196
- getAttestations: async (hashes) => {
10197
- if (hashes.length === 0) return /* @__PURE__ */ new Map();
10198
- const normalizedHashes = hashes.map((h) => h.toLowerCase());
10199
- const results = await db.select({
10200
- offerHash: merklePaths.offerHash,
10201
- treeRoot: merklePaths.treeRoot,
10202
- proofNodes: merklePaths.proofNodes,
10203
- rootSignature: trees.rootSignature
10204
- }).from(merklePaths).innerJoin(trees, eq(merklePaths.treeRoot, trees.root)).where(inArray(merklePaths.offerHash, normalizedHashes));
10205
- const attestationMap = /* @__PURE__ */ new Map();
10206
- for (const row of results) attestationMap.set(row.offerHash, {
10207
- root: row.treeRoot,
10208
- signature: row.rootSignature,
10209
- proof: splitProofs(row.proofNodes)
10210
- });
10211
- return attestationMap;
10212
- }
10213
- };
10214
- }
10215
- /**
10216
- * Concatenates an array of 32-byte hex hashes into a single hex string.
10217
- * Empty arrays return "0x".
10218
- */
10219
- function concatenateProofs(proofs) {
10220
- if (proofs.length === 0) return "0x";
10221
- return `0x${proofs.map((p) => p.slice(2)).join("")}`;
10222
- }
10223
- /**
10224
- * Splits a concatenated hex string back into an array of 32-byte hex hashes.
10225
- * Returns empty array for "0x" or empty string.
10226
- */
10227
- function splitProofs(concatenated) {
10228
- if (!concatenated || concatenated === "0x" || concatenated.length <= 2) return [];
10229
- const hex = concatenated.slice(2);
10230
- const proofs = [];
10231
- for (let i = 0; i < hex.length; i += 64) proofs.push(`0x${hex.slice(i, i + 64)}`);
10232
- return proofs;
10233
- }
11031
+ return totalInserted;
11032
+ });
11033
+ },
11034
+ delete: async (parameters) => {
11035
+ const { chainId, blockNumberGte, positionTypeId } = parameters;
11036
+ return await db.transaction(async (dbTx) => {
11037
+ const { rows: toDelete } = await dbTx.execute(sql`
11038
+ SELECT event_id, chain_id, "contract", "from", "to", "value"::text, asset, block_number
11039
+ FROM ${transfers}
11040
+ WHERE chain_id = ${chainId}
11041
+ AND position_type_id = ${positionTypeId}
11042
+ AND block_number >= ${blockNumberGte}
11043
+ `);
11044
+ if (toDelete.length === 0) return 0;
11045
+ await dbTx.execute(sql`
11046
+ WITH to_delete AS (
11047
+ VALUES ${sql.join(toDelete.map((t) => 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))`), sql`,`)}
11048
+ ),
11049
+ reverse_diffs AS (
11050
+ SELECT column1 AS chain_id, column2 AS "contract", column3 AS "user", column5 AS delta, column6 AS asset
11051
+ FROM to_delete
11052
+ UNION ALL
11053
+ SELECT column1 AS chain_id, column2 AS "contract", column4 AS "user", -column5 AS delta, column6 AS asset
11054
+ FROM to_delete
11055
+ ),
11056
+ aggregated AS (
11057
+ SELECT chain_id, "contract", "user", asset, SUM(delta) AS sum_delta
11058
+ FROM reverse_diffs
11059
+ GROUP BY 1,2,3,4
11060
+ ),
11061
+ remaining_max AS (
11062
+ SELECT t.chain_id, t."contract",
11063
+ CASE WHEN t."from" = a."user" THEN t."from" ELSE t."to" END AS "user",
11064
+ t.asset,
11065
+ MAX(t.block_number) AS max_block
11066
+ FROM ${transfers} t
11067
+ JOIN aggregated a
11068
+ ON t.chain_id = a.chain_id AND t."contract" = a."contract"
11069
+ AND t.asset = a.asset
11070
+ AND (t."from" = a."user" OR t."to" = a."user")
11071
+ WHERE t.position_type_id = ${positionTypeId}
11072
+ AND t.block_number < ${blockNumberGte}
11073
+ GROUP BY t.chain_id, t."contract",
11074
+ CASE WHEN t."from" = a."user" THEN t."from" ELSE t."to" END,
11075
+ t.asset
11076
+ )
11077
+ UPDATE ${positions} AS p
11078
+ SET
11079
+ balance = COALESCE(p.balance, 0) + a.sum_delta,
11080
+ block_number = COALESCE(rm.max_block, p.block_number),
11081
+ updated_at = NOW()
11082
+ FROM aggregated a
11083
+ LEFT JOIN remaining_max rm
11084
+ ON rm.chain_id = a.chain_id AND rm."contract" = a."contract" AND rm."user" = a."user" AND rm.asset = a.asset
11085
+ WHERE p.chain_id = a.chain_id
11086
+ AND p."contract" = a."contract"
11087
+ AND p."user" = a."user"
11088
+ AND p.asset = a.asset
11089
+ AND p.position_type_id = ${positionTypeId}
11090
+ `);
11091
+ await dbTx.execute(sql`
11092
+ DELETE FROM ${transfers}
11093
+ WHERE chain_id = ${chainId}
11094
+ AND position_type_id = ${positionTypeId}
11095
+ AND block_number >= ${blockNumberGte}
11096
+ `);
11097
+ await dbTx.execute(sql`
11098
+ DELETE FROM ${positions} p
11099
+ USING (
11100
+ SELECT DISTINCT chain_id, "contract", "user", asset
11101
+ FROM (
11102
+ SELECT chain_id, "contract", "from" AS "user", asset FROM (
11103
+ VALUES ${sql.join(toDelete.map((t) => sql`(${t.chain_id}::bigint, ${t.contract}::varchar(66), ${t.from}::varchar(42), ${t.asset}::varchar(42))`), sql`,`)}
11104
+ ) AS d(chain_id, "contract", "from", asset)
11105
+ UNION
11106
+ SELECT chain_id, "contract", "to" AS "user", asset FROM (
11107
+ VALUES ${sql.join(toDelete.map((t) => sql`(${t.chain_id}::bigint, ${t.contract}::varchar(66), ${t.to}::varchar(42), ${t.asset}::varchar(42))`), sql`,`)}
11108
+ ) AS d(chain_id, "contract", "to", asset)
11109
+ ) AS combined
11110
+ ) AS affected
11111
+ WHERE p.chain_id = affected.chain_id
11112
+ AND p."contract" = affected."contract"
11113
+ AND p."user" = affected."user"
11114
+ AND p.asset = affected.asset
11115
+ AND p.position_type_id = ${positionTypeId}
11116
+ AND NOT EXISTS (
11117
+ SELECT 1 FROM ${transfers} t
11118
+ WHERE t.chain_id = p.chain_id
11119
+ AND t."contract" = p."contract"
11120
+ AND t.asset = p.asset
11121
+ AND t.position_type_id = ${positionTypeId}
11122
+ AND (t."from" = p."user" OR t."to" = p."user")
11123
+ )
11124
+ `);
11125
+ return toDelete.length;
11126
+ });
11127
+ }
11128
+ });
10234
11129
 
10235
11130
  //#endregion
10236
11131
  //#region src/database/domains/Validations.ts
@@ -10239,26 +11134,26 @@ function create$1(db) {
10239
11134
  return {
10240
11135
  get: async (params) => {
10241
11136
  const { status: status$1, cursor, limit = DEFAULT_LIMIT } = params ?? {};
10242
- if (cursor !== null && cursor !== void 0) {
10243
- if (!cursor.startsWith("0x") || cursor.length !== 66) throw new Error("Invalid cursor format");
10244
- }
11137
+ const parsedCursor = cursor !== null && cursor !== void 0 ? parseCursor(cursor) : void 0;
10245
11138
  let query = db.select({
10246
11139
  offerHash: validations.offerHash,
11140
+ obligationId: validations.obligationId,
10247
11141
  code: status.code
10248
11142
  }).from(validations).innerJoin(status, eq(validations.statusId, status.id));
10249
11143
  if (status$1 !== void 0) query = query.where(eq(status.code, status$1));
10250
- if (cursor !== null && cursor !== void 0) {
10251
- const cursorCondition = gt(validations.offerHash, cursor.toLowerCase());
11144
+ if (parsedCursor !== void 0) {
11145
+ const cursorCondition = or(gt(validations.offerHash, parsedCursor.offerHash), and(eq(validations.offerHash, parsedCursor.offerHash), gt(validations.obligationId, parsedCursor.obligationId)));
10252
11146
  if (status$1 !== void 0) query = query.where(and(eq(status.code, status$1), cursorCondition));
10253
11147
  else query = query.where(cursorCondition);
10254
11148
  }
10255
- const mapped = (await query.orderBy(asc(validations.offerHash)).limit(limit)).map((row) => ({
11149
+ const mapped = (await query.orderBy(asc(validations.offerHash), asc(validations.obligationId)).limit(limit)).map((row) => ({
10256
11150
  offerHash: row.offerHash,
11151
+ obligationId: row.obligationId,
10257
11152
  status: row.code
10258
11153
  }));
10259
11154
  return {
10260
11155
  validations: mapped,
10261
- nextCursor: mapped.length === limit ? mapped[mapped.length - 1].offerHash : null
11156
+ nextCursor: mapped.length === limit ? formatCursor(mapped[mapped.length - 1]) : null
10262
11157
  };
10263
11158
  },
10264
11159
  upsert: async (results) => {
@@ -10268,6 +11163,7 @@ function create$1(db) {
10268
11163
  if (invalidStatuses.length > 0) throw new Error(`Unknown validation status: ${invalidStatuses.join(", ")}`);
10269
11164
  const normalized = results.map((r) => ({
10270
11165
  offerHash: r.offerHash.toLowerCase(),
11166
+ obligationId: r.obligationId.toLowerCase(),
10271
11167
  status: r.status
10272
11168
  }));
10273
11169
  const uniqueStatuses = Array.from(new Set(normalized.map((r) => r.status)));
@@ -10279,10 +11175,11 @@ function create$1(db) {
10279
11175
  for (const status of uniqueStatuses) if (!statusMap.has(status)) throw new Error(`Unknown validation status: ${status}`);
10280
11176
  const values = normalized.map((row) => ({
10281
11177
  offerHash: row.offerHash,
11178
+ obligationId: row.obligationId,
10282
11179
  statusId: statusMap.get(row.status)
10283
11180
  }));
10284
11181
  for (const batch of batch$1(values, DEFAULT_BATCH_SIZE$1)) await db.insert(validations).values(batch).onConflictDoUpdate({
10285
- target: [validations.offerHash],
11182
+ target: [validations.offerHash, validations.obligationId],
10286
11183
  set: {
10287
11184
  statusId: sql`excluded.status_id`,
10288
11185
  updatedAt: sql`NOW()`
@@ -10291,6 +11188,18 @@ function create$1(db) {
10291
11188
  }
10292
11189
  };
10293
11190
  }
11191
+ const HEX_32 = /^0x[a-fA-F0-9]{64}$/;
11192
+ function parseCursor(cursor) {
11193
+ const [offerHash, obligationId] = cursor.split(":");
11194
+ if (!offerHash || !obligationId || !HEX_32.test(offerHash) || !HEX_32.test(obligationId)) throw new Error("Invalid cursor format");
11195
+ return {
11196
+ offerHash: offerHash.toLowerCase(),
11197
+ obligationId: obligationId.toLowerCase()
11198
+ };
11199
+ }
11200
+ function formatCursor(input) {
11201
+ return `${input.offerHash.toLowerCase()}:${input.obligationId.toLowerCase()}`;
11202
+ }
10294
11203
 
10295
11204
  //#endregion
10296
11205
  //#region src/database/Database.ts
@@ -10300,23 +11209,23 @@ var Database_exports = /* @__PURE__ */ __exportAll({
10300
11209
  });
10301
11210
  function createDomains(core, chainRegistry) {
10302
11211
  return {
10303
- book: create$12({ db: core }),
10304
- blocks: create$13({
11212
+ book: create$11({ db: core }),
11213
+ blocks: create$12({
10305
11214
  db: core,
10306
11215
  chainRegistry
10307
11216
  }),
10308
- callbacks: create$11(core),
11217
+ callbacks: create$10(core),
10309
11218
  offers: create$15({ db: core }),
10310
- consumed: create$10(core),
10311
- groups: create$9(core),
10312
- lots: create$8(core),
10313
- obligations: create$7(core),
10314
- offsets: create$6(core),
10315
- oracles: create$5(core),
10316
- trees: create$2({ db: core }),
11219
+ consumed: create$9(core),
11220
+ groups: create$8(core),
11221
+ lots: create$7(core),
11222
+ obligations: create$6(core),
11223
+ offsets: create$5(core),
11224
+ oracles: create$4(core),
11225
+ trees: create$14({ db: core }),
10317
11226
  validations: create$1(core),
10318
- positions: create$4(core),
10319
- transfers: create$3(core)
11227
+ positions: create$3(core),
11228
+ transfers: create$2(core)
10320
11229
  };
10321
11230
  }
10322
11231
  function createReaders(core) {
@@ -10481,7 +11390,7 @@ async function postMigrate(driver) {
10481
11390
  const tracer = getTracer("db.postMigrate");
10482
11391
  await startActiveSpan(tracer, "db.postMigrate", async () => {
10483
11392
  await driver.execute(`INSERT INTO "${VERSION}"."status" ("code") VALUES ('${Status.VALID}'), ('${Status.SIMULATION_ERROR}') ON CONFLICT DO NOTHING;`);
10484
- await driver.execute(`INSERT INTO "${VERSION}"."position_types" ("type") VALUES ('${Type.ERC20}'), ('${Type.VAULT_V1}') ON CONFLICT DO NOTHING;`);
11393
+ 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;`);
10485
11394
  await driver.execute(`
10486
11395
  CREATE OR REPLACE FUNCTION apply_group_consumed_stmt_ins()
10487
11396
  RETURNS trigger
@@ -10635,19 +11544,23 @@ async function postMigrate(driver) {
10635
11544
  BEGIN
10636
11545
  DELETE FROM "${VERSION}"."positions" p
10637
11546
  USING (
10638
- SELECT DISTINCT c.position_chain_id, c.position_contract, c.position_user
11547
+ SELECT DISTINCT c.position_chain_id, c.position_contract, c.position_user, c.position_type_id
10639
11548
  FROM deleted_rows d
10640
11549
  JOIN "${VERSION}"."callbacks" c ON c.id = d.callback_id
10641
11550
  ) AS affected
11551
+ JOIN "${VERSION}"."position_types" pt ON pt.id = affected.position_type_id
10642
11552
  WHERE p.chain_id = affected.position_chain_id
10643
11553
  AND p.contract = affected.position_contract
10644
11554
  AND p."user" = affected.position_user
11555
+ AND p.position_type_id = affected.position_type_id
11556
+ AND pt.type = 'erc20'
10645
11557
  AND NOT EXISTS (
10646
11558
  SELECT 1 FROM "${VERSION}"."callbacks" c2
10647
11559
  JOIN "${VERSION}"."offers_callbacks" oc ON oc.callback_id = c2.id
10648
11560
  WHERE c2.position_chain_id = p.chain_id
10649
11561
  AND c2.position_contract = p.contract
10650
11562
  AND c2.position_user = p."user"
11563
+ AND c2.position_type_id = p.position_type_id
10651
11564
  );
10652
11565
  RETURN NULL;
10653
11566
  END;
@@ -10694,7 +11607,7 @@ async function postMigrate(driver) {
10694
11607
  RETURNS TRIGGER AS $$
10695
11608
  DECLARE
10696
11609
  orphan_obligation_ids TEXT[];
10697
- candidate_oracles RECORD;
11610
+ orphan_obligation_keys TEXT[];
10698
11611
  BEGIN
10699
11612
  -- 1. Find orphan obligation IDs
10700
11613
  SELECT ARRAY_AGG(DISTINCT obligation_id) INTO orphan_obligation_ids
@@ -10702,33 +11615,39 @@ async function postMigrate(driver) {
10702
11615
  WHERE NOT EXISTS (
10703
11616
  SELECT 1 FROM "${VERSION}"."offers" ov
10704
11617
  WHERE ov.obligation_id = d.obligation_id
11618
+ )
11619
+ AND NOT EXISTS (
11620
+ SELECT 1 FROM "${VERSION}"."positions" p
11621
+ JOIN "${VERSION}"."position_types" pt ON pt.id = p.position_type_id
11622
+ WHERE p."contract" = d.obligation_id
11623
+ AND pt.type IN ('debtOf', 'collateralOf')
10705
11624
  );
10706
11625
 
10707
- -- 2. If no orphan obligations, exit early
11626
+ -- 2. If no orphan obligation IDs, exit early
10708
11627
  IF orphan_obligation_ids IS NULL OR array_length(orphan_obligation_ids, 1) IS NULL THEN
10709
11628
  RETURN NULL;
10710
11629
  END IF;
10711
11630
 
10712
- -- 3. Capture candidate oracles (from collaterals of orphan obligations)
10713
- CREATE TEMP TABLE _candidate_oracles ON COMMIT DROP AS
10714
- SELECT DISTINCT oc.oracle_chain_id, oc.oracle_address
10715
- FROM "${VERSION}"."obligation_collaterals_v2" oc
10716
- WHERE oc.obligation_id = ANY(orphan_obligation_ids);
11631
+ -- 3. Delete orphan obligation id keys
11632
+ DELETE FROM "${VERSION}"."obligation_id_keys" oia
11633
+ WHERE oia.obligation_id = ANY(orphan_obligation_ids);
10717
11634
 
10718
- -- 4. Delete orphan obligations (cascades to collaterals)
10719
- DELETE FROM "${VERSION}"."obligations" ob
10720
- WHERE ob.obligation_id = ANY(orphan_obligation_ids);
11635
+ -- 4. Find canonical obligation keys with no remaining obligation id keys
11636
+ SELECT ARRAY_AGG(ob.obligation_key) INTO orphan_obligation_keys
11637
+ FROM "${VERSION}"."obligations" ob
11638
+ WHERE NOT EXISTS (
11639
+ SELECT 1 FROM "${VERSION}"."obligation_id_keys" oia
11640
+ WHERE oia.obligation_key = ob.obligation_key
11641
+ );
10721
11642
 
10722
- -- 5. Delete oracles that are now orphaned (no remaining collateral references)
10723
- DELETE FROM "${VERSION}"."oracles" o
10724
- USING _candidate_oracles co
10725
- WHERE o.chain_id = co.oracle_chain_id
10726
- AND o.address = co.oracle_address
10727
- AND NOT EXISTS (
10728
- SELECT 1 FROM "${VERSION}"."obligation_collaterals_v2" oc
10729
- WHERE oc.oracle_chain_id = o.chain_id
10730
- AND oc.oracle_address = o.address
10731
- );
11643
+ -- 5. If no orphan canonical obligations, exit early
11644
+ IF orphan_obligation_keys IS NULL OR array_length(orphan_obligation_keys, 1) IS NULL THEN
11645
+ RETURN NULL;
11646
+ END IF;
11647
+
11648
+ -- 6. Delete orphan canonical obligations (cascades to collaterals)
11649
+ DELETE FROM "${VERSION}"."obligations" ob
11650
+ WHERE ob.obligation_key = ANY(orphan_obligation_keys);
10732
11651
 
10733
11652
  RETURN NULL;
10734
11653
  END;
@@ -10746,16 +11665,21 @@ async function postMigrate(driver) {
10746
11665
  RETURNS trigger
10747
11666
  LANGUAGE plpgsql AS $$
10748
11667
  BEGIN
10749
- INSERT INTO "${VERSION}"."offsets" (chain_id, "user", contract, "group", obligation_id, value)
10750
- VALUES (
10751
- OLD.chain_id,
10752
- OLD."user",
10753
- OLD.contract,
10754
- OLD."group",
10755
- OLD.obligation_id,
10756
- OLD.upper::numeric - OLD.lower::numeric
10757
- )
10758
- ON CONFLICT (chain_id, "user", contract, "group", obligation_id) DO NOTHING;
11668
+ BEGIN
11669
+ INSERT INTO "${VERSION}"."offsets" (chain_id, "user", contract, "group", obligation_id, value)
11670
+ VALUES (
11671
+ OLD.chain_id,
11672
+ OLD."user",
11673
+ OLD.contract,
11674
+ OLD."group",
11675
+ OLD.obligation_id,
11676
+ OLD.upper::numeric - OLD.lower::numeric
11677
+ )
11678
+ ON CONFLICT (chain_id, "user", contract, "group", obligation_id) DO NOTHING;
11679
+ EXCEPTION WHEN foreign_key_violation THEN
11680
+ -- lots_positions may be gone during cascaded deletes
11681
+ NULL;
11682
+ END;
10759
11683
  RETURN OLD;
10760
11684
  END;
10761
11685
  $$;
@@ -10778,11 +11702,26 @@ async function postMigrate(driver) {
10778
11702
  AND l.contract = OLD.contract
10779
11703
  AND l."user" = OLD."user"
10780
11704
  ) THEN
10781
- -- No lots remain, delete the position (cascades to offsets)
11705
+ -- No lots remain, delete erc20 positions and their lots_positions bridge rows.
11706
+ -- debtOf/collateralOf positions live independently of offers and must not be deleted here.
11707
+ -- First delete the position (for ERC20, asset = contract).
10782
11708
  DELETE FROM "${VERSION}"."positions" p
10783
- WHERE p.chain_id = OLD.chain_id
10784
- AND p.contract = OLD.contract
10785
- AND p."user" = OLD."user";
11709
+ USING "${VERSION}"."lots_positions" lp
11710
+ JOIN "${VERSION}"."position_types" pt ON pt.id = lp.position_type_id
11711
+ WHERE lp.chain_id = OLD.chain_id
11712
+ AND lp.contract = OLD.contract
11713
+ AND lp."user" = OLD."user"
11714
+ AND p.chain_id = lp.chain_id
11715
+ AND p.contract = lp.contract
11716
+ AND p."user" = lp."user"
11717
+ AND p.position_type_id = lp.position_type_id
11718
+ AND pt.type = 'erc20';
11719
+
11720
+ -- Delete the lots_positions bridge row (cascades to offsets via offsets_lots_positions_fk).
11721
+ DELETE FROM "${VERSION}"."lots_positions" lp
11722
+ WHERE lp.chain_id = OLD.chain_id
11723
+ AND lp.contract = OLD.contract
11724
+ AND lp."user" = OLD."user";
10786
11725
  END IF;
10787
11726
  RETURN NULL;
10788
11727
  END;
@@ -10851,8 +11790,12 @@ function createHttpClient(config) {
10851
11790
  body: json
10852
11791
  };
10853
11792
  };
10854
- const isAllowed = async (offers) => {
10855
- const { statusCode, body } = await validate({ offers: offers.map((offer) => toSnakeCase(offer)) });
11793
+ const isAllowed = async (parameters) => {
11794
+ const { offers, chainId } = parameters;
11795
+ const { statusCode, body } = await validate({
11796
+ chain_id: chainId,
11797
+ offers: offers.map((offer) => toSnakeCase(offer))
11798
+ });
10856
11799
  if (statusCode !== 200) {
10857
11800
  const errorMessage = extractErrorMessage(body);
10858
11801
  throw new Error(`Gatekeeper validation failed: ${errorMessage ?? `status ${statusCode}`}`);
@@ -10994,11 +11937,12 @@ var Gatekeeper_exports = /* @__PURE__ */ __exportAll({ create: () => create });
10994
11937
  * @returns Gatekeeper instance. {@link Gatekeeper}
10995
11938
  */
10996
11939
  function create(parameters) {
10997
- const { rules } = parameters;
10998
- return { isAllowed: async (offers) => {
11940
+ const { rules: rulesFactory } = parameters;
11941
+ return { isAllowed: async (parameters) => {
11942
+ const { offers, chainId } = parameters;
10999
11943
  return await run({
11000
11944
  items: offers,
11001
- rules
11945
+ rules: rulesFactory(chainId)
11002
11946
  });
11003
11947
  } };
11004
11948
  }
@@ -11008,28 +11952,11 @@ function create(parameters) {
11008
11952
  var Rules_exports = /* @__PURE__ */ __exportAll({
11009
11953
  amountMutualExclusivity: () => amountMutualExclusivity,
11010
11954
  callback: () => callback,
11011
- chains: () => chains,
11012
11955
  collateralToken: () => collateralToken,
11013
11956
  loanToken: () => loanToken,
11014
11957
  maturity: () => maturity,
11015
11958
  oracle: () => oracle,
11016
- sameMaker: () => sameMaker,
11017
- validity: () => validity
11018
- });
11019
- /**
11020
- * set of rules to validate offers.
11021
- *
11022
- * @param _parameters - Validity parameters with chain and client
11023
- * @returns Array of validation rules to evaluate against offers
11024
- */
11025
- function validity(_parameters) {
11026
- return [single("expiry", "Validates that offer has not expired", (offer) => {
11027
- if (offer.expiry < Math.floor(Date.now() / 1e3)) return { message: "Expiry mismatch" };
11028
- })];
11029
- }
11030
- const chains = ({ chains }) => single("chain_ids", `Validates that offer chain is one of: [${chains.map((c) => c.id).join(", ")}]`, (offer) => {
11031
- const allowedChainIds = chains.map((c) => c.id);
11032
- if (!allowedChainIds.some((id) => id === offer.chainId)) return { message: `Chain ID ${offer.chainId} is not in the allowed chains (${allowedChainIds.join(", ")})` };
11959
+ sameMaker: () => sameMaker
11033
11960
  });
11034
11961
  const maturity = ({ maturities }) => single("maturity", `Validates that offer maturity is one of: [${maturities.join(", ")}]`, (offer) => {
11035
11962
  const allowedMaturities = maturities.map((m) => from$16(m));
@@ -11045,9 +11972,9 @@ const callback = ({ callbacks }) => single("callback", `Validates callbacks: buy
11045
11972
  * @param assetsByChainId - Allowed loan tokens indexed by chain id.
11046
11973
  * @returns The issue that was found. If the offer is valid, this will be undefined.
11047
11974
  */
11048
- const loanToken = ({ assetsByChainId }) => single("loan_token", "Validates that offer loan token is in the allowed token list for the offer chain", (offer) => {
11049
- const allowedLoanTokens = assetsByChainId[offer.chainId]?.map((asset) => asset.toLowerCase());
11050
- if (!allowedLoanTokens || allowedLoanTokens.length === 0) return { message: `No allowed loan tokens for chain ${offer.chainId}` };
11975
+ const loanToken = ({ assetsByChainId, chainId }) => single("loan_token", "Validates that offer loan token is in the allowed token list for the request chain", (offer) => {
11976
+ const allowedLoanTokens = assetsByChainId[chainId]?.map((asset) => asset.toLowerCase());
11977
+ if (!allowedLoanTokens || allowedLoanTokens.length === 0) return { message: `No allowed loan tokens for chain ${chainId}` };
11051
11978
  if (!allowedLoanTokens.includes(offer.loanToken.toLowerCase())) return { message: "Loan token is not allowed" };
11052
11979
  });
11053
11980
  /**
@@ -11055,9 +11982,9 @@ const loanToken = ({ assetsByChainId }) => single("loan_token", "Validates that
11055
11982
  * @param collateralAssetsByChainId - Allowed collateral tokens indexed by chain id.
11056
11983
  * @returns The issue that was found. If the offer is valid, this will be undefined.
11057
11984
  */
11058
- const collateralToken = ({ collateralAssetsByChainId }) => single("collateral_token", "Validates that offer collateral tokens are in the allowed token list for the offer chain", (offer) => {
11059
- const allowedCollateralTokens = collateralAssetsByChainId[offer.chainId]?.map((asset) => asset.toLowerCase()) ?? [];
11060
- if (allowedCollateralTokens.length === 0) return { message: `No allowed collateral tokens for chain ${offer.chainId}` };
11985
+ const collateralToken = ({ collateralAssetsByChainId, chainId }) => single("collateral_token", "Validates that offer collateral tokens are in the allowed token list for the request chain", (offer) => {
11986
+ const allowedCollateralTokens = collateralAssetsByChainId[chainId]?.map((asset) => asset.toLowerCase()) ?? [];
11987
+ if (allowedCollateralTokens.length === 0) return { message: `No allowed collateral tokens for chain ${chainId}` };
11061
11988
  if (offer.collaterals.length === 0) return { message: "At least one collateral token is required" };
11062
11989
  if (offer.collaterals.some((collateral) => !allowedCollateralTokens.includes(collateral.asset.toLowerCase()))) return { message: "Collateral token is not allowed" };
11063
11990
  });
@@ -11066,9 +11993,9 @@ const collateralToken = ({ collateralAssetsByChainId }) => single("collateral_to
11066
11993
  * @param oraclesByChainId - Allowed oracles indexed by chain id.
11067
11994
  * @returns The issue that was found. If the offer is valid, this will be undefined.
11068
11995
  */
11069
- const oracle = ({ oraclesByChainId }) => single("oracle", "Validates that offer collateral oracles are in the allowed oracle list for the offer chain", (offer) => {
11070
- const allowedOracles = oraclesByChainId[offer.chainId]?.map((oracle) => oracle.toLowerCase());
11071
- if (!allowedOracles || allowedOracles.length === 0) return { message: `No allowed oracles for chain ${offer.chainId}` };
11996
+ const oracle = ({ oraclesByChainId, chainId }) => single("oracle", "Validates that offer collateral oracles are in the allowed oracle list for the request chain", (offer) => {
11997
+ const allowedOracles = oraclesByChainId[chainId]?.map((oracle) => oracle.toLowerCase());
11998
+ if (!allowedOracles || allowedOracles.length === 0) return { message: `No allowed oracles for chain ${chainId}` };
11072
11999
  if (offer.collaterals.some((collateral) => !allowedOracles.includes(collateral.oracle.toLowerCase()))) return { message: "Oracle is not allowed" };
11073
12000
  });
11074
12001
  /**
@@ -11100,11 +12027,12 @@ const amountMutualExclusivity = () => single("amount_mutual_exclusivity", "Valid
11100
12027
 
11101
12028
  //#endregion
11102
12029
  //#region src/gatekeeper/morphoRules.ts
11103
- const morphoRules = (chains$3) => {
12030
+ const morphoRules = (parameters) => {
12031
+ const { chains, chainId } = parameters;
11104
12032
  const assetsByChainId = {};
11105
12033
  const collateralAssetsByChainId = {};
11106
12034
  const oraclesByChainId = {};
11107
- for (const chain of chains$3) {
12035
+ for (const chain of chains) {
11108
12036
  assetsByChainId[chain.id] = assets[chain.id.toString()] ?? [];
11109
12037
  collateralAssetsByChainId[chain.id] = collateralAssets[chain.id.toString()] ?? [];
11110
12038
  oraclesByChainId[chain.id] = oracles[chain.id.toString()] ?? [];
@@ -11112,15 +12040,23 @@ const morphoRules = (chains$3) => {
11112
12040
  return [
11113
12041
  sameMaker(),
11114
12042
  amountMutualExclusivity(),
11115
- chains({ chains: chains$3 }),
11116
12043
  maturity({ maturities: [MaturityType.EndOfWeek, MaturityType.EndOfNextWeek] }),
11117
12044
  callback({
11118
12045
  callbacks: [Type$1.BuyWithEmptyCallback, Type$1.SellWithEmptyCallback],
11119
12046
  allowedAddresses: []
11120
12047
  }),
11121
- loanToken({ assetsByChainId }),
11122
- collateralToken({ collateralAssetsByChainId }),
11123
- oracle({ oraclesByChainId })
12048
+ loanToken({
12049
+ assetsByChainId,
12050
+ chainId
12051
+ }),
12052
+ collateralToken({
12053
+ collateralAssetsByChainId,
12054
+ chainId
12055
+ }),
12056
+ oracle({
12057
+ oraclesByChainId,
12058
+ chainId
12059
+ })
11124
12060
  ];
11125
12061
  };
11126
12062
 
@@ -11150,9 +12086,7 @@ function from(parameters) {
11150
12086
  async function add(config, offers) {
11151
12087
  if (!config.client.account) throw new WalletAccountNotSetError();
11152
12088
  const tree = from$8(offers.map((o) => from$14(o)));
11153
- const chainId = await getChainId(config.client);
11154
- for (const offer of tree.offers) if (chainId !== offer.chainId) throw new ChainIdMismatchError(offer.chainId, chainId);
11155
- const signatureDomain$1 = resolveSignatureDomain(config, chainId);
12089
+ const signatureDomain$1 = resolveSignatureDomain(config, await getChainId(config.client));
11156
12090
  const signature = await config.client.signTypedData({
11157
12091
  account: config.client.account,
11158
12092
  domain: signatureDomain(signatureDomain$1),
@@ -11257,12 +12191,6 @@ var WalletAccountNotSetError = class extends BaseError {
11257
12191
  var ViemClientError = class extends BaseError {
11258
12192
  name = "Mempool.ViemClientError";
11259
12193
  };
11260
- var ChainIdMismatchError = class extends BaseError {
11261
- name = "Mempool.ChainIdMismatchError";
11262
- constructor(expected, actual) {
11263
- super(`Chain ID mismatch. Offer chain ID is ${expected}, network chain ID is ${actual}.`);
11264
- }
11265
- };
11266
12194
  const resolveSignatureDomain = (config, chainId) => {
11267
12195
  const chain = config.client.chain;
11268
12196
  const verifyingContract = config.morphoAddress ?? chain?.custom?.morpho?.address ?? getChain(chainId)?.custom.morpho.address;
@@ -11291,7 +12219,6 @@ function connect(parameters) {
11291
12219
  //#endregion
11292
12220
  //#region src/mempool/index.ts
11293
12221
  var mempool_exports = /* @__PURE__ */ __exportAll({
11294
- ChainIdMismatchError: () => ChainIdMismatchError,
11295
12222
  MissingMorphoAddressError: () => MissingMorphoAddressError,
11296
12223
  ViemClientError: () => ViemClientError,
11297
12224
  WalletAccountNotSetError: () => WalletAccountNotSetError,
@@ -11302,5 +12229,5 @@ var mempool_exports = /* @__PURE__ */ __exportAll({
11302
12229
  });
11303
12230
 
11304
12231
  //#endregion
11305
- export { Abi_exports as Abi, BookResponse_exports as BookResponse, BooksController, BrandTypeId, Callback_exports as Callback, Chain_exports as Chain, ChainHealth, ChainRegistry_exports as ChainRegistry, ChainsHealthResponse, Collateral_exports as Collateral, CollectorHealth, CollectorsHealthResponse, ConfigContractsController, ConfigRulesController, Database_exports as Database, ERC4626_exports as ERC4626, Errors_exports as Errors, Format_exports as Format, Gatekeeper_exports as Gatekeeper, Client_exports as GatekeeperClient, Health_exports as Health, HealthController, Indexer_exports as Indexer, LLTV_exports as LLTV, Liquidity_exports as Liquidity, Logger_exports as Logger, Maturity_exports as Maturity, mempool_exports as Mempool, Obligation_exports as Obligation, ObligationResponse_exports as ObligationResponse, ObligationsController, Offer_exports as Offer, OfferResponse_exports as OfferResponse, OffersController, drizzle_exports as OffersSchema, OpenApi, Oracle_exports as Oracle, Position_exports as Position, PositionResponse_exports as PositionResponse, Quote_exports as Quote, RouterApi_exports as RouterApi, Client_exports$1 as RouterClient, RouterStatusResponse, Rules_exports as Rules, Tick_exports as Tick, time_exports as Time, TradingFee_exports as TradingFee, Transfer_exports as Transfer, Tree_exports as Tree, UsersController, utils_exports as Utils, ValidateController, Gate_exports as Validation, morphoRules, parse, safeParse };
12232
+ export { Abi_exports as Abi, BookResponse_exports as BookResponse, BooksController, BrandTypeId, Callback_exports as Callback, Chain_exports as Chain, ChainHealth, ChainRegistry_exports as ChainRegistry, ChainsHealthResponse, Collateral_exports as Collateral, CollectorHealth, CollectorsHealthResponse, ConfigContractsController, ConfigRulesController, Database_exports as Database, ERC4626_exports as ERC4626, Errors_exports as Errors, Format_exports as Format, Gatekeeper_exports as Gatekeeper, Client_exports as GatekeeperClient, Health_exports as Health, HealthController, Id_exports as Id, Indexer_exports as Indexer, LLTV_exports as LLTV, Liquidity_exports as Liquidity, Logger_exports as Logger, Maturity_exports as Maturity, mempool_exports as Mempool, Obligation_exports as Obligation, ObligationResponse_exports as ObligationResponse, ObligationsController, Offer_exports as Offer, OfferResponse_exports as OfferResponse, OffersController, drizzle_exports as OffersSchema, OpenApi, Oracle_exports as Oracle, Position_exports as Position, PositionResponse_exports as PositionResponse, Quote_exports as Quote, RouterApi_exports as RouterApi, Client_exports$1 as RouterClient, RouterStatusResponse, Rules_exports as Rules, Tick_exports as Tick, time_exports as Time, TradingFee_exports as TradingFee, Transfer_exports as Transfer, Tree_exports as Tree, UsersController, utils_exports as Utils, ValidateController, Gate_exports as Validation, morphoRules, parse, safeParse };
11306
12233
  //# sourceMappingURL=index.node.mjs.map