@morpho-dev/router 0.7.2 → 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 (65) hide show
  1. package/dist/cli.js +292 -122
  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/index.browser.d.mts +103 -33
  50. package/dist/index.browser.d.mts.map +1 -1
  51. package/dist/index.browser.d.ts +103 -33
  52. package/dist/index.browser.d.ts.map +1 -1
  53. package/dist/index.browser.js +298 -146
  54. package/dist/index.browser.js.map +1 -1
  55. package/dist/index.browser.mjs +293 -147
  56. package/dist/index.browser.mjs.map +1 -1
  57. package/dist/index.node.d.mts +182 -63
  58. package/dist/index.node.d.mts.map +1 -1
  59. package/dist/index.node.d.ts +182 -63
  60. package/dist/index.node.d.ts.map +1 -1
  61. package/dist/index.node.js +342 -127
  62. package/dist/index.node.js.map +1 -1
  63. package/dist/index.node.mjs +337 -128
  64. package/dist/index.node.mjs.map +1 -1
  65. package/package.json +1 -1
@@ -2122,7 +2122,7 @@ const OfferSchema = () => {
2122
2122
  assets: zod.bigint({ coerce: true }).min(0n).max(viem.maxUint256),
2123
2123
  obligationUnits: zod.bigint({ coerce: true }).min(0n).max(viem.maxUint256).optional().default(0n),
2124
2124
  obligationShares: zod.bigint({ coerce: true }).min(0n).max(viem.maxUint256).optional().default(0n),
2125
- price: zod.bigint({ coerce: true }).min(0n).max(viem.maxUint256),
2125
+ tick: zod.coerce.number().int().min(0).max(990),
2126
2126
  maturity: MaturitySchema,
2127
2127
  expiry: zod.number().int().max(Number.MAX_SAFE_INTEGER),
2128
2128
  start: zod.number().int().max(Number.MAX_SAFE_INTEGER),
@@ -2194,7 +2194,7 @@ const serialize = (offer) => ({
2194
2194
  assets: offer.assets.toString(),
2195
2195
  obligationUnits: offer.obligationUnits.toString(),
2196
2196
  obligationShares: offer.obligationShares.toString(),
2197
- price: offer.price.toString(),
2197
+ tick: offer.tick,
2198
2198
  maturity: Number(offer.maturity),
2199
2199
  expiry: Number(offer.expiry),
2200
2200
  start: Number(offer.start),
@@ -2239,14 +2239,13 @@ function random$1(config) {
2239
2239
  [.98, 2]
2240
2240
  ]));
2241
2241
  const buy = config?.buy !== void 0 ? config.buy : bool();
2242
- const ONE = 1000000000000000000n;
2243
- const qMin = buy ? 16 : 4;
2244
- const len = (buy ? 32 : 16) - qMin + 1;
2245
- const pricePairs = Array.from({ length: len }, (_, idx) => {
2246
- const q = qMin + idx;
2247
- 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];
2248
2247
  });
2249
- const price = config?.price ?? weightedChoice(pricePairs);
2248
+ const tick = config?.tick ?? weightedChoice(tickPairs);
2250
2249
  const loanTokenDecimals = config?.assetsDecimals?.[loanToken] ?? 18;
2251
2250
  const unit = BigInt(10) ** BigInt(loanTokenDecimals);
2252
2251
  const amountBase = BigInt(100 + int(999901));
