@morpho-dev/router 0.7.1 → 0.8.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.
Files changed (70) hide show
  1. package/dist/cli.js +441 -141
  2. package/dist/drizzle/migrations/0000_setup_single_migration_folder.sql +64 -64
  3. package/dist/drizzle/migrations/0001_add-trigger-for-consumed-events.sql +5 -5
  4. package/dist/drizzle/migrations/0002_insert-status-code.sql +1 -1
  5. package/dist/drizzle/migrations/0003_update-triggers-for-consumed-events.sql +1 -1
  6. package/dist/drizzle/migrations/0004_drop-status-offers-foreign-key-constraint.sql +1 -1
  7. package/dist/drizzle/migrations/0005_add-index-to-boost-group-query-and-offer-hash.sql +1 -1
  8. package/dist/drizzle/migrations/0006_add-callbacks-and-positions-relations.sql +11 -11
  9. package/dist/drizzle/migrations/0008_validation.sql +10 -10
  10. package/dist/drizzle/migrations/0009_add-transfers-table.sql +4 -4
  11. package/dist/drizzle/migrations/0010_add-price.sql +1 -1
  12. package/dist/drizzle/migrations/0011_nullable-callback-amount.sql +1 -1
  13. package/dist/drizzle/migrations/0012_add-position-asset.sql +1 -1
  14. package/dist/drizzle/migrations/0013_remove-depecrated-domains.sql +13 -13
  15. package/dist/drizzle/migrations/0014_rename-offers-v2-into-offers.sql +19 -19
  16. package/dist/drizzle/migrations/0015_add-lots-table.sql +3 -3
  17. package/dist/drizzle/migrations/0016_merkle-metadata.sql +7 -7
  18. package/dist/drizzle/migrations/0017_dusty_the_hunter.sql +1 -1
  19. package/dist/drizzle/migrations/0018_add_chain_collector_constraints.sql +3 -3
  20. package/dist/drizzle/migrations/0019_add-obligation-units-shares.sql +2 -2
  21. package/dist/drizzle/migrations/0020_add-session.sql +1 -1
  22. package/dist/drizzle/migrations/0021_drop_chain_collector_epoch_indexes.sql +2 -2
  23. package/dist/drizzle/migrations/0021_migrate-rate-to-price.sql +6 -6
  24. package/dist/drizzle/migrations/0022_consolidate-price.sql +5 -5
  25. package/dist/drizzle/migrations/0023_remove-block-number-for-collaterals.sql +1 -1
  26. package/dist/drizzle/migrations/0024_add-obligation-id-to-lots.sql +8 -0
  27. package/dist/drizzle/migrations/0025_rename-price-to-tick.sql +202 -0
  28. package/dist/drizzle/migrations/meta/0000_snapshot.json +48 -48
  29. package/dist/drizzle/migrations/meta/0001_snapshot.json +48 -48
  30. package/dist/drizzle/migrations/meta/0002_snapshot.json +48 -48
  31. package/dist/drizzle/migrations/meta/0003_snapshot.json +48 -48
  32. package/dist/drizzle/migrations/meta/0004_snapshot.json +47 -47
  33. package/dist/drizzle/migrations/meta/0005_snapshot.json +47 -47
  34. package/dist/drizzle/migrations/meta/0006_snapshot.json +61 -61
  35. package/dist/drizzle/migrations/meta/0008_snapshot.json +62 -62
  36. package/dist/drizzle/migrations/meta/0009_snapshot.json +66 -66
  37. package/dist/drizzle/migrations/meta/0010_snapshot.json +66 -66
  38. package/dist/drizzle/migrations/meta/0013_snapshot.json +48 -48
  39. package/dist/drizzle/migrations/meta/0014_snapshot.json +48 -48
  40. package/dist/drizzle/migrations/meta/0015_snapshot.json +52 -52
  41. package/dist/drizzle/migrations/meta/0016_snapshot.json +61 -61
  42. package/dist/drizzle/migrations/meta/0017_snapshot.json +61 -61
  43. package/dist/drizzle/migrations/meta/0018_snapshot.json +62 -62
  44. package/dist/drizzle/migrations/meta/0019_snapshot.json +62 -62
  45. package/dist/drizzle/migrations/meta/0023_snapshot.json +62 -62
  46. package/dist/drizzle/migrations/meta/0024_snapshot.json +1448 -0
  47. package/dist/drizzle/migrations/meta/0025_snapshot.json +1448 -0
  48. package/dist/drizzle/migrations/meta/_journal.json +14 -0
  49. package/dist/evm/bytecode/erc20.txt +1 -1
  50. package/dist/evm/bytecode/morpho.txt +1 -1
  51. package/dist/evm/bytecode/multicall3.txt +1 -1
  52. package/dist/evm/bytecode/oracle.txt +1 -1
  53. package/dist/evm/bytecode/vault.txt +1 -1
  54. package/dist/index.browser.d.mts +1157 -81
  55. package/dist/index.browser.d.mts.map +1 -1
  56. package/dist/index.browser.d.ts +1157 -81
  57. package/dist/index.browser.d.ts.map +1 -1
  58. package/dist/index.browser.js +371 -151
  59. package/dist/index.browser.js.map +1 -1
  60. package/dist/index.browser.mjs +366 -152
  61. package/dist/index.browser.mjs.map +1 -1
  62. package/dist/index.node.d.mts +1196 -70
  63. package/dist/index.node.d.mts.map +1 -1
  64. package/dist/index.node.d.ts +1196 -70
  65. package/dist/index.node.d.ts.map +1 -1
  66. package/dist/index.node.js +491 -145
  67. package/dist/index.node.js.map +1 -1
  68. package/dist/index.node.mjs +486 -146
  69. package/dist/index.node.mjs.map +1 -1
  70. package/package.json +1 -1