@@ -2260,7 +2259,7 @@ function random$1(config) {
2260
2259
  assets: assetsScaled,
2261
2260
  obligationUnits: config?.obligationUnits ?? 0n,
2262
2261
  obligationShares: config?.obligationShares ?? 0n,
2263
- price,
2262
+ tick,
2264
2263
  maturity,
2265
2264
  expiry: config?.expiry ?? maturity - 1,
2266
2265
  start: config?.start ?? maturity - 10,
@@ -2325,7 +2324,7 @@ const types = {
2325
2324
  type: "uint256"
2326
2325
  },
2327
2326
  {
2328
- name: "price",
2327
+ name: "tick",
2329
2328
  type: "uint256"
2330
2329
  },
2331
2330
  {
@@ -2393,7 +2392,7 @@ function hash(offer) {
2393
2392
  assets: offer.assets,
2394
2393
  obligationUnits: offer.obligationUnits,
2395
2394
  obligationShares: offer.obligationShares,
2396
- price: offer.price,
2395
+ tick: BigInt(offer.tick),
2397
2396
  maturity: BigInt(offer.maturity),
2398
2397
  expiry: BigInt(offer.expiry),
2399
2398
  group: offer.group,
@@ -2444,7 +2443,7 @@ const OfferAbi = [
2444
2443
  type: "uint256"
2445
2444
  },
2446
2445
  {
2447
- name: "price",
2446
+ name: "tick",
2448
2447
  type: "uint256"
2449
2448
  },
2450
2449
  {
@@ -2515,7 +2514,7 @@ function encode$1(offer) {
2515
2514
  offer.assets,
2516
2515
  offer.obligationUnits,
2517
2516
  offer.obligationShares,
2518
- offer.price,
2517
+ BigInt(offer.tick),
2519
2518
  BigInt(offer.maturity),
2520
2519
  BigInt(offer.expiry),
2521
2520
  offer.group,
@@ -2540,7 +2539,7 @@ function decode$1(data) {
2540
2539
  assets: decoded[1],
2541
2540
  obligationUnits: decoded[2],
2542
2541
  obligationShares: decoded[3],
2543
- price: decoded[4],
2542
+ tick: Number(decoded[4]),
2544
2543
  maturity: from$16(Number(decoded[5])),
2545
2544
  expiry: Number(decoded[6]),
2546
2545
  group: decoded[7],
@@ -2890,6 +2889,85 @@ var InvalidQuoteError = class extends BaseError {
2890
2889
  }
2891
2890
  };
2892
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
+
2893
2971
  //#endregion
2894
2972
  //#region src/core/TradingFee.ts
2895
2973
  var TradingFee_exports = /* @__PURE__ */ __exportAll({
@@ -3403,7 +3481,7 @@ const BrandTypeId = Symbol.for("mempool/Brand");
3403
3481
 
3404
3482
  //#endregion
3405
3483
  //#region src/database/drizzle/VERSION.ts
3406
- const VERSION = "router_v1.6";
3484
+ const VERSION = "router_v1.8";
3407
3485
 
3408
3486
  //#endregion
3409
3487
  //#region src/database/drizzle/schema.ts
@@ -3555,10 +3633,7 @@ const offers = s.table(EnumTableName.OFFERS, {
3555
3633
  precision: 78,
3556
3634
  scale: 0
3557
3635
  }).notNull().default("0"),
3558
- price: (0, drizzle_orm_pg_core.numeric)("price", {
3559
- precision: 78,
3560
- scale: 0
3561
- }).notNull(),
3636
+ tick: (0, drizzle_orm_pg_core.integer)("tick").notNull(),
3562
3637
  maturity: (0, drizzle_orm_pg_core.integer)("maturity").notNull(),
3563
3638
  expiry: (0, drizzle_orm_pg_core.integer)("expiry").notNull(),
3564
3639
  start: (0, drizzle_orm_pg_core.integer)("start").notNull(),
@@ -3623,6 +3698,7 @@ const lots = s.table(EnumTableName.LOTS, {
3623
3698
  user: (0, drizzle_orm_pg_core.varchar)("user", { length: 42 }).notNull(),
3624
3699
  contract: (0, drizzle_orm_pg_core.varchar)("contract", { length: 42 }).notNull(),
3625
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(),
3626
3702
  lower: (0, drizzle_orm_pg_core.numeric)("lower", {
3627
3703
  precision: 78,
3628
3704
  scale: 0
@@ -3637,7 +3713,8 @@ const lots = s.table(EnumTableName.LOTS, {
3637
3713
  table.chainId,
3638
3714
  table.user,
3639
3715
  table.contract,
3640
- table.group
3716
+ table.group,
3717
+ table.obligationId
3641
3718
  ],
3642
3719
  name: "lots_pk"
3643
3720
  }),
@@ -3673,6 +3750,7 @@ const offsets = s.table(EnumTableName.OFFSETS, {
3673
3750
  user: (0, drizzle_orm_pg_core.varchar)("user", { length: 42 }).notNull(),
3674
3751
  contract: (0, drizzle_orm_pg_core.varchar)("contract", { length: 42 }).notNull(),
3675
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(),
3676
3754
  value: (0, drizzle_orm_pg_core.numeric)("value", {
3677
3755
  precision: 78,
3678
3756
  scale: 0
@@ -3682,7 +3760,8 @@ const offsets = s.table(EnumTableName.OFFSETS, {
3682
3760
  table.chainId,
3683
3761
  table.user,
3684
3762
  table.contract,
3685
- table.group
3763
+ table.group,
3764
+ table.obligationId
3686
3765
  ],
3687
3766
  name: "offsets_pk"
3688
3767
  }), (0, drizzle_orm_pg_core.foreignKey)({
@@ -4273,6 +4352,7 @@ function decodeCallbacks(parameters) {
4273
4352
  positionContract: loanToken,
4274
4353
  positionUser: offer.maker,
4275
4354
  group: offer.group,
4355
+ obligationId: obligationId(offer),
4276
4356
  size: offer.assets
4277
4357
  });
4278
4358
  callbacks.push({
@@ -5211,8 +5291,10 @@ async function getRemoteBlockNumbers(healthClients) {
5211
5291
  //#region src/api/Schema/BookResponse.ts
5212
5292
  var BookResponse_exports = /* @__PURE__ */ __exportAll({ from: () => from$5 });
5213
5293
  function from$5(level) {
5294
+ const price = tickToPrice(level.tick);
5214
5295
  return {
5215
- price: level.price.toString(),
5296
+ tick: level.tick,
5297
+ price: price.toString(),
5216
5298
  assets: level.assets.toString(),
5217
5299
  count: level.count
5218
5300
  };
@@ -5317,7 +5399,7 @@ function from$3(input) {
5317
5399
  obligation_shares: input.obligationShares.toString(),
5318
5400
  start: input.start,
5319
5401
  expiry: input.expiry,
5320
- price: input.price.toString(),
5402
+ tick: input.tick,
5321
5403
  group: input.group,
5322
5404
  session: input.session,
5323
5405
  callback: input.callback.address,
@@ -5479,7 +5561,7 @@ const offerExample = {
5479
5561
  obligation_shares: "0",
5480
5562
  start: 1761922790,
5481
5563
  expiry: 1761922799,
5482
- price: "2750000000000000000",
5564
+ tick: 495,
5483
5565
  group: "0x000000000000000000000000000000000000000000000000000000000008b8f4",
5484
5566
  session: "0x0000000000000000000000000000000000000000000000000000000000000000",
5485
5567
  callback: "0x0000000000000000000000000000000000000000",
@@ -5520,7 +5602,7 @@ const validateOfferExample = {
5520
5602
  assets: "369216000000000000000000",
5521
5603
  obligation_units: "0",
5522
5604
  obligation_shares: "0",
5523
- price: "2750000000000000000",
5605
+ tick: 495,
5524
5606
  maturity: 1761922799,
5525
5607
  expiry: 1761922799,
5526
5608
  start: 1761922790,
@@ -5664,9 +5746,11 @@ __decorate([(0, openapi_metadata_decorators.ApiProperty)({
5664
5746
  example: offerExample.offer.expiry
5665
5747
  })], OfferDataResponse.prototype, "expiry", void 0);
5666
5748
  __decorate([(0, openapi_metadata_decorators.ApiProperty)({
5667
- type: "string",
5668
- example: offerExample.offer.price
5669
- })], 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);
5670
5754
  __decorate([(0, openapi_metadata_decorators.ApiProperty)({
5671
5755
  type: "string",
5672
5756
  example: offerExample.offer.group
@@ -5907,9 +5991,11 @@ __decorate([(0, openapi_metadata_decorators.ApiProperty)({
5907
5991
  required: false
5908
5992
  })], ValidateOfferRequest.prototype, "obligation_shares", void 0);
5909
5993
  __decorate([(0, openapi_metadata_decorators.ApiProperty)({
5910
- type: "string",
5911
- example: validateOfferExample.price
5912
- })], ValidateOfferRequest.prototype, "price", void 0);
5994
+ type: "number",
5995
+ example: validateOfferExample.tick,
5996
+ minimum: 0,
5997
+ maximum: 990
5998
+ })], ValidateOfferRequest.prototype, "tick", void 0);
5913
5999
  __decorate([(0, openapi_metadata_decorators.ApiProperty)({
5914
6000
  type: "number",
5915
6001
  example: validateOfferExample.maturity
@@ -6009,9 +6095,16 @@ __decorate([(0, openapi_metadata_decorators.ApiProperty)({
6009
6095
  description: "List of validation issues. Returned when any offer fails validation."
6010
6096
  })], ValidationFailureResponse.prototype, "data", void 0);
6011
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);
6012
6104
  __decorate([(0, openapi_metadata_decorators.ApiProperty)({
6013
6105
  type: "string",
6014
- example: "2750000000000000000"
6106
+ example: "500000000000000000",
6107
+ description: "Price derived from tick, scaled by 1e18."
6015
6108
  })], BookLevelResponse.prototype, "price", void 0);
6016
6109
  __decorate([(0, openapi_metadata_decorators.ApiProperty)({
6017
6110
  type: "string",
@@ -6025,6 +6118,7 @@ const positionExample = {
6025
6118
  chain_id: 1,
6026
6119
  contract: "0xC9A9C45C0eB717f8b5F193Af6bAa05A1c0Ac5078",
6027
6120
  user: "0x7b093658BE7f90B63D7c359e8f408e503c2D9401",
6121
+ obligation_id: "0x12590ae1aee324a005be565f3bcdd16dbf8daf7969b26c181c8b8f467dad9f67",
6028
6122
  reserved: "200000000000000000000",
6029
6123
  block_number: 21345678
6030
6124
  };
@@ -6041,6 +6135,12 @@ __decorate([(0, openapi_metadata_decorators.ApiProperty)({
6041
6135
  type: "string",
6042
6136
  example: positionExample.user
6043
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);
6044
6144
  __decorate([(0, openapi_metadata_decorators.ApiProperty)({
6045
6145
  type: "string",
6046
6146
  example: positionExample.reserved
@@ -6068,7 +6168,7 @@ __decorate([(0, openapi_metadata_decorators.ApiProperty)({
6068
6168
  })], BookListResponse.prototype, "cursor", void 0);
6069
6169
  __decorate([(0, openapi_metadata_decorators.ApiProperty)({
6070
6170
  type: () => [BookLevelResponse],
6071
- description: "Aggregated book levels grouped by computed price."
6171
+ description: "Aggregated book levels grouped by offer tick."
6072
6172
  })], BookListResponse.prototype, "data", void 0);
6073
6173
  let BooksController = class BooksController {
6074
6174
  async getBook() {}
@@ -6078,7 +6178,7 @@ __decorate([
6078
6178
  methods: ["get"],
6079
6179
  path: "/v1/books/{obligationId}/{side}",
6080
6180
  summary: "Get aggregated book",
6081
- 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)."
6082
6182
  }),
6083
6183
  (0, openapi_metadata_decorators.ApiParam)({
6084
6184
  name: "obligationId",
@@ -6103,7 +6203,7 @@ __decorate([
6103
6203
  name: "limit",
6104
6204
  type: "number",
6105
6205
  example: 10,
6106
- description: "Maximum number of price levels to return."
6206
+ description: "Maximum number of tick levels to return."
6107
6207
  }),
6108
6208
  (0, openapi_metadata_decorators.ApiResponse)({
6109
6209
  status: 200,
@@ -6297,6 +6397,11 @@ const configRulesLoanTokenExample = {
6297
6397
  chain_id: 1,
6298
6398
  address: "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48"
6299
6399
  };
6400
+ const configRulesCollateralTokenExample = {
6401
+ type: "collateral_token",
6402
+ chain_id: 1,
6403
+ address: "0x7f39C581F595B53c5cb19bD0b3f8dA6c935E2Ca0"
6404
+ };
6300
6405
  const configRulesOracleExample = {
6301
6406
  type: "oracle",
6302
6407
  chain_id: 1,
@@ -6306,6 +6411,7 @@ const configRulesChecksumExample = "f1d2d2f924e986ac86fdf7b36c94bcdf";
6306
6411
  const configRulesPayloadExample = [
6307
6412
  configRulesMaturityExample,
6308
6413
  configRulesLoanTokenExample,
6414
+ configRulesCollateralTokenExample,
6309
6415
  configRulesOracleExample
6310
6416
  ];
6311
6417
  const configContractNames = [
@@ -6432,7 +6538,7 @@ __decorate([
6432
6538
  methods: ["get"],
6433
6539
  path: "/v1/config/rules",
6434
6540
  summary: "Get config rules",
6435
- 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."
6436
6542
  }),
6437
6543
  (0, openapi_metadata_decorators.ApiQuery)({
6438
6544
  name: "cursor",
@@ -6452,7 +6558,7 @@ __decorate([
6452
6558
  name: "types",
6453
6559
  type: ["string"],
6454
6560
  required: false,
6455
- example: "maturity,loan_token,oracle",
6561
+ example: "maturity,loan_token,collateral_token,oracle",
6456
6562
  description: "Filter by rule types (comma-separated).",
6457
6563
  style: "form",
6458
6564
  explode: false
@@ -6570,7 +6676,7 @@ __decorate([
6570
6676
  methods: ["get"],
6571
6677
  path: "/v1/users/{userAddress}/positions",
6572
6678
  summary: "Get user positions",
6573
- 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."
6574
6680
  }),
6575
6681
  (0, openapi_metadata_decorators.ApiParam)({
6576
6682
  name: "userAddress",
@@ -6658,6 +6764,7 @@ function from$2(position) {
6658
6764
  chain_id: position.chainId,
6659
6765
  contract: position.contract,
6660
6766
  user: position.user,
6767
+ obligation_id: position.obligationId,
6661
6768
  reserved: position.reserved.toString(),
6662
6769
  block_number: position.blockNumber
6663
6770
  };
@@ -6711,10 +6818,11 @@ const ConfigRuleTypes = zod.enum([
6711
6818
  "maturity",
6712
6819
  "callback",
6713
6820
  "loan_token",
6821
+ "collateral_token",
6714
6822
  "oracle"
6715
6823
  ]);
6716
6824
  const GetConfigRulesQueryParams = zod.object({
6717
- 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({
6718
6826
  description: "Pagination cursor in type:chain_id:<value> format",
6719
6827
  example: "maturity:1:1730415600:end_of_next_month"
6720
6828
  }),
@@ -6724,7 +6832,7 @@ const GetConfigRulesQueryParams = zod.object({
6724
6832
  }),
6725
6833
  types: csvArray(ConfigRuleTypes).meta({
6726
6834
  description: "Filter by rule types (comma-separated).",
6727
- example: "maturity,loan_token,oracle"
6835
+ example: "maturity,loan_token,collateral_token,oracle"
6728
6836
  }),
6729
6837
  chains: csvArray(zod.string().regex(/^[1-9]\d*$/, { message: "Chain must be a positive integer" }).transform((val) => Number.parseInt(val, 10))).meta({
6730
6838
  description: "Filter by chain IDs (comma-separated).",
@@ -6824,12 +6932,11 @@ const GetObligationParams = zod.object({ obligation_id: zod.string({ error: "Obl
6824
6932
  description: "Obligation id",
6825
6933
  example: "0x1234567890123456789012345678901234567890123456789012345678901234"
6826
6934
  }) });
6827
- /** Validate a book cursor format: {side, lastPrice, offersCursor} */
6935
+ /** Validate a book cursor format: {side, lastTick, offersCursor} */
6828
6936
  function isValidBookCursor(cursorString) {
6829
- const isNumericString = (value) => typeof value === "string" && /^-?\d+$/.test(value);
6830
6937
  try {
6831
6938
  const v = JSON.parse(Buffer.from(cursorString, "base64url").toString("utf8"));
6832
- 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");
6833
6940
  } catch {
6834
6941
  return false;
6835
6942
  }
@@ -6927,7 +7034,7 @@ async function getBook(params, db) {
6927
7034
  side: query.side,
6928
7035
  levels_count: levels.length,
6929
7036
  has_next_cursor: nextCursor != null,
6930
- first_level_price: firstLevel?.price.toString() ?? null,
7037
+ first_level_tick: firstLevel?.tick ?? null,
6931
7038
  first_level_assets: firstLevel?.assets.toString() ?? null,
6932
7039
  first_level_count: firstLevel?.count ?? null
6933
7040
  });
@@ -7079,6 +7186,33 @@ const assets = {
7079
7186
  "0x2260FAC5E5542a773Aa44fBCfeDf7C193bc2C599"
7080
7187
  ]
7081
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
+ };
7082
7216
  const oracles = {
7083
7217
  [ChainId.ETHEREUM.toString()]: [
7084
7218
  "0xDddd770BADd886dF3864029e4B377B5F6a2B6b83",
@@ -7138,7 +7272,7 @@ const configs = {
7138
7272
  //#endregion
7139
7273
  //#region src/gatekeeper/ConfigRules.ts
7140
7274
  /**
7141
- * 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.
7142
7276
  * @param chains - Chains to include in the configured rules.
7143
7277
  * @returns Sorted list of config rules.
7144
7278
  */
@@ -7158,6 +7292,12 @@ function buildConfigRules(chains) {
7158
7292
  chain_id: chain.id,
7159
7293
  address: normalizeAddress(address)
7160
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
+ });
7161
7301
  const oracles$2 = oracles[chain.id.toString()] ?? [];
7162
7302
  for (const address of oracles$2) rules.push({
7163
7303
  type: "oracle",
@@ -7185,6 +7325,10 @@ function buildConfigRulesChecksum(rules) {
7185
7325
  hash.update(`callback:${rule.chain_id}:${rule.callback_type}:${rule.address}\n`);
7186
7326
  continue;
7187
7327
  }
7328
+ if (rule.type === "collateral_token") {
7329
+ hash.update(`collateral_token:${rule.chain_id}:${rule.address}\n`);
7330
+ continue;
7331
+ }
7188
7332
  if (rule.type === "oracle") {
7189
7333
  hash.update(`oracle:${rule.chain_id}:${rule.address}\n`);
7190
7334
  continue;
@@ -7205,6 +7349,7 @@ function compareConfigRules(left, right) {
7205
7349
  return left.address.localeCompare(right.address);
7206
7350
  }
7207
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);
7208
7353
  if (left.type === "oracle" && right.type === "oracle") return left.address.localeCompare(right.address);
7209
7354
  return 0;
7210
7355
  }
@@ -7251,6 +7396,7 @@ function formatCursor(rule) {
7251
7396
  if (rule.type === "maturity") return `maturity:${rule.chain_id}:${rule.timestamp}:${rule.name}`;
7252
7397
  if (rule.type === "callback") return `callback:${rule.chain_id}:${rule.callback_type}:${rule.address.toLowerCase()}`;
7253
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()}`;
7254
7400
  return `loan_token:${rule.chain_id}:${rule.address.toLowerCase()}`;
7255
7401
  }
7256
7402
  function parseCursor(cursor) {
@@ -7283,7 +7429,7 @@ function parseCursor(cursor) {
7283
7429
  address: parseAddress(addressValue, "Cursor address")
7284
7430
  };
7285
7431
  }
7286
- if (type === "loan_token" || type === "oracle") {
7432
+ if (type === "loan_token" || type === "collateral_token" || type === "oracle") {
7287
7433
  const addressValue = rest.join(":");
7288
7434
  if (!addressValue) throw new BadRequestError(`Cursor must be in the format ${type}:chain_id:address`);
7289
7435
  return {
@@ -7310,7 +7456,7 @@ function parseAddress(address, label) {
7310
7456
  return address.toLowerCase();
7311
7457
  }
7312
7458
  function isConfigRuleType(value) {
7313
- return value === "maturity" || value === "callback" || value === "loan_token" || value === "oracle";
7459
+ return value === "maturity" || value === "callback" || value === "loan_token" || value === "collateral_token" || value === "oracle";
7314
7460
  }
7315
7461
  function isMaturityType(value) {
7316
7462
  return Object.values(MaturityType).includes(value);
@@ -7657,7 +7803,7 @@ function create$15(config) {
7657
7803
  assets: offers.assets,
7658
7804
  obligationUnits: offers.obligationUnits,
7659
7805
  obligationShares: offers.obligationShares,
7660
- price: offers.price,
7806
+ tick: offers.tick,
7661
7807
  maturity: offers.maturity,
7662
7808
  expiry: offers.expiry,
7663
7809
  start: offers.start,
@@ -7677,7 +7823,7 @@ function create$15(config) {
7677
7823
  assets: BigInt(row.assets),
7678
7824
  obligationUnits: BigInt(row.obligationUnits),
7679
7825
  obligationShares: BigInt(row.obligationShares),
7680
- price: BigInt(row.price),
7826
+ tick: row.tick,
7681
7827
  maturity: from$16(row.maturity),
7682
7828
  expiry: row.expiry,
7683
7829
  start: row.start,
@@ -7759,8 +7905,8 @@ function create$15(config) {
7759
7905
  const now$2 = now();
7760
7906
  const query = ({ side }) => db.selectDistinctOn([offers.obligationId], {
7761
7907
  obligationId: offers.obligationId,
7762
- price: offers.price
7763
- }).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`);
7764
7910
  const [bestBuys, bestSells] = await Promise.all([query({ side: "buy" }), query({ side: "sell" })]);
7765
7911
  const quotes = /* @__PURE__ */ new Map();
7766
7912
  for (const row of bestSells) quotes.set(row.obligationId, {
@@ -7876,7 +8022,7 @@ async function getOffersQuery(db, parameters) {
7876
8022
  obligationUnits: offers.obligationUnits,
7877
8023
  obligationShares: offers.obligationShares,
7878
8024
  consumed: groups.consumed,
7879
- price: offers.price,
8025
+ tick: offers.tick,
7880
8026
  maturity: offers.maturity,
7881
8027
  expiry: offers.expiry,
7882
8028
  start: offers.start,
@@ -7914,7 +8060,7 @@ async function getOffersQuery(db, parameters) {
7914
8060
  assets: BigInt(row.assets),
7915
8061
  obligationUnits: BigInt(row.obligationUnits),
7916
8062
  obligationShares: BigInt(row.obligationShares),
7917
- price: BigInt(row.price),
8063
+ tick: row.tick,
7918
8064
  maturity: from$16(row.maturity),
7919
8065
  expiry: row.expiry,
7920
8066
  start: row.start,
@@ -8313,7 +8459,7 @@ async function getOffers(apiClient, parameters) {
8313
8459
  assets: offerData.assets,
8314
8460
  obligation_units: offerData.obligation_units,
8315
8461
  obligation_shares: offerData.obligation_shares,
8316
- price: offerData.price,
8462
+ tick: offerData.tick,
8317
8463
  maturity: from$16(offerData.obligation.maturity),
8318
8464
  expiry: offerData.expiry,
8319
8465
  start: offerData.start,
@@ -8672,6 +8818,7 @@ function create$12(config) {
8672
8818
  return {
8673
8819
  get: async (parameters) => {
8674
8820
  const { side, obligationId, cursor: cursorString, limit = DEFAULT_LIMIT$2 } = parameters;
8821
+ const tickSortDirection = side === "sell" ? "asc" : "desc";
8675
8822
  const inputCursor = LevelCursor.decode(cursorString, logger);
8676
8823
  if (cursorString != null && inputCursor === null) return {
8677
8824
  levels: [],
@@ -8686,23 +8833,23 @@ function create$12(config) {
8686
8833
  cursor: inputCursor?.offersCursor ?? void 0,
8687
8834
  limit: fetchLimit
8688
8835
  });
8689
- const priceMap = /* @__PURE__ */ new Map();
8836
+ const tickMap = /* @__PURE__ */ new Map();
8690
8837
  for (const row of rows) {
8691
- const priceKey = row.price.toString();
8692
- const existing = priceMap.get(priceKey);
8838
+ const existing = tickMap.get(row.tick);
8693
8839
  if (existing) {
8694
8840
  existing.assets += row.takeable;
8695
8841
  existing.count += 1;
8696
- } else priceMap.set(priceKey, {
8842
+ } else tickMap.set(row.tick, {
8697
8843
  assets: row.takeable,
8698
8844
  count: 1
8699
8845
  });
8700
8846
  }
8701
- const levels = Array.from(priceMap.entries()).map(([price, data]) => ({
8702
- price: BigInt(price),
8703
- assets: data.assets,
8704
- count: data.count
8847
+ const levels = Array.from(tickMap.entries()).map(([tick, level]) => ({
8848
+ tick,
8849
+ assets: level.assets,
8850
+ count: level.count
8705
8851
  }));
8852
+ levels.sort((a, b) => tickSortDirection === "asc" ? a.tick - b.tick : b.tick - a.tick);
8706
8853
  const paginatedLevels = levels.slice(0, limit);
8707
8854
  const hasMore = levels.length > limit || offersNextCursor !== null;
8708
8855
  const lastLevel = paginatedLevels[paginatedLevels.length - 1];
@@ -8748,14 +8895,14 @@ async function _getOffers(db, params) {
8748
8895
  AND (s.code IS NULL OR s.code = ${Status.VALID})
8749
8896
  ORDER BY
8750
8897
  o.group_chain_id, o.group_maker, o."group_group",
8751
- 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
8752
8899
  ),
8753
8900
  enriched AS (
8754
8901
  SELECT
8755
8902
  w.*,
8756
8903
  g.consumed, g.chain_id, obl.loan_token,
8757
8904
  CASE WHEN ${priceSortDirection === "asc" ? drizzle_orm.sql`TRUE` : drizzle_orm.sql`FALSE`}
8758
- THEN w.price::numeric ELSE -w.price::numeric END AS price_norm,
8905
+ THEN w.tick ELSE -w.tick END AS tick_norm,
8759
8906
  w.block_number AS block_norm,
8760
8907
  -w.assets AS assets_norm,
8761
8908
  w.hash AS hash_norm
@@ -8772,33 +8919,35 @@ async function _getOffers(db, params) {
8772
8919
  FROM enriched e
8773
8920
  ${cursor != null ? drizzle_orm.sql`
8774
8921
  WHERE
8775
- (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)
8776
8923
  > (
8777
8924
  CASE WHEN ${priceSortDirection === "asc" ? drizzle_orm.sql`TRUE` : drizzle_orm.sql`FALSE`}
8778
- THEN ${cursor.price}::numeric ELSE -${cursor.price}::numeric END,
8925
+ THEN ${cursor.tick}::integer ELSE -${cursor.tick}::integer END,
8779
8926
  ${cursor.blockNumber},
8780
8927
  -${cursor.assets}::numeric,
8781
8928
  ${cursor.hash}
8782
8929
  )` : drizzle_orm.sql``}
8783
- 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
8784
8931
  LIMIT ${limit}
8785
8932
  ),
8786
- -- Compute sum of offsets per position
8933
+ -- Compute sum of offsets per position and obligation
8787
8934
  position_offsets AS (
8788
8935
  SELECT
8789
8936
  chain_id,
8790
8937
  "user",
8791
8938
  contract,
8939
+ obligation_id,
8792
8940
  SUM(value::numeric) AS total_offset
8793
8941
  FROM ${offsets}
8794
- GROUP BY chain_id, "user", contract
8942
+ GROUP BY chain_id, "user", contract, obligation_id
8795
8943
  ),
8796
- -- 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)
8797
8945
  position_consumed AS (
8798
8946
  SELECT
8799
8947
  l.chain_id,
8800
8948
  l.contract,
8801
8949
  l."user",
8950
+ l.obligation_id,
8802
8951
  SUM(
8803
8952
  CASE
8804
8953
  WHEN wo.assets::numeric > 0
@@ -8815,7 +8964,7 @@ async function _getOffers(db, params) {
8815
8964
  ON wo.group_chain_id = g.chain_id
8816
8965
  AND LOWER(wo.group_maker) = LOWER(g.maker)
8817
8966
  AND wo.group_group = g."group"
8818
- GROUP BY l.chain_id, l.contract, l."user"
8967
+ GROUP BY l.chain_id, l.contract, l."user", l.obligation_id
8819
8968
  ),
8820
8969
  -- Compute callback contributions with lot balance
8821
8970
  callback_contributions AS (
@@ -8823,7 +8972,7 @@ async function _getOffers(db, params) {
8823
8972
  p.hash,
8824
8973
  p.obligation_id,
8825
8974
  p.assets,
8826
- p.price,
8975
+ p.tick,
8827
8976
  p.obligation_units,
8828
8977
  p.obligation_shares,
8829
8978
  p.maturity,
@@ -8868,6 +9017,7 @@ async function _getOffers(db, params) {
8868
9017
  AND LOWER(l.contract) = LOWER(c.position_contract)
8869
9018
  AND LOWER(l."user") = LOWER(c.position_user)
8870
9019
  AND l."group" = p.group_group
9020
+ AND l.obligation_id = p.obligation_id
8871
9021
  LEFT JOIN ${positions} pos
8872
9022
  ON pos.chain_id = c.position_chain_id
8873
9023
  AND LOWER(pos.contract) = LOWER(c.position_contract)
@@ -8876,10 +9026,12 @@ async function _getOffers(db, params) {
8876
9026
  ON pos_offsets.chain_id = c.position_chain_id
8877
9027
  AND LOWER(pos_offsets.contract) = LOWER(c.position_contract)
8878
9028
  AND LOWER(pos_offsets."user") = LOWER(c.position_user)
9029
+ AND pos_offsets.obligation_id = p.obligation_id
8879
9030
  LEFT JOIN position_consumed pc
8880
9031
  ON pc.chain_id = c.position_chain_id
8881
9032
  AND LOWER(pc.contract) = LOWER(c.position_contract)
8882
9033
  AND LOWER(pc."user") = LOWER(c.position_user)
9034
+ AND pc.obligation_id = p.obligation_id
8883
9035
  ),
8884
9036
  -- Compute contribution per callback in loan terms (loan token only — collateral positions are not indexed)
8885
9037
  callback_loan_contribution AS (
@@ -8897,7 +9049,7 @@ async function _getOffers(db, params) {
8897
9049
  hash,
8898
9050
  obligation_id,
8899
9051
  assets,
8900
- price,
9052
+ tick,
8901
9053
  obligation_units,
8902
9054
  obligation_shares,
8903
9055
  maturity,
@@ -8923,13 +9075,13 @@ async function _getOffers(db, params) {
8923
9075
  WHERE clc.callback_id IS NOT NULL
8924
9076
  ORDER BY clc.hash, clc.position_chain_id, clc.position_contract, clc.position_user, clc.contribution_in_loan DESC
8925
9077
  ) deduped
8926
- 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,
8927
9079
  callback_address, callback_data, block_number, group_chain_id, group_maker,
8928
9080
  consumed, chain_id, loan_token, session
8929
9081
  UNION ALL
8930
9082
  -- Sell offers without callbacks: collateral positions not indexed, takeable = assets - consumed
8931
9083
  SELECT
8932
- p.hash, p.obligation_id, p.assets, p.price,
9084
+ p.hash, p.obligation_id, p.assets, p.tick,
8933
9085
  p.obligation_units, p.obligation_shares,
8934
9086
  p.maturity, p.expiry, p.start, p.group_group,
8935
9087
  p.buy, p.callback_address, p.callback_data,
@@ -8951,7 +9103,7 @@ async function _getOffers(db, params) {
8951
9103
  oc.obligation_units,
8952
9104
  oc.obligation_shares,
8953
9105
  oc.consumed,
8954
- oc.price,
9106
+ oc.tick,
8955
9107
  oc.maturity,
8956
9108
  oc.expiry,
8957
9109
  oc.start,
@@ -8983,7 +9135,7 @@ async function _getOffers(db, params) {
8983
9135
  ))
8984
9136
  END > 0
8985
9137
  ORDER BY
8986
- 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`},
8987
9139
  oc.block_number ASC,
8988
9140
  oc.assets DESC,
8989
9141
  oc.hash ASC;
@@ -8996,7 +9148,7 @@ async function _getOffers(db, params) {
8996
9148
  assets: BigInt(row.assets),
8997
9149
  obligationUnits: BigInt(row.obligation_units ?? 0),
8998
9150
  obligationShares: BigInt(row.obligation_shares ?? 0),
8999
- price: BigInt(row.price),
9151
+ tick: row.tick,
9000
9152
  maturity: row.maturity,
9001
9153
  expiry: row.expiry,
9002
9154
  start: row.start,
@@ -9028,7 +9180,7 @@ let Cursor;
9028
9180
  function encode(row, totalReturned, now, side) {
9029
9181
  return Buffer.from(JSON.stringify({
9030
9182
  side,
9031
- price: row.price.toString(),
9183
+ tick: row.tick,
9032
9184
  blockNumber: row.blockNumber,
9033
9185
  assets: row.assets.toString(),
9034
9186
  hash: row.hash,
@@ -9039,10 +9191,9 @@ let Cursor;
9039
9191
  _Cursor.encode = encode;
9040
9192
  function decode(cursorString, logger) {
9041
9193
  if (cursorString == null) return null;
9042
- const isNumericString = (value) => typeof value === "string" && /^-?\d+$/.test(value);
9043
9194
  try {
9044
9195
  const v = JSON.parse(Buffer.from(cursorString, "base64url").toString("utf8"));
9045
- 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;
9046
9197
  throw new Error("Invalid cursor");
9047
9198
  } catch {
9048
9199
  logger.error({
@@ -9060,7 +9211,7 @@ let LevelCursor;
9060
9211
  function encode(lastLevel, offersCursor, side, now) {
9061
9212
  return Buffer.from(JSON.stringify({
9062
9213
  side,
9063
- lastPrice: lastLevel.price.toString(),
9214
+ lastTick: lastLevel.tick,
9064
9215
  now,
9065
9216
  offersCursor
9066
9217
  })).toString("base64url");
@@ -9068,10 +9219,9 @@ let LevelCursor;
9068
9219
  _LevelCursor.encode = encode;
9069
9220
  function decode(cursorString, logger) {
9070
9221
  if (cursorString == null) return null;
9071
- const isNumericString = (value) => typeof value === "string" && /^-?\d+$/.test(value);
9072
9222
  try {
9073
9223
  const v = JSON.parse(Buffer.from(cursorString, "base64url").toString("utf8"));
9074
- 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;
9075
9225
  throw new Error("Invalid book cursor");
9076
9226
  } catch {
9077
9227
  logger.error({
@@ -9214,31 +9364,33 @@ function create$9(db) {
9214
9364
  function create$8(db) {
9215
9365
  return {
9216
9366
  get: async (parameters) => {
9217
- const { chainId, user, contract, group } = parameters ?? {};
9367
+ const { chainId, user, contract, group, obligationId } = parameters ?? {};
9218
9368
  const conditions = [];
9219
9369
  if (chainId !== void 0) conditions.push((0, drizzle_orm.eq)(lots.chainId, chainId));
9220
9370
  if (user !== void 0) conditions.push((0, drizzle_orm.eq)(lots.user, user.toLowerCase()));
9221
9371
  if (contract !== void 0) conditions.push((0, drizzle_orm.eq)(lots.contract, contract.toLowerCase()));
9222
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));
9223
9374
  return (await db.select().from(lots).where(conditions.length > 0 ? (0, drizzle_orm.and)(...conditions) : void 0)).map((row) => ({
9224
9375
  chainId: row.chainId,
9225
9376
  user: row.user,
9226
9377
  contract: row.contract,
9227
9378
  group: row.group,
9379
+ obligationId: row.obligationId,
9228
9380
  lower: BigInt(row.lower),
9229
9381
  upper: BigInt(row.upper)
9230
9382
  }));
9231
9383
  },
9232
9384
  create: async (parameters) => {
9233
9385
  if (parameters.length === 0) return;
9234
- const lotsByPositionGroup = /* @__PURE__ */ new Map();
9386
+ const lotsByKey = /* @__PURE__ */ new Map();
9235
9387
  for (const offer of parameters) {
9236
- const key = `${offer.positionChainId}-${offer.positionContract}-${offer.positionUser}-${offer.group}`.toLowerCase();
9237
- const existing = lotsByPositionGroup.get(key);
9238
- 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);
9239
9391
  }
9240
- 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) {
9241
- 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())));
9242
9394
  const newLower = BigInt(maxUpperResult[0]?.maxUpper ?? "0");
9243
9395
  const newUpper = newLower + offer.size;
9244
9396
  await db.insert(lots).values({
@@ -9246,6 +9398,7 @@ function create$8(db) {
9246
9398
  user: offer.positionUser.toLowerCase(),
9247
9399
  contract: offer.positionContract.toLowerCase(),
9248
9400
  group: offer.group.toLowerCase(),
9401
+ obligationId: offer.obligationId.toLowerCase(),
9249
9402
  lower: newLower.toString(),
9250
9403
  upper: newUpper.toString()
9251
9404
  });
@@ -9300,17 +9453,19 @@ function create$7(db) {
9300
9453
  //#region src/database/domains/Offsets.ts
9301
9454
  function create$6(db) {
9302
9455
  return { get: async (parameters) => {
9303
- const { chainId, user, contract, group } = parameters ?? {};
9456
+ const { chainId, user, contract, group, obligationId } = parameters ?? {};
9304
9457
  const conditions = [];
9305
9458
  if (chainId !== void 0) conditions.push((0, drizzle_orm.eq)(offsets.chainId, chainId));
9306
9459
  if (user !== void 0) conditions.push((0, drizzle_orm.eq)(offsets.user, user.toLowerCase()));
9307
9460
  if (contract !== void 0) conditions.push((0, drizzle_orm.eq)(offsets.contract, contract.toLowerCase()));
9308
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));
9309
9463
  return (await db.select().from(offsets).where(conditions.length > 0 ? (0, drizzle_orm.and)(...conditions) : void 0)).map((row) => ({
9310
9464
  chainId: row.chainId,
9311
9465
  user: row.user,
9312
9466
  contract: row.contract,
9313
9467
  group: row.group,
9468
+ obligationId: row.obligationId,
9314
9469
  value: BigInt(row.value)
9315
9470
  }));
9316
9471
  } };
@@ -9460,7 +9615,8 @@ const create$4 = (db) => {
9460
9615
  if (!parsed.chainId || !parsed.contract) throw new Error("Invalid cursor format");
9461
9616
  cursor = {
9462
9617
  chainId: parsed.chainId,
9463
- contract: parsed.contract
9618
+ contract: parsed.contract,
9619
+ obligationId: parsed.obligationId ?? null
9464
9620
  };
9465
9621
  }
9466
9622
  const raw = await db.execute(drizzle_orm.sql`
@@ -9469,16 +9625,18 @@ const create$4 = (db) => {
9469
9625
  chain_id,
9470
9626
  "user",
9471
9627
  contract,
9628
+ obligation_id,
9472
9629
  SUM(value::numeric) AS total_offset
9473
9630
  FROM ${offsets}
9474
9631
  WHERE LOWER("user") = LOWER(${user})
9475
- GROUP BY chain_id, "user", contract
9632
+ GROUP BY chain_id, "user", contract, obligation_id
9476
9633
  ),
9477
9634
  position_consumed AS (
9478
9635
  SELECT
9479
9636
  l.chain_id,
9480
9637
  l.contract,
9481
9638
  l."user",
9639
+ l.obligation_id,
9482
9640
  SUM(
9483
9641
  CASE
9484
9642
  WHEN offer_agg.assets > 0
@@ -9504,50 +9662,64 @@ const create$4 = (db) => {
9504
9662
  AND LOWER(offer_agg.group_maker) = LOWER(g.maker)
9505
9663
  AND offer_agg."group_group" = g."group"
9506
9664
  WHERE LOWER(l."user") = LOWER(${user})
9507
- GROUP BY l.chain_id, l.contract, l."user"
9665
+ GROUP BY l.chain_id, l.contract, l."user", l.obligation_id
9508
9666
  ),
9509
9667
  position_max_lot AS (
9510
9668
  SELECT
9511
9669
  chain_id,
9512
9670
  contract,
9513
9671
  "user",
9672
+ obligation_id,
9514
9673
  MAX(upper::numeric) AS max_upper
9515
9674
  FROM ${lots}
9516
9675
  WHERE LOWER("user") = LOWER(${user})
9517
- 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
9518
9700
  )
9519
9701
  SELECT
9520
9702
  p.chain_id,
9521
9703
  p.contract,
9522
9704
  p."user",
9523
9705
  p.block_number,
9524
- GREATEST(0,
9525
- COALESCE(pml.max_upper, 0)
9526
- - COALESCE(po.total_offset, 0)
9527
- - COALESCE(pc.consumed, 0)
9528
- )::text AS reserved_balance
9706
+ po.obligation_id,
9707
+ COALESCE(po.reserved_balance, '0') AS reserved_balance
9529
9708
  FROM ${positions} p
9530
- LEFT JOIN position_offsets po
9709
+ LEFT JOIN per_obligation po
9531
9710
  ON po.chain_id = p.chain_id
9532
9711
  AND LOWER(po.contract) = LOWER(p.contract)
9533
9712
  AND LOWER(po."user") = LOWER(p."user")
9534
- LEFT JOIN position_consumed pc
9535
- ON pc.chain_id = p.chain_id
9536
- AND LOWER(pc.contract) = LOWER(p.contract)
9537
- AND LOWER(pc."user") = LOWER(p."user")
9538
- LEFT JOIN position_max_lot pml
9539
- ON pml.chain_id = p.chain_id
9540
- AND LOWER(pml.contract) = LOWER(p.contract)
9541
- AND LOWER(pml."user") = LOWER(p."user")
9542
9713
  WHERE LOWER(p."user") = LOWER(${user})
9543
9714
  AND p."user" != ${viem.zeroAddress}
9544
- ${cursor !== null ? drizzle_orm.sql`AND (p.chain_id, p.contract) > (${cursor.chainId}, ${cursor.contract})` : drizzle_orm.sql``}
9545
- 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
9546
9717
  LIMIT ${limit}
9547
9718
  `);
9548
9719
  const nextCursor = raw.rows.length === limit ? Buffer.from(JSON.stringify({
9549
9720
  chainId: raw.rows[raw.rows.length - 1].chain_id.toString(),
9550
- 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
9551
9723
  })).toString("base64url") : null;
9552
9724
  return {
9553
9725
  positions: raw.rows.map((row) => ({
@@ -9555,6 +9727,7 @@ const create$4 = (db) => {
9555
9727
  contract: row.contract,
9556
9728
  user: row.user,
9557
9729
  blockNumber: row.block_number,
9730
+ obligationId: row.obligation_id,
9558
9731
  reserved: BigInt(row.reserved_balance.split(".")[0] ?? "0")
9559
9732
  })),
9560
9733
  nextCursor
@@ -9833,7 +10006,10 @@ function create$1(db) {
9833
10006
 
9834
10007
  //#endregion
9835
10008
  //#region src/database/Database.ts
9836
- var Database_exports = /* @__PURE__ */ __exportAll({ connect: () => connect$1 });
10009
+ var Database_exports = /* @__PURE__ */ __exportAll({
10010
+ connect: () => connect$1,
10011
+ getSchemaNamesForMigration: () => getSchemaNamesForMigration
10012
+ });
9837
10013
  function createDomains(core, chainRegistry) {
9838
10014
  return {
9839
10015
  book: create$12({ db: core }),
@@ -9930,6 +10106,7 @@ function augmentWithDomains(base, chainRegistry) {
9930
10106
  return wrapped;
9931
10107
  }
9932
10108
  const InMemoryDbMap = /* @__PURE__ */ new Map();
10109
+ const LEGACY_SCHEMA_START_MINOR = 7;
9933
10110
  /**
9934
10111
  * Connect to the database.
9935
10112
  * @notice If no connection string is provided, an in-process PGLite database is created.
@@ -9984,9 +10161,26 @@ function applyMigrations(kind, driver) {
9984
10161
  async function preMigrate(driver) {
9985
10162
  const tracer = getTracer("db.preMigrate");
9986
10163
  await startActiveSpan(tracer, "db.preMigrate", async () => {
9987
- 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}"`);
9988
10166
  });
9989
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
+ }
9990
10184
  async function postMigrate(driver) {
9991
10185
  const tracer = getTracer("db.postMigrate");
9992
10186
  await startActiveSpan(tracer, "db.postMigrate", async () => {
@@ -10256,15 +10450,16 @@ async function postMigrate(driver) {
10256
10450
  RETURNS trigger
10257
10451
  LANGUAGE plpgsql AS $$
10258
10452
  BEGIN
10259
- INSERT INTO "${VERSION}"."offsets" (chain_id, "user", contract, "group", value)
10453
+ INSERT INTO "${VERSION}"."offsets" (chain_id, "user", contract, "group", obligation_id, value)
10260
10454
  VALUES (
10261
10455
  OLD.chain_id,
10262
10456
  OLD."user",
10263
10457
  OLD.contract,
10264
10458
  OLD."group",
10459
+ OLD.obligation_id,
10265
10460
  OLD.upper::numeric - OLD.lower::numeric
10266
10461
  )
10267
- ON CONFLICT (chain_id, "user", contract, "group") DO NOTHING;
10462
+ ON CONFLICT (chain_id, "user", contract, "group", obligation_id) DO NOTHING;
10268
10463
  RETURN OLD;
10269
10464
  END;
10270
10465
  $$;
@@ -10518,10 +10713,11 @@ var Rules_exports = /* @__PURE__ */ __exportAll({
10518
10713
  amountMutualExclusivity: () => amountMutualExclusivity,
10519
10714
  callback: () => callback,
10520
10715
  chains: () => chains,
10716
+ collateralToken: () => collateralToken,
10717
+ loanToken: () => loanToken,
10521
10718
  maturity: () => maturity,
10522
10719
  oracle: () => oracle,
10523
10720
  sameMaker: () => sameMaker,
10524
- token: () => token,
10525
10721
  validity: () => validity
10526
10722
  });
10527
10723
  /**
@@ -10549,15 +10745,25 @@ const callback = ({ callbacks }) => single("callback", `Validates callbacks: buy
10549
10745
  if (isEmptyCallback(offer) && !offer.buy && !callbacks.includes(Type$1.SellWithEmptyCallback)) return { message: "Sell offers with empty callback not allowed." };
10550
10746
  });
10551
10747
  /**
10552
- * A validation rule that checks if the offer's tokens are allowed for its chain.
10553
- * @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.
10554
10750
  * @returns The issue that was found. If the offer is valid, this will be undefined.
10555
10751
  */
10556
- const token = ({ assetsByChainId }) => single("token", "Validates that offer loan token and collateral tokens are in the allowed assets list for the offer chain", (offer) => {
10557
- const allowedAssets = assetsByChainId[offer.chainId]?.map((asset) => asset.toLowerCase());
10558
- if (!allowedAssets || allowedAssets.length === 0) return { message: `No allowed assets for chain ${offer.chainId}` };
10559
- if (!allowedAssets.includes(offer.loanToken.toLowerCase())) return { message: "Loan token is not allowed" };
10560
- 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" };
10561
10767
  });
10562
10768
  /**
10563
10769
  * A validation rule that checks if the offer's oracle addresses are allowed for its chain.
@@ -10600,9 +10806,11 @@ const amountMutualExclusivity = () => single("amount_mutual_exclusivity", "Valid
10600
10806
  //#region src/gatekeeper/morphoRules.ts
10601
10807
  const morphoRules = (chains$3) => {
10602
10808
  const assetsByChainId = {};
10809
+ const collateralAssetsByChainId = {};
10603
10810
  const oraclesByChainId = {};
10604
10811
  for (const chain of chains$3) {
10605
10812
  assetsByChainId[chain.id] = assets[chain.id.toString()] ?? [];
10813
+ collateralAssetsByChainId[chain.id] = collateralAssets[chain.id.toString()] ?? [];
10606
10814
  oraclesByChainId[chain.id] = oracles[chain.id.toString()] ?? [];
10607
10815
  }
10608
10816
  return [
@@ -10614,7 +10822,8 @@ const morphoRules = (chains$3) => {
10614
10822
  callbacks: [Type$1.BuyWithEmptyCallback, Type$1.SellWithEmptyCallback],
10615
10823
  allowedAddresses: []
10616
10824
  }),
10617
- token({ assetsByChainId }),
10825
+ loanToken({ assetsByChainId }),
10826
+ collateralToken({ collateralAssetsByChainId }),
10618
10827
  oracle({ oraclesByChainId })
10619
10828
  ];
10620
10829
  };
@@ -11026,6 +11235,12 @@ Object.defineProperty(exports, 'Rules', {
11026
11235
  return Rules_exports;
11027
11236
  }
11028
11237
  });
11238
+ Object.defineProperty(exports, 'Tick', {
11239
+ enumerable: true,
11240
+ get: function () {
11241
+ return Tick_exports;
11242
+ }
11243
+ });
11029
11244
  Object.defineProperty(exports, 'Time', {
11030
11245
  enumerable: true,
11031
11246
  get: function () {