@@ -991,6 +991,61 @@ const MetaMorpho = (0, viem.parseAbi)([
991
991
  //#region src/core/Abi/MetaMorphoFactory.ts
992
992
  const MetaMorphoFactory = (0, viem.parseAbi)(["event CreateMetaMorpho(address indexed metaMorpho,address indexed caller,address initialOwner,uint256 initialTimelock,address indexed asset,string name,string symbol,bytes32 salt)", "function isMetaMorpho(address) view returns (bool)"]);
993
993
 
994
+ //#endregion
995
+ //#region src/core/Abi/MorphoV2.ts
996
+ const MorphoV2 = (0, viem.parseAbi)([
997
+ "constructor()",
998
+ "function collateralOf(bytes32 id, address user, address collateralToken) view returns (uint256)",
999
+ "function consume(bytes32 group, uint256 amount)",
1000
+ "function consumed(address user, bytes32 group) view returns (uint256)",
1001
+ "function debtOf(bytes32 id, address user) view returns (uint256)",
1002
+ "function defaultFees(address loanToken, uint256 index) view returns (uint16)",
1003
+ "function feeSetter() view returns (address)",
1004
+ "function fees(bytes32 id) view returns (uint16[6])",
1005
+ "function flashLoan(address token, uint256 assets, address callback, bytes data)",
1006
+ "function isHealthy((address loanToken, (address token, uint256 lltv, address oracle)[] collaterals, uint256 maturity) obligation, bytes32 id, address borrower) view returns (bool)",
1007
+ "function liquidate((address loanToken, (address token, uint256 lltv, address oracle)[] collaterals, uint256 maturity) obligation, (uint256 collateralIndex, uint256 repaid, uint256 seized)[] seizures, address borrower, bytes data) returns ((uint256 collateralIndex, uint256 repaid, uint256 seized)[])",
1008
+ "function multicall(bytes[] calls)",
1009
+ "function obligationCreated(bytes32 id) view returns (bool)",
1010
+ "function obligationState(bytes32 id) view returns (uint128 totalUnits, uint128 totalShares, uint256 withdrawable, bool created)",
1011
+ "function owner() view returns (address)",
1012
+ "function repay((address loanToken, (address token, uint256 lltv, address oracle)[] collaterals, uint256 maturity) obligation, uint256 obligationUnits, address onBehalf)",
1013
+ "function session(address user) view returns (bytes32)",
1014
+ "function setDefaultTradingFee(address loanToken, uint256 index, uint256 newTradingFee)",
1015
+ "function setFeeSetter(address newFeeSetter)",
1016
+ "function setObligationTradingFee(bytes32 id, uint256 index, uint256 newTradingFee)",
1017
+ "function setOwner(address newOwner)",
1018
+ "function setTradingFeeRecipient(address recipient)",
1019
+ "function sharesOf(bytes32 id, address user) view returns (uint256)",
1020
+ "function shuffleSession()",
1021
+ "function supplyCollateral((address loanToken, (address token, uint256 lltv, address oracle)[] collaterals, uint256 maturity) obligation, address collateral, uint256 assets, address onBehalf)",
1022
+ "function take(uint256 buyerAssets, uint256 sellerAssets, uint256 obligationUnits, uint256 obligationShares, address taker, ((address loanToken, (address token, uint256 lltv, address oracle)[] collaterals, uint256 maturity) obligation, bool buy, address maker, uint256 assets, uint256 obligationUnits, uint256 obligationShares, uint256 start, uint256 expiry, uint256 tick, bytes32 group, bytes32 session, address callback, bytes callbackData) offer, (uint8 v, bytes32 r, bytes32 s) sig, bytes32 root, bytes32[] proof, address takerCallback, bytes takerCallbackData) returns (uint256, uint256, uint256, uint256)",
1023
+ "function totalShares(bytes32 id) view returns (uint256)",
1024
+ "function totalUnits(bytes32 id) view returns (uint256)",
1025
+ "function touchObligation((address loanToken, (address token, uint256 lltv, address oracle)[] collaterals, uint256 maturity) obligation) returns (bytes32)",
1026
+ "function tradingFee(bytes32 id, uint256 timeToMaturity) view returns (uint256)",
1027
+ "function tradingFeeRecipient() view returns (address)",
1028
+ "function withdraw((address loanToken, (address token, uint256 lltv, address oracle)[] collaterals, uint256 maturity) obligation, uint256 obligationUnits, uint256 shares, address onBehalf) returns (uint256, uint256)",
1029
+ "function withdrawCollateral((address loanToken, (address token, uint256 lltv, address oracle)[] collaterals, uint256 maturity) obligation, address collateral, uint256 assets, address onBehalf)",
1030
+ "function withdrawable(bytes32 id) view returns (uint256)",
1031
+ "event Constructor(address indexed owner)",
1032
+ "event Consume(address indexed user, bytes32 indexed group, uint256 amount)",
1033
+ "event FlashLoan(address indexed caller, address indexed token, uint256 assets)",
1034
+ "event Liquidate(address indexed caller, bytes32 indexed id, (uint256 collateralIndex, uint256 repaid, uint256 seized)[] seizures, address indexed borrower, uint256 totalRepaid, uint256 badDebt)",
1035
+ "event ObligationCreated(bytes32 indexed id, (address loanToken, (address token, uint256 lltv, address oracle)[] collaterals, uint256 maturity) obligation)",
1036
+ "event Repay(address indexed caller, bytes32 indexed id, uint256 obligationUnits, address indexed onBehalf)",
1037
+ "event SetDefaultTradingFee(address indexed loanToken, uint256 indexed index, uint256 newTradingFee)",
1038
+ "event SetFeeSetter(address indexed feeSetter)",
1039
+ "event SetObligationTradingFee(bytes32 indexed id, uint256 indexed index, uint256 newTradingFee)",
1040
+ "event SetOwner(address indexed owner)",
1041
+ "event SetTradingFeeRecipient(address indexed recipient)",
1042
+ "event ShuffleSession(address indexed user, bytes32 session)",
1043
+ "event SupplyCollateral(address caller, bytes32 indexed id, address indexed collateral, uint256 assets, address indexed onBehalf)",
1044
+ "event Take(address caller, bytes32 indexed id, address indexed maker, address indexed taker, bool offerIsBuy, uint256 buyerAssets, uint256 sellerAssets, uint256 obligationUnits, uint256 obligationShares, bool buyerIsLender, bool sellerIsBorrower, bytes32 group, uint256 consumed)",
1045
+ "event Withdraw(address indexed caller, bytes32 indexed id, uint256 obligationUnits, uint256 shares, address indexed onBehalf)",
1046
+ "event WithdrawCollateral(address caller, bytes32 indexed id, address indexed collateral, uint256 assets, address indexed onBehalf)"
1047
+ ]);
1048
+
994
1049
  //#endregion
995
1050
  //#region src/core/Abi/index.ts
996
1051
  var Abi_exports = /* @__PURE__ */ __exportAll({
@@ -998,6 +1053,7 @@ var Abi_exports = /* @__PURE__ */ __exportAll({
998
1053
  MetaMorpho: () => MetaMorpho,
999
1054
  MetaMorphoFactory: () => MetaMorphoFactory,
1000
1055
  Morpho: () => Morpho,
1056
+ MorphoV2: () => MorphoV2,
1001
1057
  Oracle: () => Oracle
1002
1058
  });
1003
1059
  const Oracle = [{
@@ -1259,8 +1315,8 @@ const chains$2 = {
1259
1315
  name: "ethereum-virtual-testnet",
1260
1316
  custom: {
1261
1317
  morpho: {
1262
- address: "0x11a002d45db720ed47a80d2f3489cba5b833eaf5",
1263
- blockCreated: 0
1318
+ address: "0x634b095371e4e45feed94c1a45c37798e173ea50",
1319
+ blockCreated: 23226700
1264
1320
  },
1265
1321
  morphoBlue: {
1266
1322
  address: "0xBBBBBbbBBb9cC5e90e3b3Af64bdAF62C37EEFFCb",
@@ -2066,7 +2122,7 @@ const OfferSchema = () => {
2066
2122
  assets: zod.bigint({ coerce: true }).min(0n).max(viem.maxUint256),
2067
2123
  obligationUnits: zod.bigint({ coerce: true }).min(0n).max(viem.maxUint256).optional().default(0n),
2068
2124
  obligationShares: zod.bigint({ coerce: true }).min(0n).max(viem.maxUint256).optional().default(0n),
2069
- price: zod.bigint({ coerce: true }).min(0n).max(viem.maxUint256),
2125
+ tick: zod.coerce.number().int().min(0).max(990),
2070
2126
  maturity: MaturitySchema,
2071
2127
  expiry: zod.number().int().max(Number.MAX_SAFE_INTEGER),
2072
2128
  start: zod.number().int().max(Number.MAX_SAFE_INTEGER),
@@ -2138,7 +2194,7 @@ const serialize = (offer) => ({
2138
2194
  assets: offer.assets.toString(),
2139
2195
  obligationUnits: offer.obligationUnits.toString(),
2140
2196
  obligationShares: offer.obligationShares.toString(),
2141
- price: offer.price.toString(),
2197
+ tick: offer.tick,
2142
2198
  maturity: Number(offer.maturity),
2143
2199
  expiry: Number(offer.expiry),
2144
2200
  start: Number(offer.start),
@@ -2183,14 +2239,13 @@ function random$1(config) {
2183
2239
  [.98, 2]
2184
2240
  ]));
2185
2241
  const buy = config?.buy !== void 0 ? config.buy : bool();
2186
- const ONE = 1000000000000000000n;
2187
- const qMin = buy ? 16 : 4;
2188
- const len = (buy ? 32 : 16) - qMin + 1;
2189
- const pricePairs = Array.from({ length: len }, (_, idx) => {
2190
- const q = qMin + idx;
2191
- return [BigInt(q) * (ONE / 4n), buy ? 1 + idx : 1 + (len - 1 - idx)];
2242
+ const tickMin = buy ? 0 : 495;
2243
+ const len = (buy ? 495 : 990) - tickMin + 1;
2244
+ const tickPairs = Array.from({ length: len }, (_, idx) => {
2245
+ const weight = buy ? 1 + idx : 1 + (len - 1 - idx);
2246
+ return [tickMin + idx, weight];
2192
2247
  });
2193
- const price = config?.price ?? weightedChoice(pricePairs);
2248
+ const tick = config?.tick ?? weightedChoice(tickPairs);
2194
2249
  const loanTokenDecimals = config?.assetsDecimals?.[loanToken] ?? 18;
2195
2250
  const unit = BigInt(10) ** BigInt(loanTokenDecimals);
2196
2251
  const amountBase = BigInt(100 + int(999901));
@@ -2204,7 +2259,7 @@ function random$1(config) {
2204
2259
  assets: assetsScaled,
2205
2260
  obligationUnits: config?.obligationUnits ?? 0n,
2206
2261
  obligationShares: config?.obligationShares ?? 0n,
2207
- price,
2262
+ tick,
2208
2263
  maturity,
2209
2264
  expiry: config?.expiry ?? maturity - 1,
2210
2265
  start: config?.start ?? maturity - 10,
@@ -2269,7 +2324,7 @@ const types = {
2269
2324
  type: "uint256"
2270
2325
  },
2271
2326
  {
2272
- name: "price",
2327
+ name: "tick",
2273
2328
  type: "uint256"
2274
2329
  },
2275
2330
  {
@@ -2337,7 +2392,7 @@ function hash(offer) {
2337
2392
  assets: offer.assets,
2338
2393
  obligationUnits: offer.obligationUnits,
2339
2394
  obligationShares: offer.obligationShares,
2340
- price: offer.price,
2395
+ tick: BigInt(offer.tick),
2341
2396
  maturity: BigInt(offer.maturity),
2342
2397
  expiry: BigInt(offer.expiry),
2343
2398
  group: offer.group,
@@ -2388,7 +2443,7 @@ const OfferAbi = [
2388
2443
  type: "uint256"
2389
2444
  },
2390
2445
  {
2391
- name: "price",
2446
+ name: "tick",
2392
2447
  type: "uint256"
2393
2448
  },
2394
2449
  {
@@ -2459,7 +2514,7 @@ function encode$1(offer) {
2459
2514
  offer.assets,
2460
2515
  offer.obligationUnits,
2461
2516
  offer.obligationShares,
2462
- offer.price,
2517
+ BigInt(offer.tick),
2463
2518
  BigInt(offer.maturity),
2464
2519
  BigInt(offer.expiry),
2465
2520
  offer.group,
@@ -2484,7 +2539,7 @@ function decode$1(data) {
2484
2539
  assets: decoded[1],
2485
2540
  obligationUnits: decoded[2],
2486
2541
  obligationShares: decoded[3],
2487
- price: decoded[4],
2542
+ tick: Number(decoded[4]),
2488
2543
  maturity: from$16(Number(decoded[5])),
2489
2544
  expiry: Number(decoded[6]),
2490
2545
  group: decoded[7],
@@ -2834,6 +2889,85 @@ var InvalidQuoteError = class extends BaseError {
2834
2889
  }
2835
2890
  };
2836
2891
 
2892
+ //#endregion
2893
+ //#region src/core/Tick.ts
2894
+ var Tick_exports = /* @__PURE__ */ __exportAll({
2895
+ InvalidPriceError: () => InvalidPriceError,
2896
+ InvalidTickError: () => InvalidTickError,
2897
+ MAX_PRICE: () => MAX_PRICE,
2898
+ TICK_RANGE: () => TICK_RANGE,
2899
+ priceToTick: () => priceToTick,
2900
+ tickToPrice: () => tickToPrice
2901
+ });
2902
+ /** ln(1 + 0.025), scaled by 1e18. Matches TickLib onchain constant. */
2903
+ const LN_ONE_PLUS_DELTA = 24692612590371501n;
2904
+ /** ln(2), scaled by 1e18. Matches TickLib onchain constant. */
2905
+ const LN2 = 693147180559945309n;
2906
+ const WAD$1 = 10n ** 18n;
2907
+ const WAD_SQUARED = 10n ** 36n;
2908
+ const PRICE_STEP = 10n ** 13n;
2909
+ const HALF_TICK_RANGE = 495n;
2910
+ /** Tick domain supported by Morpho V2. */
2911
+ const TICK_RANGE = 990;
2912
+ /** Max allowed price (1e18 in wad). */
2913
+ const MAX_PRICE = WAD$1;
2914
+ /**
2915
+ * Converts a tick to a wad price using the same approximation and rounding as TickLib.
2916
+ * @param tick - Tick value in the inclusive range [0, 990].
2917
+ * @returns The price in wad units.
2918
+ * @throws {@link InvalidTickError} If tick is not an integer in range [0, 990].
2919
+ */
2920
+ function tickToPrice(tick) {
2921
+ assertTick(tick);
2922
+ return divHalfDownUnchecked(divHalfDownUnchecked(WAD_SQUARED, WAD$1 + wExp(LN_ONE_PLUS_DELTA * (HALF_TICK_RANGE - BigInt(tick)))), PRICE_STEP) * PRICE_STEP;
2923
+ }
2924
+ /**
2925
+ * Returns the lowest tick with a higher-or-equal price.
2926
+ * @param price - Price in wad units.
2927
+ * @returns The first tick whose {@link tickToPrice} is greater than or equal to `price`.
2928
+ * @throws {@link InvalidPriceError} If price is outside [0, 1e18].
2929
+ */
2930
+ function priceToTick(price) {
2931
+ assertPrice(price);
2932
+ let low = 0;
2933
+ let high = TICK_RANGE;
2934
+ while (low !== high) {
2935
+ const mid = Math.floor((low + high) / 2);
2936
+ if (tickToPrice(mid) < price) low = mid + 1;
2937
+ else high = mid;
2938
+ }
2939
+ return low;
2940
+ }
2941
+ function divHalfDownUnchecked(x, d) {
2942
+ return (x + (d - 1n) / 2n) / d;
2943
+ }
2944
+ function wExp(x) {
2945
+ if (x < 0n) return WAD_SQUARED / wExp(-x);
2946
+ const q = (x + LN2 / 2n) / LN2;
2947
+ const r = x - q * LN2;
2948
+ const secondTerm = r * r / (2n * WAD$1);
2949
+ const thirdTerm = secondTerm * r / (3n * WAD$1);
2950
+ return WAD$1 + r + secondTerm + thirdTerm << q;
2951
+ }
2952
+ function assertTick(tick) {
2953
+ if (!Number.isInteger(tick) || tick < 0 || tick > TICK_RANGE) throw new InvalidTickError(tick);
2954
+ }
2955
+ function assertPrice(price) {
2956
+ if (price < 0n || price > MAX_PRICE) throw new InvalidPriceError(price);
2957
+ }
2958
+ var InvalidTickError = class extends BaseError {
2959
+ name = "Tick.InvalidTickError";
2960
+ constructor(tick) {
2961
+ super(`Invalid tick: ${tick}. Tick must be an integer between 0 and ${TICK_RANGE}.`);
2962
+ }
2963
+ };
2964
+ var InvalidPriceError = class extends BaseError {
2965
+ name = "Tick.InvalidPriceError";
2966
+ constructor(price) {
2967
+ super(`Invalid price: ${price}. Price must be between 0 and ${MAX_PRICE}.`);
2968
+ }
2969
+ };
2970
+
2837
2971
  //#endregion
2838
2972
  //#region src/core/TradingFee.ts
2839
2973
  var TradingFee_exports = /* @__PURE__ */ __exportAll({
@@ -3347,7 +3481,7 @@ const BrandTypeId = Symbol.for("mempool/Brand");
3347
3481
 
3348
3482
  //#endregion
3349
3483
  //#region src/database/drizzle/VERSION.ts
3350
- const VERSION = "router_v1.6";
3484
+ const VERSION = "router_v1.8";
3351
3485
 
3352
3486
  //#endregion
3353
3487
  //#region src/database/drizzle/schema.ts
@@ -3499,10 +3633,7 @@ const offers = s.table(EnumTableName.OFFERS, {
3499
3633
  precision: 78,
3500
3634
  scale: 0
3501
3635
  }).notNull().default("0"),
3502
- price: (0, drizzle_orm_pg_core.numeric)("price", {
3503
- precision: 78,
3504
- scale: 0
3505
- }).notNull(),
3636
+ tick: (0, drizzle_orm_pg_core.integer)("tick").notNull(),
3506
3637
  maturity: (0, drizzle_orm_pg_core.integer)("maturity").notNull(),
3507
3638
  expiry: (0, drizzle_orm_pg_core.integer)("expiry").notNull(),
3508
3639
  start: (0, drizzle_orm_pg_core.integer)("start").notNull(),
@@ -3567,6 +3698,7 @@ const lots = s.table(EnumTableName.LOTS, {
3567
3698
  user: (0, drizzle_orm_pg_core.varchar)("user", { length: 42 }).notNull(),
3568
3699
  contract: (0, drizzle_orm_pg_core.varchar)("contract", { length: 42 }).notNull(),
3569
3700
  group: (0, drizzle_orm_pg_core.varchar)("group", { length: 66 }).notNull(),
3701
+ obligationId: (0, drizzle_orm_pg_core.varchar)("obligation_id", { length: 66 }).notNull(),
3570
3702
  lower: (0, drizzle_orm_pg_core.numeric)("lower", {
3571
3703
  precision: 78,
3572
3704
  scale: 0
@@ -3581,7 +3713,8 @@ const lots = s.table(EnumTableName.LOTS, {
3581
3713
  table.chainId,
3582
3714
  table.user,
3583
3715
  table.contract,
3584
- table.group
3716
+ table.group,
3717
+ table.obligationId
3585
3718
  ],
3586
3719
  name: "lots_pk"
3587
3720
  }),
@@ -3617,6 +3750,7 @@ const offsets = s.table(EnumTableName.OFFSETS, {
3617
3750
  user: (0, drizzle_orm_pg_core.varchar)("user", { length: 42 }).notNull(),
3618
3751
  contract: (0, drizzle_orm_pg_core.varchar)("contract", { length: 42 }).notNull(),
3619
3752
  group: (0, drizzle_orm_pg_core.varchar)("group", { length: 66 }).notNull(),
3753
+ obligationId: (0, drizzle_orm_pg_core.varchar)("obligation_id", { length: 66 }).notNull(),
3620
3754
  value: (0, drizzle_orm_pg_core.numeric)("value", {
3621
3755
  precision: 78,
3622
3756
  scale: 0
@@ -3626,7 +3760,8 @@ const offsets = s.table(EnumTableName.OFFSETS, {
3626
3760
  table.chainId,
3627
3761
  table.user,
3628
3762
  table.contract,
3629
- table.group
3763
+ table.group,
3764
+ table.obligationId
3630
3765
  ],
3631
3766
  name: "offsets_pk"
3632
3767
  }), (0, drizzle_orm_pg_core.foreignKey)({
@@ -4217,6 +4352,7 @@ function decodeCallbacks(parameters) {
4217
4352
  positionContract: loanToken,
4218
4353
  positionUser: offer.maker,
4219
4354
  group: offer.group,
4355
+ obligationId: obligationId(offer),
4220
4356
  size: offer.assets
4221
4357
  });
4222
4358
  callbacks.push({
@@ -5155,8 +5291,10 @@ async function getRemoteBlockNumbers(healthClients) {
5155
5291
  //#region src/api/Schema/BookResponse.ts
5156
5292
  var BookResponse_exports = /* @__PURE__ */ __exportAll({ from: () => from$5 });
5157
5293
  function from$5(level) {
5294
+ const price = tickToPrice(level.tick);
5158
5295
  return {
5159
- price: level.price.toString(),
5296
+ tick: level.tick,
5297
+ price: price.toString(),
5160
5298
  assets: level.assets.toString(),
5161
5299
  count: level.count
5162
5300
  };
@@ -5224,6 +5362,16 @@ function from$4(obligation, quote) {
5224
5362
  //#endregion
5225
5363
  //#region src/api/Schema/OfferResponse.ts
5226
5364
  var OfferResponse_exports = /* @__PURE__ */ __exportAll({ from: () => from$3 });
5365
+ function normalizeChainId(chainId) {
5366
+ const parsedChainId = Number(chainId);
5367
+ if (!Number.isInteger(parsedChainId) || parsedChainId <= 0) throw new Error(`Invalid chain id: ${String(chainId)}`);
5368
+ return parsedChainId;
5369
+ }
5370
+ function normalizeBlockNumber(blockNumber) {
5371
+ const parsedBlockNumber = Number(blockNumber);
5372
+ if (!Number.isInteger(parsedBlockNumber) || parsedBlockNumber < 0) throw new Error(`Invalid block number: ${String(blockNumber)}`);
5373
+ return parsedBlockNumber;
5374
+ }
5227
5375
  /**
5228
5376
  * Creates an `OfferResponse` matching the Solidity Offer struct layout.
5229
5377
  * @constructor
@@ -5231,6 +5379,8 @@ var OfferResponse_exports = /* @__PURE__ */ __exportAll({ from: () => from$3 });
5231
5379
  * @returns The created `OfferResponse`. {@link OfferResponse}
5232
5380
  */
5233
5381
  function from$3(input) {
5382
+ const chainId = normalizeChainId(input.chainId);
5383
+ const blockNumber = normalizeBlockNumber(input.blockNumber);
5234
5384
  const base = {
5235
5385
  offer: {
5236
5386
  obligation: {
@@ -5249,7 +5399,7 @@ function from$3(input) {
5249
5399
  obligation_shares: input.obligationShares.toString(),
5250
5400
  start: input.start,
5251
5401
  expiry: input.expiry,
5252
- price: input.price.toString(),
5402
+ tick: input.tick,
5253
5403
  group: input.group,
5254
5404
  session: input.session,
5255
5405
  callback: input.callback.address,
@@ -5257,15 +5407,15 @@ function from$3(input) {
5257
5407
  },
5258
5408
  offer_hash: input.hash,
5259
5409
  obligation_id: id({
5260
- chainId: input.chainId,
5410
+ chainId,
5261
5411
  loanToken: input.loanToken,
5262
5412
  collaterals: [...input.collaterals],
5263
5413
  maturity: input.maturity
5264
5414
  }),
5265
- chain_id: input.chainId,
5415
+ chain_id: chainId,
5266
5416
  consumed: input.consumed.toString(),
5267
5417
  takeable: input.takeable.toString(),
5268
- block_number: input.blockNumber
5418
+ block_number: blockNumber
5269
5419
  };
5270
5420
  if (!input.proof || !input.root || !input.signature) return {
5271
5421
  ...base,
@@ -5411,7 +5561,7 @@ const offerExample = {
5411
5561
  obligation_shares: "0",
5412
5562
  start: 1761922790,
5413
5563
  expiry: 1761922799,
5414
- price: "2750000000000000000",
5564
+ tick: 495,
5415
5565
  group: "0x000000000000000000000000000000000000000000000000000000000008b8f4",
5416
5566
  session: "0x0000000000000000000000000000000000000000000000000000000000000000",
5417
5567
  callback: "0x0000000000000000000000000000000000000000",
@@ -5452,7 +5602,7 @@ const validateOfferExample = {
5452
5602
  assets: "369216000000000000000000",
5453
5603
  obligation_units: "0",
5454
5604
  obligation_shares: "0",
5455
- price: "2750000000000000000",
5605
+ tick: 495,
5456
5606
  maturity: 1761922799,
5457
5607
  expiry: 1761922799,
5458
5608
  start: 1761922790,
@@ -5596,9 +5746,11 @@ __decorate([(0, openapi_metadata_decorators.ApiProperty)({
5596
5746
  example: offerExample.offer.expiry
5597
5747
  })], OfferDataResponse.prototype, "expiry", void 0);
5598
5748
  __decorate([(0, openapi_metadata_decorators.ApiProperty)({
5599
- type: "string",
5600
- example: offerExample.offer.price
5601
- })], OfferDataResponse.prototype, "price", void 0);
5749
+ type: "number",
5750
+ example: offerExample.offer.tick,
5751
+ minimum: 0,
5752
+ maximum: 990
5753
+ })], OfferDataResponse.prototype, "tick", void 0);
5602
5754
  __decorate([(0, openapi_metadata_decorators.ApiProperty)({
5603
5755
  type: "string",
5604
5756
  example: offerExample.offer.group
@@ -5839,9 +5991,11 @@ __decorate([(0, openapi_metadata_decorators.ApiProperty)({
5839
5991
  required: false
5840
5992
  })], ValidateOfferRequest.prototype, "obligation_shares", void 0);
5841
5993
  __decorate([(0, openapi_metadata_decorators.ApiProperty)({
5842
- type: "string",
5843
- example: validateOfferExample.price
5844
- })], ValidateOfferRequest.prototype, "price", void 0);
5994
+ type: "number",
5995
+ example: validateOfferExample.tick,
5996
+ minimum: 0,
5997
+ maximum: 990
5998
+ })], ValidateOfferRequest.prototype, "tick", void 0);
5845
5999
  __decorate([(0, openapi_metadata_decorators.ApiProperty)({
5846
6000
  type: "number",
5847
6001
  example: validateOfferExample.maturity
@@ -5941,9 +6095,16 @@ __decorate([(0, openapi_metadata_decorators.ApiProperty)({
5941
6095
  description: "List of validation issues. Returned when any offer fails validation."
5942
6096
  })], ValidationFailureResponse.prototype, "data", void 0);
5943
6097
  var BookLevelResponse = class {};
6098
+ __decorate([(0, openapi_metadata_decorators.ApiProperty)({
6099
+ type: "number",
6100
+ example: 495,
6101
+ minimum: 0,
6102
+ maximum: 990
6103
+ })], BookLevelResponse.prototype, "tick", void 0);
5944
6104
  __decorate([(0, openapi_metadata_decorators.ApiProperty)({
5945
6105
  type: "string",
5946
- example: "2750000000000000000"
6106
+ example: "500000000000000000",
6107
+ description: "Price derived from tick, scaled by 1e18."
5947
6108
  })], BookLevelResponse.prototype, "price", void 0);
5948
6109
  __decorate([(0, openapi_metadata_decorators.ApiProperty)({
5949
6110
  type: "string",
@@ -5957,6 +6118,7 @@ const positionExample = {
5957
6118
  chain_id: 1,
5958
6119
  contract: "0xC9A9C45C0eB717f8b5F193Af6bAa05A1c0Ac5078",
5959
6120
  user: "0x7b093658BE7f90B63D7c359e8f408e503c2D9401",
6121
+ obligation_id: "0x12590ae1aee324a005be565f3bcdd16dbf8daf7969b26c181c8b8f467dad9f67",
5960
6122
  reserved: "200000000000000000000",
5961
6123
  block_number: 21345678
5962
6124
  };
@@ -5973,6 +6135,12 @@ __decorate([(0, openapi_metadata_decorators.ApiProperty)({
5973
6135
  type: "string",
5974
6136
  example: positionExample.user
5975
6137
  })], PositionListItemResponse.prototype, "user", void 0);
6138
+ __decorate([(0, openapi_metadata_decorators.ApiProperty)({
6139
+ type: "string",
6140
+ nullable: true,
6141
+ example: positionExample.obligation_id,
6142
+ description: "Obligation id this reserved amount belongs to, or null if no lots exist."
6143
+ })], PositionListItemResponse.prototype, "obligation_id", void 0);
5976
6144
  __decorate([(0, openapi_metadata_decorators.ApiProperty)({
5977
6145
  type: "string",
5978
6146
  example: positionExample.reserved
@@ -6000,7 +6168,7 @@ __decorate([(0, openapi_metadata_decorators.ApiProperty)({
6000
6168
  })], BookListResponse.prototype, "cursor", void 0);
6001
6169
  __decorate([(0, openapi_metadata_decorators.ApiProperty)({
6002
6170
  type: () => [BookLevelResponse],
6003
- description: "Aggregated book levels grouped by computed price."
6171
+ description: "Aggregated book levels grouped by offer tick."
6004
6172
  })], BookListResponse.prototype, "data", void 0);
6005
6173
  let BooksController = class BooksController {
6006
6174
  async getBook() {}
@@ -6010,7 +6178,7 @@ __decorate([
6010
6178
  methods: ["get"],
6011
6179
  path: "/v1/books/{obligationId}/{side}",
6012
6180
  summary: "Get aggregated book",
6013
- description: "Returns aggregated book data for a given obligation and side. Offers are grouped by computed price with summed takeable amounts. Book levels are sorted by price (ascending for buy side, descending for sell side)."
6181
+ description: "Returns aggregated book data for a given obligation and side. Offers are grouped by tick with summed takeable amounts, and each level includes the corresponding wad-scaled price. Book levels are sorted by tick (ascending for sell side, descending for buy side)."
6014
6182
  }),
6015
6183
  (0, openapi_metadata_decorators.ApiParam)({
6016
6184
  name: "obligationId",
@@ -6035,7 +6203,7 @@ __decorate([
6035
6203
  name: "limit",
6036
6204
  type: "number",
6037
6205
  example: 10,
6038
- description: "Maximum number of price levels to return."
6206
+ description: "Maximum number of tick levels to return."
6039
6207
  }),
6040
6208
  (0, openapi_metadata_decorators.ApiResponse)({
6041
6209
  status: 200,
@@ -6229,6 +6397,11 @@ const configRulesLoanTokenExample = {
6229
6397
  chain_id: 1,
6230
6398
  address: "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48"
6231
6399
  };
6400
+ const configRulesCollateralTokenExample = {
6401
+ type: "collateral_token",
6402
+ chain_id: 1,
6403
+ address: "0x7f39C581F595B53c5cb19bD0b3f8dA6c935E2Ca0"
6404
+ };
6232
6405
  const configRulesOracleExample = {
6233
6406
  type: "oracle",
6234
6407
  chain_id: 1,
@@ -6238,6 +6411,7 @@ const configRulesChecksumExample = "f1d2d2f924e986ac86fdf7b36c94bcdf";
6238
6411
  const configRulesPayloadExample = [
6239
6412
  configRulesMaturityExample,
6240
6413
  configRulesLoanTokenExample,
6414
+ configRulesCollateralTokenExample,
6241
6415
  configRulesOracleExample
6242
6416
  ];
6243
6417
  const configContractNames = [
@@ -6364,7 +6538,7 @@ __decorate([
6364
6538
  methods: ["get"],
6365
6539
  path: "/v1/config/rules",
6366
6540
  summary: "Get config rules",
6367
- description: "Returns configured rules (maturities, loan tokens, oracles) for supported chains."
6541
+ description: "Returns configured rules (maturities, loan tokens, collateral tokens, oracles) for supported chains."
6368
6542
  }),
6369
6543
  (0, openapi_metadata_decorators.ApiQuery)({
6370
6544
  name: "cursor",
@@ -6384,7 +6558,7 @@ __decorate([
6384
6558
  name: "types",
6385
6559
  type: ["string"],
6386
6560
  required: false,
6387
- example: "maturity,loan_token,oracle",
6561
+ example: "maturity,loan_token,collateral_token,oracle",
6388
6562
  description: "Filter by rule types (comma-separated).",
6389
6563
  style: "form",
6390
6564
  explode: false
@@ -6502,7 +6676,7 @@ __decorate([
6502
6676
  methods: ["get"],
6503
6677
  path: "/v1/users/{userAddress}/positions",
6504
6678
  summary: "Get user positions",
6505
- description: "Returns positions for a user with reserved balance. The reserved balance is the amount locked by active offers (max lot upper - offset - consumed)."
6679
+ description: "Returns positions for a user with reserved balance per obligation. Each (position, obligation) pair is returned as a separate row. Positions with no lots return a single row with obligation_id = null and reserved = 0."
6506
6680
  }),
6507
6681
  (0, openapi_metadata_decorators.ApiParam)({
6508
6682
  name: "userAddress",
@@ -6590,6 +6764,7 @@ function from$2(position) {
6590
6764
  chain_id: position.chainId,
6591
6765
  contract: position.contract,
6592
6766
  user: position.user,
6767
+ obligation_id: position.obligationId,
6593
6768
  reserved: position.reserved.toString(),
6594
6769
  block_number: position.blockNumber
6595
6770
  };
@@ -6643,10 +6818,11 @@ const ConfigRuleTypes = zod.enum([
6643
6818
  "maturity",
6644
6819
  "callback",
6645
6820
  "loan_token",
6821
+ "collateral_token",
6646
6822
  "oracle"
6647
6823
  ]);
6648
6824
  const GetConfigRulesQueryParams = zod.object({
6649
- cursor: zod.string().regex(/^(maturity|callback|loan_token|oracle):[1-9]\d*:.+$/, { message: "Cursor must be in the format type:chain_id:<value>" }).optional().meta({
6825
+ cursor: zod.string().regex(/^(maturity|callback|loan_token|collateral_token|oracle):[1-9]\d*:.+$/, { message: "Cursor must be in the format type:chain_id:<value>" }).optional().meta({
6650
6826
  description: "Pagination cursor in type:chain_id:<value> format",
6651
6827
  example: "maturity:1:1730415600:end_of_next_month"
6652
6828
  }),
@@ -6656,7 +6832,7 @@ const GetConfigRulesQueryParams = zod.object({
6656
6832
  }),
6657
6833
  types: csvArray(ConfigRuleTypes).meta({
6658
6834
  description: "Filter by rule types (comma-separated).",
6659
- example: "maturity,loan_token,oracle"
6835
+ example: "maturity,loan_token,collateral_token,oracle"
6660
6836
  }),
6661
6837
  chains: csvArray(zod.string().regex(/^[1-9]\d*$/, { message: "Chain must be a positive integer" }).transform((val) => Number.parseInt(val, 10))).meta({
6662
6838
  description: "Filter by chain IDs (comma-separated).",
@@ -6756,12 +6932,11 @@ const GetObligationParams = zod.object({ obligation_id: zod.string({ error: "Obl
6756
6932
  description: "Obligation id",
6757
6933
  example: "0x1234567890123456789012345678901234567890123456789012345678901234"
6758
6934
  }) });
6759
- /** Validate a book cursor format: {side, lastPrice, offersCursor} */
6935
+ /** Validate a book cursor format: {side, lastTick, offersCursor} */
6760
6936
  function isValidBookCursor(cursorString) {
6761
- const isNumericString = (value) => typeof value === "string" && /^-?\d+$/.test(value);
6762
6937
  try {
6763
6938
  const v = JSON.parse(Buffer.from(cursorString, "base64url").toString("utf8"));
6764
- return (v?.side === "buy" || v?.side === "sell") && isNumericString(v?.lastPrice) && (v?.offersCursor === null || typeof v?.offersCursor === "string");
6939
+ return (v?.side === "buy" || v?.side === "sell") && typeof v?.lastTick === "number" && Number.isInteger(v.lastTick) && (v?.offersCursor === null || typeof v?.offersCursor === "string");
6765
6940
  } catch {
6766
6941
  return false;
6767
6942
  }
@@ -6835,12 +7010,34 @@ async function getBook(params, db) {
6835
7010
  if (!result.success) return failure(result.error);
6836
7011
  const query = result.data;
6837
7012
  try {
7013
+ logger.debug({
7014
+ service: "api_controller",
7015
+ endpoint: "get_book",
7016
+ msg: "Loading book levels",
7017
+ obligation_id: query.obligation_id,
7018
+ side: query.side,
7019
+ limit: query.limit ?? null,
7020
+ has_cursor: query.cursor != null
7021
+ });
6838
7022
  const { levels, nextCursor } = await db.book.get({
6839
7023
  side: query.side,
6840
7024
  obligationId: query.obligation_id,
6841
7025
  cursor: query.cursor,
6842
7026
  limit: query.limit
6843
7027
  });
7028
+ const firstLevel = levels[0];
7029
+ logger.debug({
7030
+ service: "api_controller",
7031
+ endpoint: "get_book",
7032
+ msg: "Loaded book levels",
7033
+ obligation_id: query.obligation_id,
7034
+ side: query.side,
7035
+ levels_count: levels.length,
7036
+ has_next_cursor: nextCursor != null,
7037
+ first_level_tick: firstLevel?.tick ?? null,
7038
+ first_level_assets: firstLevel?.assets.toString() ?? null,
7039
+ first_level_count: firstLevel?.count ?? null
7040
+ });
6844
7041
  return success({
6845
7042
  data: levels.map(from$5),
6846
7043
  cursor: nextCursor
@@ -6989,6 +7186,33 @@ const assets = {
6989
7186
  "0x2260FAC5E5542a773Aa44fBCfeDf7C193bc2C599"
6990
7187
  ]
6991
7188
  };
7189
+ const collateralAssets = {
7190
+ [ChainId.ETHEREUM.toString()]: [
7191
+ "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
7192
+ "0x1aBaEA1f7C830bD89Acc67eC4af516284b1bC33c",
7193
+ "0x2260FAC5E5542a773Aa44fBCfeDf7C193bc2C599",
7194
+ "0x7f39C581F595B53c5cb19bD0b3f8dA6c935E2Ca0"
7195
+ ],
7196
+ [ChainId.BASE.toString()]: [
7197
+ "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
7198
+ "0x4200000000000000000000000000000000000006",
7199
+ "0xcbB7C0000aB88B473b1f5aFd9ef808440eed33Bf",
7200
+ "0xc1CBa3fCea344f92D9239c08C0568f6F2F0ee452",
7201
+ "0x60a3E35Cc302bFA44Cb288Bc5a4F316Fdb1adb42"
7202
+ ],
7203
+ [ChainId["ETHEREUM-VIRTUAL-TESTNET"].toString()]: [
7204
+ "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
7205
+ "0x1aBaEA1f7C830bD89Acc67eC4af516284b1bC33c",
7206
+ "0x2260FAC5E5542a773Aa44fBCfeDf7C193bc2C599",
7207
+ "0x7f39C581F595B53c5cb19bD0b3f8dA6c935E2Ca0"
7208
+ ],
7209
+ [ChainId.ANVIL.toString()]: [
7210
+ "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
7211
+ "0x1aBaEA1f7C830bD89Acc67eC4af516284b1bC33c",
7212
+ "0x2260FAC5E5542a773Aa44fBCfeDf7C193bc2C599",
7213
+ "0x7f39C581F595B53c5cb19bD0b3f8dA6c935E2Ca0"
7214
+ ]
7215
+ };
6992
7216
  const oracles = {
6993
7217
  [ChainId.ETHEREUM.toString()]: [
6994
7218
  "0xDddd770BADd886dF3864029e4B377B5F6a2B6b83",
@@ -7048,7 +7272,7 @@ const configs = {
7048
7272
  //#endregion
7049
7273
  //#region src/gatekeeper/ConfigRules.ts
7050
7274
  /**
7051
- * Build the configured rules (maturities + callback addresses + loan tokens + oracles) for the provided chains.
7275
+ * Build the configured rules (maturities + callback addresses + loan tokens + collateral tokens + oracles) for the provided chains.
7052
7276
  * @param chains - Chains to include in the configured rules.
7053
7277
  * @returns Sorted list of config rules.
7054
7278
  */
@@ -7068,6 +7292,12 @@ function buildConfigRules(chains) {
7068
7292
  chain_id: chain.id,
7069
7293
  address: normalizeAddress(address)
7070
7294
  });
7295
+ const collateralTokens = collateralAssets[chain.id.toString()] ?? [];
7296
+ for (const address of collateralTokens) rules.push({
7297
+ type: "collateral_token",
7298
+ chain_id: chain.id,
7299
+ address: normalizeAddress(address)
7300
+ });
7071
7301
  const oracles$2 = oracles[chain.id.toString()] ?? [];
7072
7302
  for (const address of oracles$2) rules.push({
7073
7303
  type: "oracle",
@@ -7095,6 +7325,10 @@ function buildConfigRulesChecksum(rules) {
7095
7325
  hash.update(`callback:${rule.chain_id}:${rule.callback_type}:${rule.address}\n`);
7096
7326
  continue;
7097
7327
  }
7328
+ if (rule.type === "collateral_token") {
7329
+ hash.update(`collateral_token:${rule.chain_id}:${rule.address}\n`);
7330
+ continue;
7331
+ }
7098
7332
  if (rule.type === "oracle") {
7099
7333
  hash.update(`oracle:${rule.chain_id}:${rule.address}\n`);
7100
7334
  continue;
@@ -7115,6 +7349,7 @@ function compareConfigRules(left, right) {
7115
7349
  return left.address.localeCompare(right.address);
7116
7350
  }
7117
7351
  if (left.type === "loan_token" && right.type === "loan_token") return left.address.localeCompare(right.address);
7352
+ if (left.type === "collateral_token" && right.type === "collateral_token") return left.address.localeCompare(right.address);
7118
7353
  if (left.type === "oracle" && right.type === "oracle") return left.address.localeCompare(right.address);
7119
7354
  return 0;
7120
7355
  }
@@ -7161,6 +7396,7 @@ function formatCursor(rule) {
7161
7396
  if (rule.type === "maturity") return `maturity:${rule.chain_id}:${rule.timestamp}:${rule.name}`;
7162
7397
  if (rule.type === "callback") return `callback:${rule.chain_id}:${rule.callback_type}:${rule.address.toLowerCase()}`;
7163
7398
  if (rule.type === "oracle") return `oracle:${rule.chain_id}:${rule.address.toLowerCase()}`;
7399
+ if (rule.type === "collateral_token") return `collateral_token:${rule.chain_id}:${rule.address.toLowerCase()}`;
7164
7400
  return `loan_token:${rule.chain_id}:${rule.address.toLowerCase()}`;
7165
7401
  }
7166
7402
  function parseCursor(cursor) {
@@ -7193,7 +7429,7 @@ function parseCursor(cursor) {
7193
7429
  address: parseAddress(addressValue, "Cursor address")
7194
7430
  };
7195
7431
  }
7196
- if (type === "loan_token" || type === "oracle") {
7432
+ if (type === "loan_token" || type === "collateral_token" || type === "oracle") {
7197
7433
  const addressValue = rest.join(":");
7198
7434
  if (!addressValue) throw new BadRequestError(`Cursor must be in the format ${type}:chain_id:address`);
7199
7435
  return {
@@ -7220,7 +7456,7 @@ function parseAddress(address, label) {
7220
7456
  return address.toLowerCase();
7221
7457
  }
7222
7458
  function isConfigRuleType(value) {
7223
- return value === "maturity" || value === "callback" || value === "loan_token" || value === "oracle";
7459
+ return value === "maturity" || value === "callback" || value === "loan_token" || value === "collateral_token" || value === "oracle";
7224
7460
  }
7225
7461
  function isMaturityType(value) {
7226
7462
  return Object.values(MaturityType).includes(value);
@@ -7567,7 +7803,7 @@ function create$15(config) {
7567
7803
  assets: offers.assets,
7568
7804
  obligationUnits: offers.obligationUnits,
7569
7805
  obligationShares: offers.obligationShares,
7570
- price: offers.price,
7806
+ tick: offers.tick,
7571
7807
  maturity: offers.maturity,
7572
7808
  expiry: offers.expiry,
7573
7809
  start: offers.start,
@@ -7587,7 +7823,7 @@ function create$15(config) {
7587
7823
  assets: BigInt(row.assets),
7588
7824
  obligationUnits: BigInt(row.obligationUnits),
7589
7825
  obligationShares: BigInt(row.obligationShares),
7590
- price: BigInt(row.price),
7826
+ tick: row.tick,
7591
7827
  maturity: from$16(row.maturity),
7592
7828
  expiry: row.expiry,
7593
7829
  start: row.start,
@@ -7669,8 +7905,8 @@ function create$15(config) {
7669
7905
  const now$2 = now();
7670
7906
  const query = ({ side }) => db.selectDistinctOn([offers.obligationId], {
7671
7907
  obligationId: offers.obligationId,
7672
- price: offers.price
7673
- }).from(offers).innerJoin(groups, (0, drizzle_orm.and)((0, drizzle_orm.eq)(offers.groupChainId, groups.chainId), (0, drizzle_orm.eq)(offers.groupMaker, groups.maker), (0, drizzle_orm.eq)(offers.group, groups.group))).leftJoin(validations, (0, drizzle_orm.eq)(offers.hash, validations.offerHash)).leftJoin(status, (0, drizzle_orm.eq)(validations.statusId, status.id)).where((0, drizzle_orm.and)((0, drizzle_orm.inArray)(offers.obligationId, obligationIds), (0, drizzle_orm.eq)(offers.buy, side === "buy"), (0, drizzle_orm.gte)(offers.expiry, now$2), (0, drizzle_orm.gte)(offers.maturity, now$2), (0, drizzle_orm.lte)(offers.start, now$2), drizzle_orm.sql`(${status.code} IS NULL OR ${status.code} = ${Status.VALID})`)).orderBy(offers.obligationId, side === "buy" ? drizzle_orm.sql`${offers.price}::numeric ASC` : drizzle_orm.sql`${offers.price}::numeric DESC`);
7908
+ price: offers.tick
7909
+ }).from(offers).innerJoin(groups, (0, drizzle_orm.and)((0, drizzle_orm.eq)(offers.groupChainId, groups.chainId), (0, drizzle_orm.eq)(offers.groupMaker, groups.maker), (0, drizzle_orm.eq)(offers.group, groups.group))).leftJoin(validations, (0, drizzle_orm.eq)(offers.hash, validations.offerHash)).leftJoin(status, (0, drizzle_orm.eq)(validations.statusId, status.id)).where((0, drizzle_orm.and)((0, drizzle_orm.inArray)(offers.obligationId, obligationIds), (0, drizzle_orm.eq)(offers.buy, side === "buy"), (0, drizzle_orm.gte)(offers.expiry, now$2), (0, drizzle_orm.gte)(offers.maturity, now$2), (0, drizzle_orm.lte)(offers.start, now$2), drizzle_orm.sql`(${status.code} IS NULL OR ${status.code} = ${Status.VALID})`)).orderBy(offers.obligationId, side === "buy" ? drizzle_orm.sql`${offers.tick} ASC` : drizzle_orm.sql`${offers.tick} DESC`);
7674
7910
  const [bestBuys, bestSells] = await Promise.all([query({ side: "buy" }), query({ side: "sell" })]);
7675
7911
  const quotes = /* @__PURE__ */ new Map();
7676
7912
  for (const row of bestSells) quotes.set(row.obligationId, {
@@ -7728,16 +7964,57 @@ async function getOffersQuery(db, parameters) {
7728
7964
  '[]'::jsonb
7729
7965
  )`.as("collaterals") }).from(obligationCollateralsV2).innerJoin(oracles$1, drizzle_orm.sql`${obligationCollateralsV2.oracleChainId} = ${oracles$1.chainId}
7730
7966
  AND ${obligationCollateralsV2.oracleAddress} = ${oracles$1.address}`).where((0, drizzle_orm.eq)(obligationCollateralsV2.obligationId, offers.obligationId)).as("collaterals_lateral");
7731
- const availableLateral = db.select({ available: drizzle_orm.sql`COALESCE(SUM(
7732
- CASE
7733
- WHEN ${positions.asset} IS NULL THEN 0
7734
- ELSE
7735
- CASE
7736
- WHEN ${callbacks.amount} IS NULL THEN COALESCE(${positions.balance}, 0)::numeric
7737
- ELSE LEAST(${callbacks.amount}::numeric, COALESCE(${positions.balance}, 0)::numeric)
7738
- END
7739
- END
7740
- ), 0)`.as("available") }).from(offersCallbacks).innerJoin(callbacks, (0, drizzle_orm.eq)(offersCallbacks.callbackId, callbacks.id)).innerJoin(positions, (0, drizzle_orm.and)((0, drizzle_orm.eq)(callbacks.positionChainId, positions.chainId), (0, drizzle_orm.eq)(callbacks.positionContract, positions.contract), (0, drizzle_orm.eq)(callbacks.positionUser, positions.user))).where((0, drizzle_orm.eq)(offersCallbacks.offerHash, offers.hash)).as("available_lateral");
7967
+ const lotBalanceExpr = drizzle_orm.sql`GREATEST(0, LEAST(
7968
+ COALESCE(${positions.balance}, 0)::numeric
7969
+ + COALESCE((
7970
+ SELECT SUM(${offsets.value}::numeric)
7971
+ FROM ${offsets}
7972
+ WHERE ${offsets.chainId} = ${callbacks.positionChainId}
7973
+ AND LOWER(${offsets.contract}) = LOWER(${callbacks.positionContract})
7974
+ AND LOWER(${offsets.user}) = LOWER(${callbacks.positionUser})
7975
+ ), 0)
7976
+ - COALESCE(${lots.lower}::numeric, 0),
7977
+ (COALESCE(${lots.upper}::numeric, 0) - COALESCE(${lots.lower}::numeric, 0))
7978
+ - CASE
7979
+ WHEN ${offers.assets}::numeric > 0
7980
+ THEN COALESCE(${groups.consumed}::numeric, 0)
7981
+ * (COALESCE(${lots.upper}::numeric, 0) - COALESCE(${lots.lower}::numeric, 0))
7982
+ / ${offers.assets}::numeric
7983
+ ELSE 0
7984
+ END
7985
+ ))`;
7986
+ const contributionExpr = drizzle_orm.sql`CASE
7987
+ WHEN ${positions.asset} IS NULL OR ${lots.lower} IS NULL THEN 0
7988
+ ELSE LEAST(COALESCE(${callbacks.amount}::numeric, ${lotBalanceExpr}), ${lotBalanceExpr})
7989
+ END`;
7990
+ const availableExpr = drizzle_orm.sql`COALESCE((
7991
+ SELECT SUM(deduped.contribution)
7992
+ FROM (
7993
+ SELECT DISTINCT ON (
7994
+ ${callbacks.positionChainId},
7995
+ LOWER(${callbacks.positionContract}),
7996
+ LOWER(${callbacks.positionUser})
7997
+ )
7998
+ ${contributionExpr} AS contribution
7999
+ FROM ${offersCallbacks}
8000
+ INNER JOIN ${callbacks} ON ${offersCallbacks.callbackId} = ${callbacks.id}
8001
+ LEFT JOIN ${positions}
8002
+ ON ${positions.chainId} = ${callbacks.positionChainId}
8003
+ AND LOWER(${positions.contract}) = LOWER(${callbacks.positionContract})
8004
+ AND LOWER(${positions.user}) = LOWER(${callbacks.positionUser})
8005
+ LEFT JOIN ${lots}
8006
+ ON ${lots.chainId} = ${callbacks.positionChainId}
8007
+ AND LOWER(${lots.contract}) = LOWER(${callbacks.positionContract})
8008
+ AND LOWER(${lots.user}) = LOWER(${callbacks.positionUser})
8009
+ AND LOWER(${lots.group}) = LOWER(${offers.group})
8010
+ WHERE ${offersCallbacks.offerHash} = ${offers.hash}
8011
+ ORDER BY
8012
+ ${callbacks.positionChainId},
8013
+ LOWER(${callbacks.positionContract}),
8014
+ LOWER(${callbacks.positionUser}),
8015
+ ${contributionExpr} DESC
8016
+ ) deduped
8017
+ ), 0)`;
7741
8018
  const rows = (await db.select({
7742
8019
  hash: offers.hash,
7743
8020
  maker: offers.groupMaker,
@@ -7745,7 +8022,7 @@ async function getOffersQuery(db, parameters) {
7745
8022
  obligationUnits: offers.obligationUnits,
7746
8023
  obligationShares: offers.obligationShares,
7747
8024
  consumed: groups.consumed,
7748
- price: offers.price,
8025
+ tick: offers.tick,
7749
8026
  maturity: offers.maturity,
7750
8027
  expiry: offers.expiry,
7751
8028
  start: offers.start,
@@ -7758,22 +8035,22 @@ async function getOffersQuery(db, parameters) {
7758
8035
  callbackData: offers.callbackData,
7759
8036
  collaterals: collateralsLateral.collaterals,
7760
8037
  blockNumber: offers.blockNumber,
7761
- available: drizzle_orm.sql`COALESCE(${availableLateral.available}::numeric, 0)`.as("available"),
8038
+ available: drizzle_orm.sql`${availableExpr}::numeric`.as("available"),
7762
8039
  takeable: drizzle_orm.sql`FLOOR(GREATEST(0,
7763
8040
  CASE WHEN ${offers.buy} = false
7764
8041
  THEN ${offers.assets}::numeric - ${groups.consumed}::numeric
7765
8042
  ELSE LEAST(
7766
8043
  ${offers.assets}::numeric - ${groups.consumed}::numeric,
7767
- COALESCE(${availableLateral.available}::numeric, 0)
8044
+ ${availableExpr}::numeric
7768
8045
  )
7769
8046
  END
7770
8047
  ))`.as("takeable")
7771
- }).from(offers).innerJoin(obligations, (0, drizzle_orm.eq)(offers.obligationId, obligations.obligationId)).innerJoin(groups, (0, drizzle_orm.and)((0, drizzle_orm.eq)(offers.groupChainId, groups.chainId), (0, drizzle_orm.eq)(offers.groupMaker, groups.maker), (0, drizzle_orm.eq)(offers.group, groups.group))).innerJoinLateral(collateralsLateral, drizzle_orm.sql`true`).leftJoinLateral(availableLateral, drizzle_orm.sql`true`).where((0, drizzle_orm.and)(cursor !== null && cursor !== void 0 ? (0, drizzle_orm.gt)(offers.hash, cursor) : void 0, maker !== void 0 ? (0, drizzle_orm.eq)(offers.groupMaker, maker.toLowerCase()) : void 0, (0, drizzle_orm.gte)(offers.expiry, now), (0, drizzle_orm.gte)(offers.maturity, now), maker === void 0 ? drizzle_orm.sql`GREATEST(0,
8048
+ }).from(offers).innerJoin(obligations, (0, drizzle_orm.eq)(offers.obligationId, obligations.obligationId)).innerJoin(groups, (0, drizzle_orm.and)((0, drizzle_orm.eq)(offers.groupChainId, groups.chainId), (0, drizzle_orm.eq)(offers.groupMaker, groups.maker), (0, drizzle_orm.eq)(offers.group, groups.group))).innerJoinLateral(collateralsLateral, drizzle_orm.sql`true`).where((0, drizzle_orm.and)(cursor !== null && cursor !== void 0 ? (0, drizzle_orm.gt)(offers.hash, cursor) : void 0, maker !== void 0 ? (0, drizzle_orm.eq)(offers.groupMaker, maker.toLowerCase()) : void 0, (0, drizzle_orm.gte)(offers.expiry, now), (0, drizzle_orm.gte)(offers.maturity, now), maker === void 0 ? drizzle_orm.sql`GREATEST(0,
7772
8049
  CASE WHEN ${offers.buy} = false
7773
8050
  THEN ${offers.assets}::numeric - ${groups.consumed}::numeric
7774
8051
  ELSE LEAST(
7775
8052
  ${offers.assets}::numeric - ${groups.consumed}::numeric,
7776
- COALESCE(${availableLateral.available}::numeric, 0)
8053
+ ${availableExpr}::numeric
7777
8054
  )
7778
8055
  END
7779
8056
  ) > 0` : void 0)).orderBy((0, drizzle_orm.asc)(offers.hash)).limit(limit)).map((row) => {
@@ -7783,7 +8060,7 @@ async function getOffersQuery(db, parameters) {
7783
8060
  assets: BigInt(row.assets),
7784
8061
  obligationUnits: BigInt(row.obligationUnits),
7785
8062
  obligationShares: BigInt(row.obligationShares),
7786
- price: BigInt(row.price),
8063
+ tick: row.tick,
7787
8064
  maturity: from$16(row.maturity),
7788
8065
  expiry: row.expiry,
7789
8066
  start: row.start,
@@ -8182,7 +8459,7 @@ async function getOffers(apiClient, parameters) {
8182
8459
  assets: offerData.assets,
8183
8460
  obligation_units: offerData.obligation_units,
8184
8461
  obligation_shares: offerData.obligation_shares,
8185
- price: offerData.price,
8462
+ tick: offerData.tick,
8186
8463
  maturity: from$16(offerData.obligation.maturity),
8187
8464
  expiry: offerData.expiry,
8188
8465
  start: offerData.start,
@@ -8541,6 +8818,7 @@ function create$12(config) {
8541
8818
  return {
8542
8819
  get: async (parameters) => {
8543
8820
  const { side, obligationId, cursor: cursorString, limit = DEFAULT_LIMIT$2 } = parameters;
8821
+ const tickSortDirection = side === "sell" ? "asc" : "desc";
8544
8822
  const inputCursor = LevelCursor.decode(cursorString, logger);
8545
8823
  if (cursorString != null && inputCursor === null) return {
8546
8824
  levels: [],
@@ -8555,23 +8833,23 @@ function create$12(config) {
8555
8833
  cursor: inputCursor?.offersCursor ?? void 0,
8556
8834
  limit: fetchLimit
8557
8835
  });
8558
- const priceMap = /* @__PURE__ */ new Map();
8836
+ const tickMap = /* @__PURE__ */ new Map();
8559
8837
  for (const row of rows) {
8560
- const priceKey = row.price.toString();
8561
- const existing = priceMap.get(priceKey);
8838
+ const existing = tickMap.get(row.tick);
8562
8839
  if (existing) {
8563
8840
  existing.assets += row.takeable;
8564
8841
  existing.count += 1;
8565
- } else priceMap.set(priceKey, {
8842
+ } else tickMap.set(row.tick, {
8566
8843
  assets: row.takeable,
8567
8844
  count: 1
8568
8845
  });
8569
8846
  }
8570
- const levels = Array.from(priceMap.entries()).map(([price, data]) => ({
8571
- price: BigInt(price),
8572
- assets: data.assets,
8573
- count: data.count
8847
+ const levels = Array.from(tickMap.entries()).map(([tick, level]) => ({
8848
+ tick,
8849
+ assets: level.assets,
8850
+ count: level.count
8574
8851
  }));
8852
+ levels.sort((a, b) => tickSortDirection === "asc" ? a.tick - b.tick : b.tick - a.tick);
8575
8853
  const paginatedLevels = levels.slice(0, limit);
8576
8854
  const hasMore = levels.length > limit || offersNextCursor !== null;
8577
8855
  const lastLevel = paginatedLevels[paginatedLevels.length - 1];
@@ -8617,14 +8895,14 @@ async function _getOffers(db, params) {
8617
8895
  AND (s.code IS NULL OR s.code = ${Status.VALID})
8618
8896
  ORDER BY
8619
8897
  o.group_chain_id, o.group_maker, o."group_group",
8620
- o.price::numeric ${priceSortDirection === "asc" ? drizzle_orm.sql`ASC` : drizzle_orm.sql`DESC`}, o.block_number ASC, o.assets DESC, o.hash ASC
8898
+ o.tick ${priceSortDirection === "asc" ? drizzle_orm.sql`ASC` : drizzle_orm.sql`DESC`}, o.block_number ASC, o.assets DESC, o.hash ASC
8621
8899
  ),
8622
8900
  enriched AS (
8623
8901
  SELECT
8624
8902
  w.*,
8625
8903
  g.consumed, g.chain_id, obl.loan_token,
8626
8904
  CASE WHEN ${priceSortDirection === "asc" ? drizzle_orm.sql`TRUE` : drizzle_orm.sql`FALSE`}
8627
- THEN w.price::numeric ELSE -w.price::numeric END AS price_norm,
8905
+ THEN w.tick ELSE -w.tick END AS tick_norm,
8628
8906
  w.block_number AS block_norm,
8629
8907
  -w.assets AS assets_norm,
8630
8908
  w.hash AS hash_norm
@@ -8641,33 +8919,35 @@ async function _getOffers(db, params) {
8641
8919
  FROM enriched e
8642
8920
  ${cursor != null ? drizzle_orm.sql`
8643
8921
  WHERE
8644
- (e.price_norm, e.block_norm, e.assets_norm, e.hash_norm)
8922
+ (e.tick_norm, e.block_norm, e.assets_norm, e.hash_norm)
8645
8923
  > (
8646
8924
  CASE WHEN ${priceSortDirection === "asc" ? drizzle_orm.sql`TRUE` : drizzle_orm.sql`FALSE`}
8647
- THEN ${cursor.price}::numeric ELSE -${cursor.price}::numeric END,
8925
+ THEN ${cursor.tick}::integer ELSE -${cursor.tick}::integer END,
8648
8926
  ${cursor.blockNumber},
8649
8927
  -${cursor.assets}::numeric,
8650
8928
  ${cursor.hash}
8651
8929
  )` : drizzle_orm.sql``}
8652
- ORDER BY e.price::numeric ${priceSortDirection === "asc" ? drizzle_orm.sql`ASC` : drizzle_orm.sql`DESC`}, e.block_number ASC, e.assets DESC, e.hash ASC
8930
+ ORDER BY e.tick ${priceSortDirection === "asc" ? drizzle_orm.sql`ASC` : drizzle_orm.sql`DESC`}, e.block_number ASC, e.assets DESC, e.hash ASC
8653
8931
  LIMIT ${limit}
8654
8932
  ),
8655
- -- Compute sum of offsets per position
8933
+ -- Compute sum of offsets per position and obligation
8656
8934
  position_offsets AS (
8657
8935
  SELECT
8658
8936
  chain_id,
8659
8937
  "user",
8660
8938
  contract,
8939
+ obligation_id,
8661
8940
  SUM(value::numeric) AS total_offset
8662
8941
  FROM ${offsets}
8663
- GROUP BY chain_id, "user", contract
8942
+ GROUP BY chain_id, "user", contract, obligation_id
8664
8943
  ),
8665
- -- Compute position_consumed: sum of consumed from all groups with lots on each position (converted to lot terms)
8944
+ -- Compute position_consumed: sum of consumed from all groups with lots on each position+obligation (converted to lot terms)
8666
8945
  position_consumed AS (
8667
8946
  SELECT
8668
8947
  l.chain_id,
8669
8948
  l.contract,
8670
8949
  l."user",
8950
+ l.obligation_id,
8671
8951
  SUM(
8672
8952
  CASE
8673
8953
  WHEN wo.assets::numeric > 0
@@ -8684,7 +8964,7 @@ async function _getOffers(db, params) {
8684
8964
  ON wo.group_chain_id = g.chain_id
8685
8965
  AND LOWER(wo.group_maker) = LOWER(g.maker)
8686
8966
  AND wo.group_group = g."group"
8687
- GROUP BY l.chain_id, l.contract, l."user"
8967
+ GROUP BY l.chain_id, l.contract, l."user", l.obligation_id
8688
8968
  ),
8689
8969
  -- Compute callback contributions with lot balance
8690
8970
  callback_contributions AS (
@@ -8692,7 +8972,7 @@ async function _getOffers(db, params) {
8692
8972
  p.hash,
8693
8973
  p.obligation_id,
8694
8974
  p.assets,
8695
- p.price,
8975
+ p.tick,
8696
8976
  p.obligation_units,
8697
8977
  p.obligation_shares,
8698
8978
  p.maturity,
@@ -8737,6 +9017,7 @@ async function _getOffers(db, params) {
8737
9017
  AND LOWER(l.contract) = LOWER(c.position_contract)
8738
9018
  AND LOWER(l."user") = LOWER(c.position_user)
8739
9019
  AND l."group" = p.group_group
9020
+ AND l.obligation_id = p.obligation_id
8740
9021
  LEFT JOIN ${positions} pos
8741
9022
  ON pos.chain_id = c.position_chain_id
8742
9023
  AND LOWER(pos.contract) = LOWER(c.position_contract)
@@ -8745,10 +9026,12 @@ async function _getOffers(db, params) {
8745
9026
  ON pos_offsets.chain_id = c.position_chain_id
8746
9027
  AND LOWER(pos_offsets.contract) = LOWER(c.position_contract)
8747
9028
  AND LOWER(pos_offsets."user") = LOWER(c.position_user)
9029
+ AND pos_offsets.obligation_id = p.obligation_id
8748
9030
  LEFT JOIN position_consumed pc
8749
9031
  ON pc.chain_id = c.position_chain_id
8750
9032
  AND LOWER(pc.contract) = LOWER(c.position_contract)
8751
9033
  AND LOWER(pc."user") = LOWER(c.position_user)
9034
+ AND pc.obligation_id = p.obligation_id
8752
9035
  ),
8753
9036
  -- Compute contribution per callback in loan terms (loan token only — collateral positions are not indexed)
8754
9037
  callback_loan_contribution AS (
@@ -8766,7 +9049,7 @@ async function _getOffers(db, params) {
8766
9049
  hash,
8767
9050
  obligation_id,
8768
9051
  assets,
8769
- price,
9052
+ tick,
8770
9053
  obligation_units,
8771
9054
  obligation_shares,
8772
9055
  maturity,
@@ -8792,13 +9075,13 @@ async function _getOffers(db, params) {
8792
9075
  WHERE clc.callback_id IS NOT NULL
8793
9076
  ORDER BY clc.hash, clc.position_chain_id, clc.position_contract, clc.position_user, clc.contribution_in_loan DESC
8794
9077
  ) deduped
8795
- GROUP BY hash, obligation_id, assets, price, obligation_units, obligation_shares, maturity, expiry, start, group_group, buy,
9078
+ GROUP BY hash, obligation_id, assets, tick, obligation_units, obligation_shares, maturity, expiry, start, group_group, buy,
8796
9079
  callback_address, callback_data, block_number, group_chain_id, group_maker,
8797
9080
  consumed, chain_id, loan_token, session
8798
9081
  UNION ALL
8799
9082
  -- Sell offers without callbacks: collateral positions not indexed, takeable = assets - consumed
8800
9083
  SELECT
8801
- p.hash, p.obligation_id, p.assets, p.price,
9084
+ p.hash, p.obligation_id, p.assets, p.tick,
8802
9085
  p.obligation_units, p.obligation_shares,
8803
9086
  p.maturity, p.expiry, p.start, p.group_group,
8804
9087
  p.buy, p.callback_address, p.callback_data,
@@ -8820,7 +9103,7 @@ async function _getOffers(db, params) {
8820
9103
  oc.obligation_units,
8821
9104
  oc.obligation_shares,
8822
9105
  oc.consumed,
8823
- oc.price,
9106
+ oc.tick,
8824
9107
  oc.maturity,
8825
9108
  oc.expiry,
8826
9109
  oc.start,
@@ -8852,7 +9135,7 @@ async function _getOffers(db, params) {
8852
9135
  ))
8853
9136
  END > 0
8854
9137
  ORDER BY
8855
- oc.price::numeric ${priceSortDirection === "asc" ? drizzle_orm.sql`ASC` : drizzle_orm.sql`DESC`},
9138
+ oc.tick ${priceSortDirection === "asc" ? drizzle_orm.sql`ASC` : drizzle_orm.sql`DESC`},
8856
9139
  oc.block_number ASC,
8857
9140
  oc.assets DESC,
8858
9141
  oc.hash ASC;
@@ -8865,7 +9148,7 @@ async function _getOffers(db, params) {
8865
9148
  assets: BigInt(row.assets),
8866
9149
  obligationUnits: BigInt(row.obligation_units ?? 0),
8867
9150
  obligationShares: BigInt(row.obligation_shares ?? 0),
8868
- price: BigInt(row.price),
9151
+ tick: row.tick,
8869
9152
  maturity: row.maturity,
8870
9153
  expiry: row.expiry,
8871
9154
  start: row.start,
@@ -8897,7 +9180,7 @@ let Cursor;
8897
9180
  function encode(row, totalReturned, now, side) {
8898
9181
  return Buffer.from(JSON.stringify({
8899
9182
  side,
8900
- price: row.price.toString(),
9183
+ tick: row.tick,
8901
9184
  blockNumber: row.blockNumber,
8902
9185
  assets: row.assets.toString(),
8903
9186
  hash: row.hash,
@@ -8908,10 +9191,9 @@ let Cursor;
8908
9191
  _Cursor.encode = encode;
8909
9192
  function decode(cursorString, logger) {
8910
9193
  if (cursorString == null) return null;
8911
- const isNumericString = (value) => typeof value === "string" && /^-?\d+$/.test(value);
8912
9194
  try {
8913
9195
  const v = JSON.parse(Buffer.from(cursorString, "base64url").toString("utf8"));
8914
- if ((v?.side === "buy" || v?.side === "sell") && isNumericString(v?.price) && typeof v?.blockNumber === "number" && Number.isInteger(v.blockNumber) && isNumericString(v?.assets) && (0, viem.isHex)(v?.hash) && typeof v?.totalReturned === "number" && Number.isInteger(v.totalReturned) && typeof v?.now === "number" && Number.isInteger(v.now)) return v;
9196
+ if ((v?.side === "buy" || v?.side === "sell") && typeof v?.tick === "number" && Number.isInteger(v.tick) && typeof v?.blockNumber === "number" && Number.isInteger(v.blockNumber) && typeof v?.assets === "string" && /^-?\d+$/.test(v.assets) && (0, viem.isHex)(v?.hash) && typeof v?.totalReturned === "number" && Number.isInteger(v.totalReturned) && typeof v?.now === "number" && Number.isInteger(v.now)) return v;
8915
9197
  throw new Error("Invalid cursor");
8916
9198
  } catch {
8917
9199
  logger.error({
@@ -8929,7 +9211,7 @@ let LevelCursor;
8929
9211
  function encode(lastLevel, offersCursor, side, now) {
8930
9212
  return Buffer.from(JSON.stringify({
8931
9213
  side,
8932
- lastPrice: lastLevel.price.toString(),
9214
+ lastTick: lastLevel.tick,
8933
9215
  now,
8934
9216
  offersCursor
8935
9217
  })).toString("base64url");
@@ -8937,10 +9219,9 @@ let LevelCursor;
8937
9219
  _LevelCursor.encode = encode;
8938
9220
  function decode(cursorString, logger) {
8939
9221
  if (cursorString == null) return null;
8940
- const isNumericString = (value) => typeof value === "string" && /^-?\d+$/.test(value);
8941
9222
  try {
8942
9223
  const v = JSON.parse(Buffer.from(cursorString, "base64url").toString("utf8"));
8943
- if ((v?.side === "buy" || v?.side === "sell") && isNumericString(v?.lastPrice) && typeof v?.now === "number" && Number.isInteger(v.now) && (v?.offersCursor === null || typeof v?.offersCursor === "string")) return v;
9224
+ if ((v?.side === "buy" || v?.side === "sell") && typeof v?.lastTick === "number" && Number.isInteger(v.lastTick) && typeof v?.now === "number" && Number.isInteger(v.now) && (v?.offersCursor === null || typeof v?.offersCursor === "string")) return v;
8944
9225
  throw new Error("Invalid book cursor");
8945
9226
  } catch {
8946
9227
  logger.error({
@@ -9083,31 +9364,33 @@ function create$9(db) {
9083
9364
  function create$8(db) {
9084
9365
  return {
9085
9366
  get: async (parameters) => {
9086
- const { chainId, user, contract, group } = parameters ?? {};
9367
+ const { chainId, user, contract, group, obligationId } = parameters ?? {};
9087
9368
  const conditions = [];
9088
9369
  if (chainId !== void 0) conditions.push((0, drizzle_orm.eq)(lots.chainId, chainId));
9089
9370
  if (user !== void 0) conditions.push((0, drizzle_orm.eq)(lots.user, user.toLowerCase()));
9090
9371
  if (contract !== void 0) conditions.push((0, drizzle_orm.eq)(lots.contract, contract.toLowerCase()));
9091
9372
  if (group !== void 0) conditions.push((0, drizzle_orm.eq)(lots.group, group));
9373
+ if (obligationId !== void 0) conditions.push((0, drizzle_orm.eq)(lots.obligationId, obligationId));
9092
9374
  return (await db.select().from(lots).where(conditions.length > 0 ? (0, drizzle_orm.and)(...conditions) : void 0)).map((row) => ({
9093
9375
  chainId: row.chainId,
9094
9376
  user: row.user,
9095
9377
  contract: row.contract,
9096
9378
  group: row.group,
9379
+ obligationId: row.obligationId,
9097
9380
  lower: BigInt(row.lower),
9098
9381
  upper: BigInt(row.upper)
9099
9382
  }));
9100
9383
  },
9101
9384
  create: async (parameters) => {
9102
9385
  if (parameters.length === 0) return;
9103
- const lotsByPositionGroup = /* @__PURE__ */ new Map();
9386
+ const lotsByKey = /* @__PURE__ */ new Map();
9104
9387
  for (const offer of parameters) {
9105
- const key = `${offer.positionChainId}-${offer.positionContract}-${offer.positionUser}-${offer.group}`.toLowerCase();
9106
- const existing = lotsByPositionGroup.get(key);
9107
- if (!existing || offer.size > existing.size) lotsByPositionGroup.set(key, offer);
9388
+ const key = `${offer.positionChainId}-${offer.positionContract}-${offer.positionUser}-${offer.group}-${offer.obligationId}`.toLowerCase();
9389
+ const existing = lotsByKey.get(key);
9390
+ if (!existing || offer.size > existing.size) lotsByKey.set(key, offer);
9108
9391
  }
9109
- for (const offer of lotsByPositionGroup.values()) if ((await db.select().from(lots).where((0, drizzle_orm.and)((0, drizzle_orm.eq)(lots.chainId, offer.positionChainId), (0, drizzle_orm.eq)(lots.contract, offer.positionContract.toLowerCase()), (0, drizzle_orm.eq)(lots.user, offer.positionUser.toLowerCase()), (0, drizzle_orm.eq)(lots.group, offer.group.toLowerCase()))).limit(1)).length === 0) {
9110
- const maxUpperResult = await db.select({ maxUpper: drizzle_orm.sql`COALESCE(MAX(${lots.upper}::numeric), 0)` }).from(lots).where((0, drizzle_orm.and)((0, drizzle_orm.eq)(lots.chainId, offer.positionChainId), (0, drizzle_orm.eq)(lots.contract, offer.positionContract.toLowerCase()), (0, drizzle_orm.eq)(lots.user, offer.positionUser.toLowerCase())));
9392
+ for (const offer of lotsByKey.values()) if ((await db.select().from(lots).where((0, drizzle_orm.and)((0, drizzle_orm.eq)(lots.chainId, offer.positionChainId), (0, drizzle_orm.eq)(lots.contract, offer.positionContract.toLowerCase()), (0, drizzle_orm.eq)(lots.user, offer.positionUser.toLowerCase()), (0, drizzle_orm.eq)(lots.group, offer.group.toLowerCase()), (0, drizzle_orm.eq)(lots.obligationId, offer.obligationId.toLowerCase()))).limit(1)).length === 0) {
9393
+ const maxUpperResult = await db.select({ maxUpper: drizzle_orm.sql`COALESCE(MAX(${lots.upper}::numeric), 0)` }).from(lots).where((0, drizzle_orm.and)((0, drizzle_orm.eq)(lots.chainId, offer.positionChainId), (0, drizzle_orm.eq)(lots.contract, offer.positionContract.toLowerCase()), (0, drizzle_orm.eq)(lots.user, offer.positionUser.toLowerCase()), (0, drizzle_orm.eq)(lots.obligationId, offer.obligationId.toLowerCase())));
9111
9394
  const newLower = BigInt(maxUpperResult[0]?.maxUpper ?? "0");
9112
9395
  const newUpper = newLower + offer.size;
9113
9396
  await db.insert(lots).values({
@@ -9115,6 +9398,7 @@ function create$8(db) {
9115
9398
  user: offer.positionUser.toLowerCase(),
9116
9399
  contract: offer.positionContract.toLowerCase(),
9117
9400
  group: offer.group.toLowerCase(),
9401
+ obligationId: offer.obligationId.toLowerCase(),
9118
9402
  lower: newLower.toString(),
9119
9403
  upper: newUpper.toString()
9120
9404
  });
@@ -9169,17 +9453,19 @@ function create$7(db) {
9169
9453
  //#region src/database/domains/Offsets.ts
9170
9454
  function create$6(db) {
9171
9455
  return { get: async (parameters) => {
9172
- const { chainId, user, contract, group } = parameters ?? {};
9456
+ const { chainId, user, contract, group, obligationId } = parameters ?? {};
9173
9457
  const conditions = [];
9174
9458
  if (chainId !== void 0) conditions.push((0, drizzle_orm.eq)(offsets.chainId, chainId));
9175
9459
  if (user !== void 0) conditions.push((0, drizzle_orm.eq)(offsets.user, user.toLowerCase()));
9176
9460
  if (contract !== void 0) conditions.push((0, drizzle_orm.eq)(offsets.contract, contract.toLowerCase()));
9177
9461
  if (group !== void 0) conditions.push((0, drizzle_orm.eq)(offsets.group, group));
9462
+ if (obligationId !== void 0) conditions.push((0, drizzle_orm.eq)(offsets.obligationId, obligationId));
9178
9463
  return (await db.select().from(offsets).where(conditions.length > 0 ? (0, drizzle_orm.and)(...conditions) : void 0)).map((row) => ({
9179
9464
  chainId: row.chainId,
9180
9465
  user: row.user,
9181
9466
  contract: row.contract,
9182
9467
  group: row.group,
9468
+ obligationId: row.obligationId,
9183
9469
  value: BigInt(row.value)
9184
9470
  }));
9185
9471
  } };
@@ -9329,7 +9615,8 @@ const create$4 = (db) => {
9329
9615
  if (!parsed.chainId || !parsed.contract) throw new Error("Invalid cursor format");
9330
9616
  cursor = {
9331
9617
  chainId: parsed.chainId,
9332
- contract: parsed.contract
9618
+ contract: parsed.contract,
9619
+ obligationId: parsed.obligationId ?? null
9333
9620
  };
9334
9621
  }
9335
9622
  const raw = await db.execute(drizzle_orm.sql`
@@ -9338,16 +9625,18 @@ const create$4 = (db) => {
9338
9625
  chain_id,
9339
9626
  "user",
9340
9627
  contract,
9628
+ obligation_id,
9341
9629
  SUM(value::numeric) AS total_offset
9342
9630
  FROM ${offsets}
9343
9631
  WHERE LOWER("user") = LOWER(${user})
9344
- GROUP BY chain_id, "user", contract
9632
+ GROUP BY chain_id, "user", contract, obligation_id
9345
9633
  ),
9346
9634
  position_consumed AS (
9347
9635
  SELECT
9348
9636
  l.chain_id,
9349
9637
  l.contract,
9350
9638
  l."user",
9639
+ l.obligation_id,
9351
9640
  SUM(
9352
9641
  CASE
9353
9642
  WHEN offer_agg.assets > 0
@@ -9373,50 +9662,64 @@ const create$4 = (db) => {
9373
9662
  AND LOWER(offer_agg.group_maker) = LOWER(g.maker)
9374
9663
  AND offer_agg."group_group" = g."group"
9375
9664
  WHERE LOWER(l."user") = LOWER(${user})
9376
- GROUP BY l.chain_id, l.contract, l."user"
9665
+ GROUP BY l.chain_id, l.contract, l."user", l.obligation_id
9377
9666
  ),
9378
9667
  position_max_lot AS (
9379
9668
  SELECT
9380
9669
  chain_id,
9381
9670
  contract,
9382
9671
  "user",
9672
+ obligation_id,
9383
9673
  MAX(upper::numeric) AS max_upper
9384
9674
  FROM ${lots}
9385
9675
  WHERE LOWER("user") = LOWER(${user})
9386
- GROUP BY chain_id, contract, "user"
9676
+ GROUP BY chain_id, contract, "user", obligation_id
9677
+ ),
9678
+ per_obligation AS (
9679
+ SELECT
9680
+ pml.chain_id,
9681
+ pml.contract,
9682
+ pml."user",
9683
+ pml.obligation_id,
9684
+ GREATEST(0,
9685
+ COALESCE(pml.max_upper, 0)
9686
+ - COALESCE(po.total_offset, 0)
9687
+ - COALESCE(pc.consumed, 0)
9688
+ )::text AS reserved_balance
9689
+ FROM position_max_lot pml
9690
+ LEFT JOIN position_offsets po
9691
+ ON po.chain_id = pml.chain_id
9692
+ AND LOWER(po.contract) = LOWER(pml.contract)
9693
+ AND LOWER(po."user") = LOWER(pml."user")
9694
+ AND po.obligation_id = pml.obligation_id
9695
+ LEFT JOIN position_consumed pc
9696
+ ON pc.chain_id = pml.chain_id
9697
+ AND LOWER(pc.contract) = LOWER(pml.contract)
9698
+ AND LOWER(pc."user") = LOWER(pml."user")
9699
+ AND pc.obligation_id = pml.obligation_id
9387
9700
  )
9388
9701
  SELECT
9389
9702
  p.chain_id,
9390
9703
  p.contract,
9391
9704
  p."user",
9392
9705
  p.block_number,
9393
- GREATEST(0,
9394
- COALESCE(pml.max_upper, 0)
9395
- - COALESCE(po.total_offset, 0)
9396
- - COALESCE(pc.consumed, 0)
9397
- )::text AS reserved_balance
9706
+ po.obligation_id,
9707
+ COALESCE(po.reserved_balance, '0') AS reserved_balance
9398
9708
  FROM ${positions} p
9399
- LEFT JOIN position_offsets po
9709
+ LEFT JOIN per_obligation po
9400
9710
  ON po.chain_id = p.chain_id
9401
9711
  AND LOWER(po.contract) = LOWER(p.contract)
9402
9712
  AND LOWER(po."user") = LOWER(p."user")
9403
- LEFT JOIN position_consumed pc
9404
- ON pc.chain_id = p.chain_id
9405
- AND LOWER(pc.contract) = LOWER(p.contract)
9406
- AND LOWER(pc."user") = LOWER(p."user")
9407
- LEFT JOIN position_max_lot pml
9408
- ON pml.chain_id = p.chain_id
9409
- AND LOWER(pml.contract) = LOWER(p.contract)
9410
- AND LOWER(pml."user") = LOWER(p."user")
9411
9713
  WHERE LOWER(p."user") = LOWER(${user})
9412
9714
  AND p."user" != ${viem.zeroAddress}
9413
- ${cursor !== null ? drizzle_orm.sql`AND (p.chain_id, p.contract) > (${cursor.chainId}, ${cursor.contract})` : drizzle_orm.sql``}
9414
- ORDER BY p.chain_id ASC, p.contract ASC
9715
+ ${cursor !== null ? drizzle_orm.sql`AND (p.chain_id, p.contract, COALESCE(po.obligation_id, '')) > (${cursor.chainId}, ${cursor.contract}, ${cursor.obligationId ?? ""})` : drizzle_orm.sql``}
9716
+ ORDER BY p.chain_id ASC, p.contract ASC, po.obligation_id ASC NULLS FIRST
9415
9717
  LIMIT ${limit}
9416
9718
  `);
9417
9719
  const nextCursor = raw.rows.length === limit ? Buffer.from(JSON.stringify({
9418
9720
  chainId: raw.rows[raw.rows.length - 1].chain_id.toString(),
9419
- contract: raw.rows[raw.rows.length - 1].contract
9721
+ contract: raw.rows[raw.rows.length - 1].contract,
9722
+ obligationId: raw.rows[raw.rows.length - 1].obligation_id
9420
9723
  })).toString("base64url") : null;
9421
9724
  return {
9422
9725
  positions: raw.rows.map((row) => ({
@@ -9424,6 +9727,7 @@ const create$4 = (db) => {
9424
9727
  contract: row.contract,
9425
9728
  user: row.user,
9426
9729
  blockNumber: row.block_number,
9730
+ obligationId: row.obligation_id,
9427
9731
  reserved: BigInt(row.reserved_balance.split(".")[0] ?? "0")
9428
9732
  })),
9429
9733
  nextCursor
@@ -9702,7 +10006,10 @@ function create$1(db) {
9702
10006
 
9703
10007
  //#endregion
9704
10008
  //#region src/database/Database.ts
9705
- var Database_exports = /* @__PURE__ */ __exportAll({ connect: () => connect$1 });
10009
+ var Database_exports = /* @__PURE__ */ __exportAll({
10010
+ connect: () => connect$1,
10011
+ getSchemaNamesForMigration: () => getSchemaNamesForMigration
10012
+ });
9706
10013
  function createDomains(core, chainRegistry) {
9707
10014
  return {
9708
10015
  book: create$12({ db: core }),
@@ -9799,6 +10106,7 @@ function augmentWithDomains(base, chainRegistry) {
9799
10106
  return wrapped;
9800
10107
  }
9801
10108
  const InMemoryDbMap = /* @__PURE__ */ new Map();
10109
+ const LEGACY_SCHEMA_START_MINOR = 7;
9802
10110
  /**
9803
10111
  * Connect to the database.
9804
10112
  * @notice If no connection string is provided, an in-process PGLite database is created.
@@ -9853,9 +10161,26 @@ function applyMigrations(kind, driver) {
9853
10161
  async function preMigrate(driver) {
9854
10162
  const tracer = getTracer("db.preMigrate");
9855
10163
  await startActiveSpan(tracer, "db.preMigrate", async () => {
9856
- await driver.execute(`create schema if not exists "${VERSION}"`);
10164
+ const schemaNames = getSchemaNamesForMigration(VERSION);
10165
+ for (const schemaName of schemaNames) await driver.execute(`create schema if not exists "${schemaName}"`);
9857
10166
  });
9858
10167
  }
10168
+ /**
10169
+ * Build the list of router schemas that should exist before running migrations.
10170
+ * @param version - Current schema version (e.g. `router_v1.8`).
10171
+ * @returns Ordered schema names from `router_v1.7` to current, or just current if parsing fails.
10172
+ */
10173
+ function getSchemaNamesForMigration(version) {
10174
+ const parsed = /^router_v(?<major>\d+)\.(?<minor>\d+)$/.exec(version);
10175
+ if (!parsed?.groups?.major || !parsed.groups.minor) return [version];
10176
+ const major = Number.parseInt(parsed.groups.major, 10);
10177
+ const currentMinor = Number.parseInt(parsed.groups.minor, 10);
10178
+ if (!Number.isInteger(major) || !Number.isInteger(currentMinor)) return [version];
10179
+ if (currentMinor < LEGACY_SCHEMA_START_MINOR) return [version];
10180
+ const schemaNames = [];
10181
+ for (let minor = LEGACY_SCHEMA_START_MINOR; minor <= currentMinor; minor += 1) schemaNames.push(`router_v${major}.${minor}`);
10182
+ return schemaNames;
10183
+ }
9859
10184
  async function postMigrate(driver) {
9860
10185
  const tracer = getTracer("db.postMigrate");
9861
10186
  await startActiveSpan(tracer, "db.postMigrate", async () => {
@@ -10125,15 +10450,16 @@ async function postMigrate(driver) {
10125
10450
  RETURNS trigger
10126
10451
  LANGUAGE plpgsql AS $$
10127
10452
  BEGIN
10128
- INSERT INTO "${VERSION}"."offsets" (chain_id, "user", contract, "group", value)
10453
+ INSERT INTO "${VERSION}"."offsets" (chain_id, "user", contract, "group", obligation_id, value)
10129
10454
  VALUES (
10130
10455
  OLD.chain_id,
10131
10456
  OLD."user",
10132
10457
  OLD.contract,
10133
10458
  OLD."group",
10459
+ OLD.obligation_id,
10134
10460
  OLD.upper::numeric - OLD.lower::numeric
10135
10461
  )
10136
- ON CONFLICT (chain_id, "user", contract, "group") DO NOTHING;
10462
+ ON CONFLICT (chain_id, "user", contract, "group", obligation_id) DO NOTHING;
10137
10463
  RETURN OLD;
10138
10464
  END;
10139
10465
  $$;
@@ -10387,10 +10713,11 @@ var Rules_exports = /* @__PURE__ */ __exportAll({
10387
10713
  amountMutualExclusivity: () => amountMutualExclusivity,
10388
10714
  callback: () => callback,
10389
10715
  chains: () => chains,
10716
+ collateralToken: () => collateralToken,
10717
+ loanToken: () => loanToken,
10390
10718
  maturity: () => maturity,
10391
10719
  oracle: () => oracle,
10392
10720
  sameMaker: () => sameMaker,
10393
- token: () => token,
10394
10721
  validity: () => validity
10395
10722
  });
10396
10723
  /**
@@ -10418,15 +10745,25 @@ const callback = ({ callbacks }) => single("callback", `Validates callbacks: buy
10418
10745
  if (isEmptyCallback(offer) && !offer.buy && !callbacks.includes(Type$1.SellWithEmptyCallback)) return { message: "Sell offers with empty callback not allowed." };
10419
10746
  });
10420
10747
  /**
10421
- * A validation rule that checks if the offer's tokens are allowed for its chain.
10422
- * @param assetsByChainId - Allowed assets indexed by chain id.
10748
+ * A validation rule that checks if the offer's loan token is allowed for its chain.
10749
+ * @param assetsByChainId - Allowed loan tokens indexed by chain id.
10423
10750
  * @returns The issue that was found. If the offer is valid, this will be undefined.
10424
10751
  */
10425
- const token = ({ assetsByChainId }) => single("token", "Validates that offer loan token and collateral tokens are in the allowed assets list for the offer chain", (offer) => {
10426
- const allowedAssets = assetsByChainId[offer.chainId]?.map((asset) => asset.toLowerCase());
10427
- if (!allowedAssets || allowedAssets.length === 0) return { message: `No allowed assets for chain ${offer.chainId}` };
10428
- if (!allowedAssets.includes(offer.loanToken.toLowerCase())) return { message: "Loan token is not allowed" };
10429
- if (offer.collaterals.some((collateral) => !allowedAssets.includes(collateral.asset.toLowerCase()))) return { message: "Collateral is not allowed" };
10752
+ const loanToken = ({ assetsByChainId }) => single("loan_token", "Validates that offer loan token is in the allowed token list for the offer chain", (offer) => {
10753
+ const allowedLoanTokens = assetsByChainId[offer.chainId]?.map((asset) => asset.toLowerCase());
10754
+ if (!allowedLoanTokens || allowedLoanTokens.length === 0) return { message: `No allowed loan tokens for chain ${offer.chainId}` };
10755
+ if (!allowedLoanTokens.includes(offer.loanToken.toLowerCase())) return { message: "Loan token is not allowed" };
10756
+ });
10757
+ /**
10758
+ * A validation rule that checks if the offer's collateral tokens are allowed for its chain.
10759
+ * @param collateralAssetsByChainId - Allowed collateral tokens indexed by chain id.
10760
+ * @returns The issue that was found. If the offer is valid, this will be undefined.
10761
+ */
10762
+ const collateralToken = ({ collateralAssetsByChainId }) => single("collateral_token", "Validates that offer collateral tokens are in the allowed token list for the offer chain", (offer) => {
10763
+ const allowedCollateralTokens = collateralAssetsByChainId[offer.chainId]?.map((asset) => asset.toLowerCase()) ?? [];
10764
+ if (allowedCollateralTokens.length === 0) return { message: `No allowed collateral tokens for chain ${offer.chainId}` };
10765
+ if (offer.collaterals.length === 0) return { message: "At least one collateral token is required" };
10766
+ if (offer.collaterals.some((collateral) => !allowedCollateralTokens.includes(collateral.asset.toLowerCase()))) return { message: "Collateral token is not allowed" };
10430
10767
  });
10431
10768
  /**
10432
10769
  * A validation rule that checks if the offer's oracle addresses are allowed for its chain.
@@ -10469,9 +10806,11 @@ const amountMutualExclusivity = () => single("amount_mutual_exclusivity", "Valid
10469
10806
  //#region src/gatekeeper/morphoRules.ts
10470
10807
  const morphoRules = (chains$3) => {
10471
10808
  const assetsByChainId = {};
10809
+ const collateralAssetsByChainId = {};
10472
10810
  const oraclesByChainId = {};
10473
10811
  for (const chain of chains$3) {
10474
10812
  assetsByChainId[chain.id] = assets[chain.id.toString()] ?? [];
10813
+ collateralAssetsByChainId[chain.id] = collateralAssets[chain.id.toString()] ?? [];
10475
10814
  oraclesByChainId[chain.id] = oracles[chain.id.toString()] ?? [];
10476
10815
  }
10477
10816
  return [
@@ -10483,7 +10822,8 @@ const morphoRules = (chains$3) => {
10483
10822
  callbacks: [Type$1.BuyWithEmptyCallback, Type$1.SellWithEmptyCallback],
10484
10823
  allowedAddresses: []
10485
10824
  }),
10486
- token({ assetsByChainId }),
10825
+ loanToken({ assetsByChainId }),
10826
+ collateralToken({ collateralAssetsByChainId }),
10487
10827
  oracle({ oraclesByChainId })
10488
10828
  ];
10489
10829
  };
@@ -10895,6 +11235,12 @@ Object.defineProperty(exports, 'Rules', {
10895
11235
  return Rules_exports;
10896
11236
  }
10897
11237
  });
11238
+ Object.defineProperty(exports, 'Tick', {
11239
+ enumerable: true,
11240
+ get: function () {
11241
+ return Tick_exports;
11242
+ }
11243
+ });
10898
11244
  Object.defineProperty(exports, 'Time', {
10899
11245
  enumerable: true,
10900
11246
  get: function () {