@morpho-dev/router 0.7.2 → 0.9.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 (68) hide show
  1. package/dist/cli.js +834 -334
  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/0026_add-receiver-if-maker-is-seller.sql +1 -0
  29. package/dist/drizzle/migrations/meta/0000_snapshot.json +48 -48
  30. package/dist/drizzle/migrations/meta/0001_snapshot.json +48 -48
  31. package/dist/drizzle/migrations/meta/0002_snapshot.json +48 -48
  32. package/dist/drizzle/migrations/meta/0003_snapshot.json +48 -48
  33. package/dist/drizzle/migrations/meta/0004_snapshot.json +47 -47
  34. package/dist/drizzle/migrations/meta/0005_snapshot.json +47 -47
  35. package/dist/drizzle/migrations/meta/0006_snapshot.json +61 -61
  36. package/dist/drizzle/migrations/meta/0008_snapshot.json +62 -62
  37. package/dist/drizzle/migrations/meta/0009_snapshot.json +66 -66
  38. package/dist/drizzle/migrations/meta/0010_snapshot.json +66 -66
  39. package/dist/drizzle/migrations/meta/0013_snapshot.json +48 -48
  40. package/dist/drizzle/migrations/meta/0014_snapshot.json +48 -48
  41. package/dist/drizzle/migrations/meta/0015_snapshot.json +52 -52
  42. package/dist/drizzle/migrations/meta/0016_snapshot.json +61 -61
  43. package/dist/drizzle/migrations/meta/0017_snapshot.json +61 -61
  44. package/dist/drizzle/migrations/meta/0018_snapshot.json +62 -62
  45. package/dist/drizzle/migrations/meta/0019_snapshot.json +62 -62
  46. package/dist/drizzle/migrations/meta/0023_snapshot.json +62 -62
  47. package/dist/drizzle/migrations/meta/0024_snapshot.json +1448 -0
  48. package/dist/drizzle/migrations/meta/0025_snapshot.json +1448 -0
  49. package/dist/drizzle/migrations/meta/0026_snapshot.json +1454 -0
  50. package/dist/drizzle/migrations/meta/_journal.json +21 -0
  51. package/dist/evm/bytecode/morpho.txt +1 -1
  52. package/dist/index.browser.d.mts +206 -77
  53. package/dist/index.browser.d.mts.map +1 -1
  54. package/dist/index.browser.d.ts +206 -77
  55. package/dist/index.browser.d.ts.map +1 -1
  56. package/dist/index.browser.js +445 -197
  57. package/dist/index.browser.js.map +1 -1
  58. package/dist/index.browser.mjs +440 -198
  59. package/dist/index.browser.mjs.map +1 -1
  60. package/dist/index.node.d.mts +347 -119
  61. package/dist/index.node.d.mts.map +1 -1
  62. package/dist/index.node.d.ts +347 -119
  63. package/dist/index.node.d.ts.map +1 -1
  64. package/dist/index.node.js +865 -312
  65. package/dist/index.node.js.map +1 -1
  66. package/dist/index.node.mjs +861 -314
  67. package/dist/index.node.mjs.map +1 -1
  68. package/package.json +1 -1
@@ -608,7 +608,7 @@ var utils_exports = /* @__PURE__ */ __exportAll({
608
608
 
609
609
  //#endregion
610
610
  //#region src/indexer/collectors/Admin.ts
611
- function create$20(parameters) {
611
+ function create$21(parameters) {
612
612
  const collector = "admin";
613
613
  const { client, db, options: { maxBatchSize = 25, maxBlockNumber } = {} } = parameters;
614
614
  const maxBlockNumberBI = maxBlockNumber !== void 0 ? BigInt(maxBlockNumber) : void 0;
@@ -848,8 +848,8 @@ const names = [
848
848
  "positions",
849
849
  "prices"
850
850
  ];
851
- function create$19({ name, collect, client, db, options }) {
852
- const admin = create$20({
851
+ function create$20({ name, collect, client, db, options }) {
852
+ const admin = create$21({
853
853
  client,
854
854
  db,
855
855
  options
@@ -1015,18 +1015,18 @@ const MorphoV2 = (0, viem.parseAbi)([
1015
1015
  "function setFeeSetter(address newFeeSetter)",
1016
1016
  "function setObligationTradingFee(bytes32 id, uint256 index, uint256 newTradingFee)",
1017
1017
  "function setOwner(address newOwner)",
1018
- "function setTradingFeeRecipient(address recipient)",
1018
+ "function setTradingFeeRecipient(address feeRecipient)",
1019
1019
  "function sharesOf(bytes32 id, address user) view returns (uint256)",
1020
1020
  "function shuffleSession()",
1021
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)",
1022
+ "function take(uint256 buyerAssets, uint256 sellerAssets, uint256 obligationUnits, uint256 obligationShares, address taker, address takerCallback, bytes takerCallbackData, address receiverIfTakerIsSeller, ((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, address receiverIfMakerIsSeller) offer, (uint8 v, bytes32 r, bytes32 s) sig, bytes32 root, bytes32[] proof) returns (uint256, uint256, uint256, uint256)",
1023
1023
  "function totalShares(bytes32 id) view returns (uint256)",
1024
1024
  "function totalUnits(bytes32 id) view returns (uint256)",
1025
1025
  "function touchObligation((address loanToken, (address token, uint256 lltv, address oracle)[] collaterals, uint256 maturity) obligation) returns (bytes32)",
1026
1026
  "function tradingFee(bytes32 id, uint256 timeToMaturity) view returns (uint256)",
1027
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)",
1028
+ "function withdraw((address loanToken, (address token, uint256 lltv, address oracle)[] collaterals, uint256 maturity) obligation, uint256 obligationUnits, uint256 shares, address onBehalf, address receiver) returns (uint256, uint256)",
1029
+ "function withdrawCollateral((address loanToken, (address token, uint256 lltv, address oracle)[] collaterals, uint256 maturity) obligation, address collateral, uint256 assets, address onBehalf, address receiver)",
1030
1030
  "function withdrawable(bytes32 id) view returns (uint256)",
1031
1031
  "event Constructor(address indexed owner)",
1032
1032
  "event Consume(address indexed user, bytes32 indexed group, uint256 amount)",
@@ -1038,12 +1038,12 @@ const MorphoV2 = (0, viem.parseAbi)([
1038
1038
  "event SetFeeSetter(address indexed feeSetter)",
1039
1039
  "event SetObligationTradingFee(bytes32 indexed id, uint256 indexed index, uint256 newTradingFee)",
1040
1040
  "event SetOwner(address indexed owner)",
1041
- "event SetTradingFeeRecipient(address indexed recipient)",
1041
+ "event SetTradingFeeRecipient(address indexed feeRecipient)",
1042
1042
  "event ShuffleSession(address indexed user, bytes32 session)",
1043
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)"
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, address sellerReceiver, bytes32 group, uint256 consumed)",
1045
+ "event Withdraw(address caller, bytes32 indexed id, uint256 obligationUnits, uint256 shares, address indexed onBehalf, address indexed receiver)",
1046
+ "event WithdrawCollateral(address caller, bytes32 indexed id, address indexed collateral, uint256 assets, address indexed onBehalf, address receiver)"
1047
1047
  ]);
1048
1048
 
1049
1049
  //#endregion
@@ -1315,8 +1315,8 @@ const chains$2 = {
1315
1315
  name: "ethereum-virtual-testnet",
1316
1316
  custom: {
1317
1317
  morpho: {
1318
- address: "0x634b095371e4e45feed94c1a45c37798e173ea50",
1319
- blockCreated: 23226700
1318
+ address: "0xc9f3c65996fc46b9500608b2c9a9152c01c540f7",
1319
+ blockCreated: 23226871
1320
1320
  },
1321
1321
  morphoBlue: {
1322
1322
  address: "0xBBBBBbbBBb9cC5e90e3b3Af64bdAF62C37EEFFCb",
@@ -1462,13 +1462,13 @@ var MissingBlockNumberError = class extends BaseError {
1462
1462
 
1463
1463
  //#endregion
1464
1464
  //#region src/core/ChainRegistry.ts
1465
- var ChainRegistry_exports = /* @__PURE__ */ __exportAll({ create: () => create$18 });
1465
+ var ChainRegistry_exports = /* @__PURE__ */ __exportAll({ create: () => create$19 });
1466
1466
  /**
1467
1467
  * Creates a chain registry from a list of chains.
1468
1468
  * @param chains - Array of chain objects to register.
1469
1469
  * @returns A registry for looking up chains by ID. {@link ChainRegistry}
1470
1470
  */
1471
- function create$18(chains) {
1471
+ function create$19(chains) {
1472
1472
  const byId = /* @__PURE__ */ new Map();
1473
1473
  for (const chain of chains) byId.set(chain.id, chain);
1474
1474
  return {
@@ -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),
@@ -2143,7 +2143,8 @@ const OfferSchema = () => {
2143
2143
  callback: zod.object({
2144
2144
  address: zod.string().transform(transformAddress),
2145
2145
  data: zod.string().transform(transformHex)
2146
- })
2146
+ }),
2147
+ receiverIfMakerIsSeller: zod.string().transform(transformAddress)
2147
2148
  }).refine((data) => data.start < data.expiry, {
2148
2149
  message: "start must be before expiry",
2149
2150
  path: ["start"]
@@ -2159,8 +2160,12 @@ const OfferSchema = () => {
2159
2160
  * @returns The created offer.
2160
2161
  */
2161
2162
  function from$14(input) {
2163
+ const normalizedInput = {
2164
+ ...input,
2165
+ receiverIfMakerIsSeller: input.receiverIfMakerIsSeller ?? input.maker
2166
+ };
2162
2167
  try {
2163
- return OfferSchema().parse(input);
2168
+ return OfferSchema().parse(normalizedInput);
2164
2169
  } catch (error) {
2165
2170
  throw new InvalidOfferError(error);
2166
2171
  }
@@ -2194,7 +2199,7 @@ const serialize = (offer) => ({
2194
2199
  assets: offer.assets.toString(),
2195
2200
  obligationUnits: offer.obligationUnits.toString(),
2196
2201
  obligationShares: offer.obligationShares.toString(),
2197
- price: offer.price.toString(),
2202
+ tick: offer.tick,
2198
2203
  maturity: Number(offer.maturity),
2199
2204
  expiry: Number(offer.expiry),
2200
2205
  start: Number(offer.start),
@@ -2212,6 +2217,7 @@ const serialize = (offer) => ({
2212
2217
  address: offer.callback.address,
2213
2218
  data: offer.callback.data
2214
2219
  },
2220
+ receiverIfMakerIsSeller: offer.receiverIfMakerIsSeller,
2215
2221
  hash: hash(offer)
2216
2222
  });
2217
2223
  /**
@@ -2239,14 +2245,13 @@ function random$1(config) {
2239
2245
  [.98, 2]
2240
2246
  ]));
2241
2247
  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)];
2248
+ const tickMin = buy ? 0 : 495;
2249
+ const len = (buy ? 495 : 990) - tickMin + 1;
2250
+ const tickPairs = Array.from({ length: len }, (_, idx) => {
2251
+ const weight = buy ? 1 + idx : 1 + (len - 1 - idx);
2252
+ return [tickMin + idx, weight];
2248
2253
  });
2249
- const price = config?.price ?? weightedChoice(pricePairs);
2254
+ const tick = config?.tick ?? weightedChoice(tickPairs);
2250
2255
  const loanTokenDecimals = config?.assetsDecimals?.[loanToken] ?? 18;
2251
2256
  const unit = BigInt(10) ** BigInt(loanTokenDecimals);
2252
2257
  const amountBase = BigInt(100 + int(999901));
@@ -2255,12 +2260,13 @@ function random$1(config) {
2255
2260
  address: viem.zeroAddress,
2256
2261
  data: "0x"
2257
2262
  };
2263
+ const maker = config?.maker ?? address();
2258
2264
  return from$14({
2259
- maker: config?.maker ?? address(),
2265
+ maker,
2260
2266
  assets: assetsScaled,
2261
2267
  obligationUnits: config?.obligationUnits ?? 0n,
2262
2268
  obligationShares: config?.obligationShares ?? 0n,
2263
- price,
2269
+ tick,
2264
2270
  maturity,
2265
2271
  expiry: config?.expiry ?? maturity - 1,
2266
2272
  start: config?.start ?? maturity - 10,
@@ -2273,7 +2279,8 @@ function random$1(config) {
2273
2279
  ...random$3(),
2274
2280
  lltv
2275
2281
  })).sort((a, b) => a.asset.localeCompare(b.asset)),
2276
- callback: config?.callback ?? emptyCallback
2282
+ callback: config?.callback ?? emptyCallback,
2283
+ receiverIfMakerIsSeller: config?.receiverIfMakerIsSeller ?? maker
2277
2284
  });
2278
2285
  }
2279
2286
  const weightedChoice = (pairs) => {
@@ -2325,7 +2332,7 @@ const types = {
2325
2332
  type: "uint256"
2326
2333
  },
2327
2334
  {
2328
- name: "price",
2335
+ name: "tick",
2329
2336
  type: "uint256"
2330
2337
  },
2331
2338
  {
@@ -2359,6 +2366,10 @@ const types = {
2359
2366
  {
2360
2367
  name: "callback",
2361
2368
  type: "Callback"
2369
+ },
2370
+ {
2371
+ name: "receiverIfMakerIsSeller",
2372
+ type: "address"
2362
2373
  }
2363
2374
  ],
2364
2375
  Collateral: [
@@ -2393,7 +2404,7 @@ function hash(offer) {
2393
2404
  assets: offer.assets,
2394
2405
  obligationUnits: offer.obligationUnits,
2395
2406
  obligationShares: offer.obligationShares,
2396
- price: offer.price,
2407
+ tick: BigInt(offer.tick),
2397
2408
  maturity: BigInt(offer.maturity),
2398
2409
  expiry: BigInt(offer.expiry),
2399
2410
  group: offer.group,
@@ -2404,7 +2415,8 @@ function hash(offer) {
2404
2415
  callback: {
2405
2416
  address: offer.callback.address.toLowerCase(),
2406
2417
  data: offer.callback.data
2407
- }
2418
+ },
2419
+ receiverIfMakerIsSeller: offer.receiverIfMakerIsSeller.toLowerCase()
2408
2420
  },
2409
2421
  primaryType: "Offer",
2410
2422
  types
@@ -2444,7 +2456,7 @@ const OfferAbi = [
2444
2456
  type: "uint256"
2445
2457
  },
2446
2458
  {
2447
- name: "price",
2459
+ name: "tick",
2448
2460
  type: "uint256"
2449
2461
  },
2450
2462
  {
@@ -2507,6 +2519,10 @@ const OfferAbi = [
2507
2519
  name: "data",
2508
2520
  type: "bytes"
2509
2521
  }]
2522
+ },
2523
+ {
2524
+ name: "receiverIfMakerIsSeller",
2525
+ type: "address"
2510
2526
  }
2511
2527
  ];
2512
2528
  function encode$1(offer) {
@@ -2515,7 +2531,7 @@ function encode$1(offer) {
2515
2531
  offer.assets,
2516
2532
  offer.obligationUnits,
2517
2533
  offer.obligationShares,
2518
- offer.price,
2534
+ BigInt(offer.tick),
2519
2535
  BigInt(offer.maturity),
2520
2536
  BigInt(offer.expiry),
2521
2537
  offer.group,
@@ -2525,7 +2541,8 @@ function encode$1(offer) {
2525
2541
  offer.loanToken,
2526
2542
  BigInt(offer.start),
2527
2543
  offer.collaterals,
2528
- offer.callback
2544
+ offer.callback,
2545
+ offer.receiverIfMakerIsSeller
2529
2546
  ]);
2530
2547
  }
2531
2548
  function decode$1(data) {
@@ -2540,7 +2557,7 @@ function decode$1(data) {
2540
2557
  assets: decoded[1],
2541
2558
  obligationUnits: decoded[2],
2542
2559
  obligationShares: decoded[3],
2543
- price: decoded[4],
2560
+ tick: Number(decoded[4]),
2544
2561
  maturity: from$16(Number(decoded[5])),
2545
2562
  expiry: Number(decoded[6]),
2546
2563
  group: decoded[7],
@@ -2559,7 +2576,8 @@ function decode$1(data) {
2559
2576
  callback: {
2560
2577
  address: decoded[14].address,
2561
2578
  data: decoded[14].data
2562
- }
2579
+ },
2580
+ receiverIfMakerIsSeller: decoded[15]
2563
2581
  });
2564
2582
  }
2565
2583
  /**
@@ -2635,6 +2653,12 @@ const takeEvent = {
2635
2653
  indexed: false,
2636
2654
  internalType: "bool"
2637
2655
  },
2656
+ {
2657
+ name: "sellerReceiver",
2658
+ type: "address",
2659
+ indexed: false,
2660
+ internalType: "address"
2661
+ },
2638
2662
  {
2639
2663
  name: "group",
2640
2664
  type: "bytes32",
@@ -2820,20 +2844,99 @@ function from$12(parameters) {
2820
2844
  };
2821
2845
  }
2822
2846
 
2847
+ //#endregion
2848
+ //#region src/core/Tick.ts
2849
+ var Tick_exports = /* @__PURE__ */ __exportAll({
2850
+ InvalidPriceError: () => InvalidPriceError,
2851
+ InvalidTickError: () => InvalidTickError,
2852
+ MAX_PRICE: () => MAX_PRICE,
2853
+ TICK_RANGE: () => TICK_RANGE,
2854
+ priceToTick: () => priceToTick,
2855
+ tickToPrice: () => tickToPrice
2856
+ });
2857
+ /** ln(1 + 0.025), scaled by 1e18. Matches TickLib onchain constant. */
2858
+ const LN_ONE_PLUS_DELTA = 24692612590371501n;
2859
+ /** ln(2), scaled by 1e18. Matches TickLib onchain constant. */
2860
+ const LN2 = 693147180559945309n;
2861
+ const WAD$1 = 10n ** 18n;
2862
+ const WAD_SQUARED = 10n ** 36n;
2863
+ const PRICE_STEP = 10n ** 13n;
2864
+ const HALF_TICK_RANGE = 495n;
2865
+ /** Tick domain supported by Morpho V2. */
2866
+ const TICK_RANGE = 990;
2867
+ /** Max allowed price (1e18 in wad). */
2868
+ const MAX_PRICE = WAD$1;
2869
+ /**
2870
+ * Converts a tick to a wad price using the same approximation and rounding as TickLib.
2871
+ * @param tick - Tick value in the inclusive range [0, 990].
2872
+ * @returns The price in wad units.
2873
+ * @throws {@link InvalidTickError} If tick is not an integer in range [0, 990].
2874
+ */
2875
+ function tickToPrice(tick) {
2876
+ assertTick(tick);
2877
+ return divHalfDownUnchecked(divHalfDownUnchecked(WAD_SQUARED, WAD$1 + wExp(LN_ONE_PLUS_DELTA * (HALF_TICK_RANGE - BigInt(tick)))), PRICE_STEP) * PRICE_STEP;
2878
+ }
2879
+ /**
2880
+ * Returns the lowest tick with a higher-or-equal price.
2881
+ * @param price - Price in wad units.
2882
+ * @returns The first tick whose {@link tickToPrice} is greater than or equal to `price`.
2883
+ * @throws {@link InvalidPriceError} If price is outside [0, 1e18].
2884
+ */
2885
+ function priceToTick(price) {
2886
+ assertPrice(price);
2887
+ let low = 0;
2888
+ let high = TICK_RANGE;
2889
+ while (low !== high) {
2890
+ const mid = Math.floor((low + high) / 2);
2891
+ if (tickToPrice(mid) < price) low = mid + 1;
2892
+ else high = mid;
2893
+ }
2894
+ return low;
2895
+ }
2896
+ function divHalfDownUnchecked(x, d) {
2897
+ return (x + (d - 1n) / 2n) / d;
2898
+ }
2899
+ function wExp(x) {
2900
+ if (x < 0n) return WAD_SQUARED / wExp(-x);
2901
+ const q = (x + LN2 / 2n) / LN2;
2902
+ const r = x - q * LN2;
2903
+ const secondTerm = r * r / (2n * WAD$1);
2904
+ const thirdTerm = secondTerm * r / (3n * WAD$1);
2905
+ return WAD$1 + r + secondTerm + thirdTerm << q;
2906
+ }
2907
+ function assertTick(tick) {
2908
+ if (!Number.isInteger(tick) || tick < 0 || tick > TICK_RANGE) throw new InvalidTickError(tick);
2909
+ }
2910
+ function assertPrice(price) {
2911
+ if (price < 0n || price > MAX_PRICE) throw new InvalidPriceError(price);
2912
+ }
2913
+ var InvalidTickError = class extends BaseError {
2914
+ name = "Tick.InvalidTickError";
2915
+ constructor(tick) {
2916
+ super(`Invalid tick: ${tick}. Tick must be an integer between 0 and ${TICK_RANGE}.`);
2917
+ }
2918
+ };
2919
+ var InvalidPriceError = class extends BaseError {
2920
+ name = "Tick.InvalidPriceError";
2921
+ constructor(price) {
2922
+ super(`Invalid price: ${price}. Price must be between 0 and ${MAX_PRICE}.`);
2923
+ }
2924
+ };
2925
+
2823
2926
  //#endregion
2824
2927
  //#region src/core/Quote.ts
2825
2928
  var Quote_exports = /* @__PURE__ */ __exportAll({
2826
2929
  InvalidQuoteError: () => InvalidQuoteError,
2827
- QuoteSchema: () => QuoteSchema,
2828
2930
  from: () => from$11,
2829
2931
  fromSnakeCase: () => fromSnakeCase,
2830
2932
  random: () => random
2831
2933
  });
2832
- const QuoteSchema = zod.object({
2934
+ const SideInputSchema = zod.object({ tick: zod.number().int().min(0).max(TICK_RANGE).nullable() }).strict();
2935
+ const QuoteInputSchema = zod.object({
2833
2936
  obligationId: zod.string().transform(transformHex),
2834
- ask: zod.object({ price: zod.bigint({ coerce: true }).min(0n).max(viem.maxUint256) }),
2835
- bid: zod.object({ price: zod.bigint({ coerce: true }).min(0n).max(viem.maxUint256) })
2836
- });
2937
+ ask: SideInputSchema,
2938
+ bid: SideInputSchema
2939
+ }).strict();
2837
2940
  /**
2838
2941
  * Creates a quote for a given obligation.
2839
2942
  * @constructor
@@ -2843,16 +2946,16 @@ const QuoteSchema = zod.object({
2843
2946
  *
2844
2947
  * @example
2845
2948
  * ```ts
2846
- * const quote = Quote.from({ obligationId: "0x123", ask: { price: 100n }, bid: { price: 100n } });
2949
+ * const quote = Quote.from({ obligationId: "0x123", ask: { tick: 500 }, bid: { tick: 510 } });
2847
2950
  * ```
2848
2951
  */
2849
2952
  function from$11(parameters) {
2850
2953
  try {
2851
- const parsedQuote = QuoteSchema.parse(parameters);
2954
+ const parsedQuote = QuoteInputSchema.parse(parameters);
2852
2955
  return {
2853
2956
  obligationId: parsedQuote.obligationId,
2854
- ask: parsedQuote.ask,
2855
- bid: parsedQuote.bid
2957
+ ask: sideFromTick(parsedQuote.ask),
2958
+ bid: sideFromTick(parsedQuote.bid)
2856
2959
  };
2857
2960
  } catch (error) {
2858
2961
  throw new InvalidQuoteError(error);
@@ -2879,8 +2982,8 @@ function fromSnakeCase(snake) {
2879
2982
  function random() {
2880
2983
  return from$11({
2881
2984
  obligationId: id(random$2()),
2882
- ask: { price: BigInt(int(1e6)) },
2883
- bid: { price: BigInt(int(1e6)) }
2985
+ ask: { tick: int(TICK_RANGE + 1) },
2986
+ bid: { tick: int(TICK_RANGE + 1) }
2884
2987
  });
2885
2988
  }
2886
2989
  var InvalidQuoteError = class extends BaseError {
@@ -2889,6 +2992,12 @@ var InvalidQuoteError = class extends BaseError {
2889
2992
  super("Invalid quote.", { cause: error });
2890
2993
  }
2891
2994
  };
2995
+ function sideFromTick(side) {
2996
+ return {
2997
+ tick: side.tick,
2998
+ price: side.tick === null ? 0n : tickToPrice(side.tick)
2999
+ };
3000
+ }
2892
3001
 
2893
3002
  //#endregion
2894
3003
  //#region src/core/TradingFee.ts
@@ -3403,7 +3512,7 @@ const BrandTypeId = Symbol.for("mempool/Brand");
3403
3512
 
3404
3513
  //#endregion
3405
3514
  //#region src/database/drizzle/VERSION.ts
3406
- const VERSION = "router_v1.6";
3515
+ const VERSION = "router_v1.8";
3407
3516
 
3408
3517
  //#endregion
3409
3518
  //#region src/database/drizzle/schema.ts
@@ -3555,10 +3664,7 @@ const offers = s.table(EnumTableName.OFFERS, {
3555
3664
  precision: 78,
3556
3665
  scale: 0
3557
3666
  }).notNull().default("0"),
3558
- price: (0, drizzle_orm_pg_core.numeric)("price", {
3559
- precision: 78,
3560
- scale: 0
3561
- }).notNull(),
3667
+ tick: (0, drizzle_orm_pg_core.integer)("tick").notNull(),
3562
3668
  maturity: (0, drizzle_orm_pg_core.integer)("maturity").notNull(),
3563
3669
  expiry: (0, drizzle_orm_pg_core.integer)("expiry").notNull(),
3564
3670
  start: (0, drizzle_orm_pg_core.integer)("start").notNull(),
@@ -3569,6 +3675,7 @@ const offers = s.table(EnumTableName.OFFERS, {
3569
3675
  buy: (0, drizzle_orm_pg_core.boolean)("buy").notNull(),
3570
3676
  callbackAddress: (0, drizzle_orm_pg_core.varchar)("callback_address", { length: 42 }).notNull(),
3571
3677
  callbackData: (0, drizzle_orm_pg_core.text)("callback_data").notNull(),
3678
+ receiverIfMakerIsSeller: (0, drizzle_orm_pg_core.varchar)("receiver_if_maker_is_seller", { length: 42 }),
3572
3679
  blockNumber: (0, drizzle_orm_pg_core.bigint)("block_number", { mode: "number" }).notNull(),
3573
3680
  updatedAt: (0, drizzle_orm_pg_core.timestamp)("updated_at").defaultNow().notNull()
3574
3681
  }, (table) => [
@@ -3623,6 +3730,7 @@ const lots = s.table(EnumTableName.LOTS, {
3623
3730
  user: (0, drizzle_orm_pg_core.varchar)("user", { length: 42 }).notNull(),
3624
3731
  contract: (0, drizzle_orm_pg_core.varchar)("contract", { length: 42 }).notNull(),
3625
3732
  group: (0, drizzle_orm_pg_core.varchar)("group", { length: 66 }).notNull(),
3733
+ obligationId: (0, drizzle_orm_pg_core.varchar)("obligation_id", { length: 66 }).notNull(),
3626
3734
  lower: (0, drizzle_orm_pg_core.numeric)("lower", {
3627
3735
  precision: 78,
3628
3736
  scale: 0
@@ -3637,7 +3745,8 @@ const lots = s.table(EnumTableName.LOTS, {
3637
3745
  table.chainId,
3638
3746
  table.user,
3639
3747
  table.contract,
3640
- table.group
3748
+ table.group,
3749
+ table.obligationId
3641
3750
  ],
3642
3751
  name: "lots_pk"
3643
3752
  }),
@@ -3673,6 +3782,7 @@ const offsets = s.table(EnumTableName.OFFSETS, {
3673
3782
  user: (0, drizzle_orm_pg_core.varchar)("user", { length: 42 }).notNull(),
3674
3783
  contract: (0, drizzle_orm_pg_core.varchar)("contract", { length: 42 }).notNull(),
3675
3784
  group: (0, drizzle_orm_pg_core.varchar)("group", { length: 66 }).notNull(),
3785
+ obligationId: (0, drizzle_orm_pg_core.varchar)("obligation_id", { length: 66 }).notNull(),
3676
3786
  value: (0, drizzle_orm_pg_core.numeric)("value", {
3677
3787
  precision: 78,
3678
3788
  scale: 0
@@ -3682,7 +3792,8 @@ const offsets = s.table(EnumTableName.OFFSETS, {
3682
3792
  table.chainId,
3683
3793
  table.user,
3684
3794
  table.contract,
3685
- table.group
3795
+ table.group,
3796
+ table.obligationId
3686
3797
  ],
3687
3798
  name: "offsets_pk"
3688
3799
  }), (0, drizzle_orm_pg_core.foreignKey)({
@@ -4273,6 +4384,7 @@ function decodeCallbacks(parameters) {
4273
4384
  positionContract: loanToken,
4274
4385
  positionUser: offer.maker,
4275
4386
  group: offer.group,
4387
+ obligationId: obligationId(offer),
4276
4388
  size: offer.assets
4277
4389
  });
4278
4390
  callbacks.push({
@@ -4945,7 +5057,7 @@ async function* collectPrices(parameters) {
4945
5057
  //#region src/indexer/collectors/CollectorBuilder.ts
4946
5058
  function createBuilder(parameters) {
4947
5059
  const { client, db, gatekeeper, options: { maxBlockNumber, blockWindow, interval } = {} } = parameters;
4948
- const createCollector = (name, collect) => create$19({
5060
+ const createCollector = (name, collect) => create$20({
4949
5061
  name,
4950
5062
  collect,
4951
5063
  client,
@@ -5037,7 +5149,7 @@ const from$7 = (parameters) => {
5037
5149
  //#endregion
5038
5150
  //#region src/indexer/Indexer.ts
5039
5151
  var Indexer_exports = /* @__PURE__ */ __exportAll({
5040
- create: () => create$17,
5152
+ create: () => create$18,
5041
5153
  from: () => from$6
5042
5154
  });
5043
5155
  function from$6(config) {
@@ -5053,7 +5165,7 @@ function from$6(config) {
5053
5165
  retryAttempts,
5054
5166
  retryDelayMs
5055
5167
  });
5056
- return create$17({
5168
+ return create$18({
5057
5169
  client,
5058
5170
  collectors: [
5059
5171
  offersCollector,
@@ -5063,7 +5175,7 @@ function from$6(config) {
5063
5175
  ]
5064
5176
  });
5065
5177
  }
5066
- function create$17(params) {
5178
+ function create$18(params) {
5067
5179
  const { collectors, client } = params;
5068
5180
  const indexerId = `${client.chain.id.toString()}.indexer`;
5069
5181
  const tracer = getTracer(`router.${indexerId}`);
@@ -5092,12 +5204,12 @@ function create$17(params) {
5092
5204
 
5093
5205
  //#endregion
5094
5206
  //#region src/api/Health.ts
5095
- var Health_exports = /* @__PURE__ */ __exportAll({ create: () => create$16 });
5207
+ var Health_exports = /* @__PURE__ */ __exportAll({ create: () => create$17 });
5096
5208
  const DEFAULT_MAX_ALLOWED_LAG = 5;
5097
5209
  /**
5098
5210
  * Create a health service that exposes collector and chain block numbers.
5099
5211
  */
5100
- function create$16(parameters) {
5212
+ function create$17(parameters) {
5101
5213
  const { db, maxAllowedLag = DEFAULT_MAX_ALLOWED_LAG, healthClients, chainRegistry } = parameters;
5102
5214
  const loadSnapshot = async () => {
5103
5215
  const [collectorRows, chainRows, remoteBlockByChainId] = await Promise.all([
@@ -5211,8 +5323,10 @@ async function getRemoteBlockNumbers(healthClients) {
5211
5323
  //#region src/api/Schema/BookResponse.ts
5212
5324
  var BookResponse_exports = /* @__PURE__ */ __exportAll({ from: () => from$5 });
5213
5325
  function from$5(level) {
5326
+ const price = tickToPrice(level.tick);
5214
5327
  return {
5215
- price: level.price.toString(),
5328
+ tick: level.tick,
5329
+ price: price.toString(),
5216
5330
  assets: level.assets.toString(),
5217
5331
  count: level.count
5218
5332
  };
@@ -5259,6 +5373,7 @@ var ObligationResponse_exports = /* @__PURE__ */ __exportAll({ from: () => from$
5259
5373
  * Creates an `ObligationResponse` from a `Obligation`.
5260
5374
  * @constructor
5261
5375
  * @param obligation - {@link Obligation}
5376
+ * @param quote - {@link Quote}
5262
5377
  * @returns The created `ObligationResponse`. {@link ObligationResponse}
5263
5378
  */
5264
5379
  function from$4(obligation, quote) {
@@ -5272,8 +5387,14 @@ function from$4(obligation, quote) {
5272
5387
  oracle: c.oracle
5273
5388
  })),
5274
5389
  maturity: obligation.maturity,
5275
- ask: { price: quote.ask.price.toString() },
5276
- bid: { price: quote.bid.price.toString() }
5390
+ ask: {
5391
+ tick: quote.ask.tick,
5392
+ price: quote.ask.price.toString()
5393
+ },
5394
+ bid: {
5395
+ tick: quote.bid.tick,
5396
+ price: quote.bid.price.toString()
5397
+ }
5277
5398
  };
5278
5399
  }
5279
5400
 
@@ -5317,11 +5438,12 @@ function from$3(input) {
5317
5438
  obligation_shares: input.obligationShares.toString(),
5318
5439
  start: input.start,
5319
5440
  expiry: input.expiry,
5320
- price: input.price.toString(),
5441
+ tick: input.tick,
5321
5442
  group: input.group,
5322
5443
  session: input.session,
5323
5444
  callback: input.callback.address,
5324
- callback_data: input.callback.data
5445
+ callback_data: input.callback.data,
5446
+ receiver_if_maker_is_seller: input.receiverIfMakerIsSeller
5325
5447
  },
5326
5448
  offer_hash: input.hash,
5327
5449
  obligation_id: id({
@@ -5388,7 +5510,7 @@ var InternalServerError = class extends APIError {
5388
5510
  super(STATUS_CODE.INTERNAL_SERVER_ERROR, message, "INTERNAL_SERVER_ERROR");
5389
5511
  }
5390
5512
  };
5391
- var BadRequestError = class extends APIError {
5513
+ var BadRequestError$1 = class extends APIError {
5392
5514
  constructor(message = "Invalid JSON format", details) {
5393
5515
  super(STATUS_CODE.BAD_REQUEST, message, "BAD_REQUEST", details);
5394
5516
  }
@@ -5410,7 +5532,7 @@ function success(args) {
5410
5532
  */
5411
5533
  function failure(err) {
5412
5534
  if (err instanceof APIError) return handleAPIError(err);
5413
- if (err instanceof SyntaxError) return handleAPIError(new BadRequestError(err.message));
5535
+ if (err instanceof SyntaxError) return handleAPIError(new BadRequestError$1(err.message));
5414
5536
  if (err instanceof zod.ZodError) return handleAPIError(handleZodError(err));
5415
5537
  return handleAPIError(new InternalServerError());
5416
5538
  }
@@ -5460,7 +5582,7 @@ function __decorate(decorators, target, key, desc) {
5460
5582
  //#region src/api/Schema/openapi.ts
5461
5583
  const timestampExample = "2024-01-01T12:00:00.000Z";
5462
5584
  const offerCursorExample = "eyJvZmZzZXQiOjEwMH0";
5463
- const obligationCursorExample = "0x25690ae1aee324a005be565f3bcdd16dbf8daf7969b26c181c8b8f467dad9abc";
5585
+ const obligationCursorExample = "eyJzb3J0IjpbImlkIl0sImlkIjoiMHgxMjM0NTY3ODkwMTIzNDU2Nzg5MDEyMzQ1Njc4OTAxMjM0NTY3ODkwMTIzNDU2Nzg5MDEyMzQ1Njc4OTAiLCJhc2siOiIwIiwiYmlkIjoiMCIsIm1hdHVyaXR5IjoxNzYxOTIyODAwfQ";
5464
5586
  const offerExample = {
5465
5587
  offer: {
5466
5588
  obligation: {
@@ -5479,11 +5601,12 @@ const offerExample = {
5479
5601
  obligation_shares: "0",
5480
5602
  start: 1761922790,
5481
5603
  expiry: 1761922799,
5482
- price: "2750000000000000000",
5604
+ tick: 495,
5483
5605
  group: "0x000000000000000000000000000000000000000000000000000000000008b8f4",
5484
5606
  session: "0x0000000000000000000000000000000000000000000000000000000000000000",
5485
5607
  callback: "0x0000000000000000000000000000000000000000",
5486
- callback_data: "0x"
5608
+ callback_data: "0x",
5609
+ receiver_if_maker_is_seller: "0x7b093658BE7f90B63D7c359e8f408e503c2D9401"
5487
5610
  },
5488
5611
  offer_hash: "0xac4bd8318ec914f89f8af913f162230575b0ac0696a19256bc12138c5cfe1427",
5489
5612
  obligation_id: "0x25690ae1aee324a005be565f3bcdd16dbf8daf7969b26c181c8b8f467dad9abc",
@@ -5520,7 +5643,7 @@ const validateOfferExample = {
5520
5643
  assets: "369216000000000000000000",
5521
5644
  obligation_units: "0",
5522
5645
  obligation_shares: "0",
5523
- price: "2750000000000000000",
5646
+ tick: 495,
5524
5647
  maturity: 1761922799,
5525
5648
  expiry: 1761922799,
5526
5649
  start: 1761922790,
@@ -5537,7 +5660,8 @@ const validateOfferExample = {
5537
5660
  callback: {
5538
5661
  address: "0x0000000000000000000000000000000000000000",
5539
5662
  data: "0x"
5540
- }
5663
+ },
5664
+ receiver_if_maker_is_seller: "0x7b093658BE7f90B63D7c359e8f408e503c2D9401"
5541
5665
  };
5542
5666
  const routerStatusExample = {
5543
5667
  status: "live",
@@ -5608,11 +5732,23 @@ __decorate([(0, openapi_metadata_decorators.ApiProperty)({
5608
5732
  example: validateOfferExample.callback.data
5609
5733
  })], ValidateCallbackRequest.prototype, "data", void 0);
5610
5734
  var AskResponse = class {};
5735
+ __decorate([(0, openapi_metadata_decorators.ApiProperty)({
5736
+ type: "number",
5737
+ nullable: true,
5738
+ example: 500,
5739
+ description: "Best ask tick. Null when there is no active ask quote."
5740
+ })], AskResponse.prototype, "tick", void 0);
5611
5741
  __decorate([(0, openapi_metadata_decorators.ApiProperty)({
5612
5742
  type: "string",
5613
5743
  example: "1000000000000000000"
5614
5744
  })], AskResponse.prototype, "price", void 0);
5615
5745
  var BidResponse = class {};
5746
+ __decorate([(0, openapi_metadata_decorators.ApiProperty)({
5747
+ type: "number",
5748
+ nullable: true,
5749
+ example: 500,
5750
+ description: "Best bid tick. Null when there is no active bid quote."
5751
+ })], BidResponse.prototype, "tick", void 0);
5616
5752
  __decorate([(0, openapi_metadata_decorators.ApiProperty)({
5617
5753
  type: "string",
5618
5754
  example: "1000000000000000000"
@@ -5664,9 +5800,11 @@ __decorate([(0, openapi_metadata_decorators.ApiProperty)({
5664
5800
  example: offerExample.offer.expiry
5665
5801
  })], OfferDataResponse.prototype, "expiry", void 0);
5666
5802
  __decorate([(0, openapi_metadata_decorators.ApiProperty)({
5667
- type: "string",
5668
- example: offerExample.offer.price
5669
- })], OfferDataResponse.prototype, "price", void 0);
5803
+ type: "number",
5804
+ example: offerExample.offer.tick,
5805
+ minimum: 0,
5806
+ maximum: 990
5807
+ })], OfferDataResponse.prototype, "tick", void 0);
5670
5808
  __decorate([(0, openapi_metadata_decorators.ApiProperty)({
5671
5809
  type: "string",
5672
5810
  example: offerExample.offer.group
@@ -5683,6 +5821,10 @@ __decorate([(0, openapi_metadata_decorators.ApiProperty)({
5683
5821
  type: "string",
5684
5822
  example: offerExample.offer.callback_data
5685
5823
  })], OfferDataResponse.prototype, "callback_data", void 0);
5824
+ __decorate([(0, openapi_metadata_decorators.ApiProperty)({
5825
+ type: "string",
5826
+ example: offerExample.offer.receiver_if_maker_is_seller
5827
+ })], OfferDataResponse.prototype, "receiver_if_maker_is_seller", void 0);
5686
5828
  var OfferListItemResponse = class {};
5687
5829
  __decorate([(0, openapi_metadata_decorators.ApiProperty)({
5688
5830
  type: () => OfferDataResponse,
@@ -5907,9 +6049,11 @@ __decorate([(0, openapi_metadata_decorators.ApiProperty)({
5907
6049
  required: false
5908
6050
  })], ValidateOfferRequest.prototype, "obligation_shares", void 0);
5909
6051
  __decorate([(0, openapi_metadata_decorators.ApiProperty)({
5910
- type: "string",
5911
- example: validateOfferExample.price
5912
- })], ValidateOfferRequest.prototype, "price", void 0);
6052
+ type: "number",
6053
+ example: validateOfferExample.tick,
6054
+ minimum: 0,
6055
+ maximum: 990
6056
+ })], ValidateOfferRequest.prototype, "tick", void 0);
5913
6057
  __decorate([(0, openapi_metadata_decorators.ApiProperty)({
5914
6058
  type: "number",
5915
6059
  example: validateOfferExample.maturity
@@ -5950,6 +6094,10 @@ __decorate([(0, openapi_metadata_decorators.ApiProperty)({
5950
6094
  type: () => ValidateCallbackRequest,
5951
6095
  example: validateOfferExample.callback
5952
6096
  })], ValidateOfferRequest.prototype, "callback", void 0);
6097
+ __decorate([(0, openapi_metadata_decorators.ApiProperty)({
6098
+ type: "string",
6099
+ example: validateOfferExample.receiver_if_maker_is_seller
6100
+ })], ValidateOfferRequest.prototype, "receiver_if_maker_is_seller", void 0);
5953
6101
  var ValidateOffersRequest = class {};
5954
6102
  __decorate([(0, openapi_metadata_decorators.ApiProperty)({
5955
6103
  type: () => [ValidateOfferRequest],
@@ -6009,9 +6157,16 @@ __decorate([(0, openapi_metadata_decorators.ApiProperty)({
6009
6157
  description: "List of validation issues. Returned when any offer fails validation."
6010
6158
  })], ValidationFailureResponse.prototype, "data", void 0);
6011
6159
  var BookLevelResponse = class {};
6160
+ __decorate([(0, openapi_metadata_decorators.ApiProperty)({
6161
+ type: "number",
6162
+ example: 495,
6163
+ minimum: 0,
6164
+ maximum: 990
6165
+ })], BookLevelResponse.prototype, "tick", void 0);
6012
6166
  __decorate([(0, openapi_metadata_decorators.ApiProperty)({
6013
6167
  type: "string",
6014
- example: "2750000000000000000"
6168
+ example: "500000000000000000",
6169
+ description: "Price derived from tick, scaled by 1e18."
6015
6170
  })], BookLevelResponse.prototype, "price", void 0);
6016
6171
  __decorate([(0, openapi_metadata_decorators.ApiProperty)({
6017
6172
  type: "string",
@@ -6025,6 +6180,7 @@ const positionExample = {
6025
6180
  chain_id: 1,
6026
6181
  contract: "0xC9A9C45C0eB717f8b5F193Af6bAa05A1c0Ac5078",
6027
6182
  user: "0x7b093658BE7f90B63D7c359e8f408e503c2D9401",
6183
+ obligation_id: "0x12590ae1aee324a005be565f3bcdd16dbf8daf7969b26c181c8b8f467dad9f67",
6028
6184
  reserved: "200000000000000000000",
6029
6185
  block_number: 21345678
6030
6186
  };
@@ -6041,6 +6197,12 @@ __decorate([(0, openapi_metadata_decorators.ApiProperty)({
6041
6197
  type: "string",
6042
6198
  example: positionExample.user
6043
6199
  })], PositionListItemResponse.prototype, "user", void 0);
6200
+ __decorate([(0, openapi_metadata_decorators.ApiProperty)({
6201
+ type: "string",
6202
+ nullable: true,
6203
+ example: positionExample.obligation_id,
6204
+ description: "Obligation id this reserved amount belongs to, or null if no lots exist."
6205
+ })], PositionListItemResponse.prototype, "obligation_id", void 0);
6044
6206
  __decorate([(0, openapi_metadata_decorators.ApiProperty)({
6045
6207
  type: "string",
6046
6208
  example: positionExample.reserved
@@ -6068,7 +6230,7 @@ __decorate([(0, openapi_metadata_decorators.ApiProperty)({
6068
6230
  })], BookListResponse.prototype, "cursor", void 0);
6069
6231
  __decorate([(0, openapi_metadata_decorators.ApiProperty)({
6070
6232
  type: () => [BookLevelResponse],
6071
- description: "Aggregated book levels grouped by computed price."
6233
+ description: "Aggregated book levels grouped by offer tick."
6072
6234
  })], BookListResponse.prototype, "data", void 0);
6073
6235
  let BooksController = class BooksController {
6074
6236
  async getBook() {}
@@ -6078,7 +6240,7 @@ __decorate([
6078
6240
  methods: ["get"],
6079
6241
  path: "/v1/books/{obligationId}/{side}",
6080
6242
  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)."
6243
+ 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
6244
  }),
6083
6245
  (0, openapi_metadata_decorators.ApiParam)({
6084
6246
  name: "obligationId",
@@ -6103,7 +6265,7 @@ __decorate([
6103
6265
  name: "limit",
6104
6266
  type: "number",
6105
6267
  example: 10,
6106
- description: "Maximum number of price levels to return."
6268
+ description: "Maximum number of tick levels to return."
6107
6269
  }),
6108
6270
  (0, openapi_metadata_decorators.ApiResponse)({
6109
6271
  status: 200,
@@ -6297,6 +6459,11 @@ const configRulesLoanTokenExample = {
6297
6459
  chain_id: 1,
6298
6460
  address: "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48"
6299
6461
  };
6462
+ const configRulesCollateralTokenExample = {
6463
+ type: "collateral_token",
6464
+ chain_id: 1,
6465
+ address: "0x7f39C581F595B53c5cb19bD0b3f8dA6c935E2Ca0"
6466
+ };
6300
6467
  const configRulesOracleExample = {
6301
6468
  type: "oracle",
6302
6469
  chain_id: 1,
@@ -6306,6 +6473,7 @@ const configRulesChecksumExample = "f1d2d2f924e986ac86fdf7b36c94bcdf";
6306
6473
  const configRulesPayloadExample = [
6307
6474
  configRulesMaturityExample,
6308
6475
  configRulesLoanTokenExample,
6476
+ configRulesCollateralTokenExample,
6309
6477
  configRulesOracleExample
6310
6478
  ];
6311
6479
  const configContractNames = [
@@ -6432,7 +6600,7 @@ __decorate([
6432
6600
  methods: ["get"],
6433
6601
  path: "/v1/config/rules",
6434
6602
  summary: "Get config rules",
6435
- description: "Returns configured rules (maturities, loan tokens, oracles) for supported chains."
6603
+ description: "Returns configured rules (maturities, loan tokens, collateral tokens, oracles) for supported chains."
6436
6604
  }),
6437
6605
  (0, openapi_metadata_decorators.ApiQuery)({
6438
6606
  name: "cursor",
@@ -6452,7 +6620,7 @@ __decorate([
6452
6620
  name: "types",
6453
6621
  type: ["string"],
6454
6622
  required: false,
6455
- example: "maturity,loan_token,oracle",
6623
+ example: "maturity,loan_token,collateral_token,oracle",
6456
6624
  description: "Filter by rule types (comma-separated).",
6457
6625
  style: "form",
6458
6626
  explode: false
@@ -6482,13 +6650,13 @@ __decorate([
6482
6650
  methods: ["get"],
6483
6651
  path: "/v1/obligations",
6484
6652
  summary: "List all obligations",
6485
- description: "Returns a list of obligations with their current best ask and bid. Obligations are sorted by their id in ascending order by default."
6653
+ description: "Returns a list of obligations with their current best ask and bid. Sorting is customizable with the sort parameter and defaults to id ascending."
6486
6654
  }),
6487
6655
  (0, openapi_metadata_decorators.ApiQuery)({
6488
6656
  name: "cursor",
6489
6657
  type: "string",
6490
6658
  example: obligationCursorExample,
6491
- description: "Obligation id cursor for pagination."
6659
+ description: "Pagination cursor in base64url-encoded format."
6492
6660
  }),
6493
6661
  (0, openapi_metadata_decorators.ApiQuery)({
6494
6662
  name: "limit",
@@ -6532,6 +6700,15 @@ __decorate([
6532
6700
  style: "form",
6533
6701
  explode: false
6534
6702
  }),
6703
+ (0, openapi_metadata_decorators.ApiQuery)({
6704
+ name: "sort",
6705
+ type: "string",
6706
+ required: false,
6707
+ example: "-ask,bid,maturity",
6708
+ description: "Sort order as comma-separated fields (`id`, `ask`, `bid`, `maturity`). Prefix with `-` for descending order. Max 3 fields.",
6709
+ style: "form",
6710
+ explode: false
6711
+ }),
6535
6712
  (0, openapi_metadata_decorators.ApiResponse)({
6536
6713
  status: 200,
6537
6714
  description: "Success",
@@ -6570,7 +6747,7 @@ __decorate([
6570
6747
  methods: ["get"],
6571
6748
  path: "/v1/users/{userAddress}/positions",
6572
6749
  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)."
6750
+ 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
6751
  }),
6575
6752
  (0, openapi_metadata_decorators.ApiParam)({
6576
6753
  name: "userAddress",
@@ -6658,6 +6835,7 @@ function from$2(position) {
6658
6835
  chain_id: position.chainId,
6659
6836
  contract: position.contract,
6660
6837
  user: position.user,
6838
+ obligation_id: position.obligationId,
6661
6839
  reserved: position.reserved.toString(),
6662
6840
  block_number: position.blockNumber
6663
6841
  };
@@ -6666,11 +6844,13 @@ function from$2(position) {
6666
6844
  //#endregion
6667
6845
  //#region src/api/Schema/requests.ts
6668
6846
  const MAX_LIMIT = 100;
6669
- const DEFAULT_LIMIT$4 = 20;
6847
+ const DEFAULT_LIMIT$5 = 20;
6848
+ const MAX_OBLIGATION_SORT_FIELDS = 3;
6670
6849
  const CONFIG_RULES_MAX_LIMIT = 1e3;
6671
6850
  const CONFIG_RULES_DEFAULT_LIMIT = 100;
6672
6851
  const CONFIG_CONTRACTS_MAX_LIMIT = 1e3;
6673
6852
  const CONFIG_CONTRACTS_DEFAULT_LIMIT = 1e3;
6853
+ const OBLIGATION_SORT_ENTRY_REGEX = /^-?(id|ask|bid|maturity)$/;
6674
6854
  /** Validate cursor is a valid base64url-encoded JSON object.
6675
6855
  * Domain layer handles semantic validation of cursor fields. */
6676
6856
  function isValidBase64urlJson(val) {
@@ -6702,8 +6882,8 @@ const PaginationQueryParams = zod.object({
6702
6882
  description: "Pagination cursor in base64url-encoded format",
6703
6883
  example: "eyJzaWRlIjoic2VsbCIsImN1cnJlbnRQcmljZSI6IjEwMDAwMDAwMDAwMDAwMDAwMDAiLCJibG9ja051bWJlciI6MSwiYXNzZXRzIjoiMTAwMDAwMDAwMDAwMDAwMDAwMCIsImhhc2giOiIweGRmZDY4NTllM2UwODJkMTkzODlhMWFlYzFiZGFkN2U4ZDkyZDk2YjFhYTc5NDBkYTkxYTMxMjVkMzFlM2JlNWIiLCJ0b3RhbFJldHVybmVkIjoxMCwibm93IjoxNjAwMDAwMDAwfQ"
6704
6884
  }),
6705
- limit: zod.string().regex(/^[1-9]\d*$/, { message: "Limit must be a positive integer" }).transform((val) => Number.parseInt(val, 10)).pipe(zod.number().max(MAX_LIMIT, { message: `Limit cannot exceed ${MAX_LIMIT}` })).optional().default(DEFAULT_LIMIT$4).meta({
6706
- description: `Limit maximum: ${MAX_LIMIT}. Default: ${DEFAULT_LIMIT$4}`,
6885
+ limit: zod.string().regex(/^[1-9]\d*$/, { message: "Limit must be a positive integer" }).transform((val) => Number.parseInt(val, 10)).pipe(zod.number().max(MAX_LIMIT, { message: `Limit cannot exceed ${MAX_LIMIT}` })).optional().default(DEFAULT_LIMIT$5).meta({
6886
+ description: `Limit maximum: ${MAX_LIMIT}. Default: ${DEFAULT_LIMIT$5}`,
6707
6887
  example: 10
6708
6888
  })
6709
6889
  });
@@ -6711,10 +6891,11 @@ const ConfigRuleTypes = zod.enum([
6711
6891
  "maturity",
6712
6892
  "callback",
6713
6893
  "loan_token",
6894
+ "collateral_token",
6714
6895
  "oracle"
6715
6896
  ]);
6716
6897
  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({
6898
+ 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
6899
  description: "Pagination cursor in type:chain_id:<value> format",
6719
6900
  example: "maturity:1:1730415600:end_of_next_month"
6720
6901
  }),
@@ -6724,7 +6905,7 @@ const GetConfigRulesQueryParams = zod.object({
6724
6905
  }),
6725
6906
  types: csvArray(ConfigRuleTypes).meta({
6726
6907
  description: "Filter by rule types (comma-separated).",
6727
- example: "maturity,loan_token,oracle"
6908
+ example: "maturity,loan_token,collateral_token,oracle"
6728
6909
  }),
6729
6910
  chains: csvArray(zod.string().regex(/^[1-9]\d*$/, { message: "Chain must be a positive integer" }).transform((val) => Number.parseInt(val, 10))).meta({
6730
6911
  description: "Filter by chain IDs (comma-separated).",
@@ -6799,9 +6980,12 @@ const GetOffersQueryParams = PaginationQueryParams.omit({ cursor: true }).extend
6799
6980
  });
6800
6981
  const GetObligationsQueryParams = zod.object({
6801
6982
  ...PaginationQueryParams.shape,
6802
- cursor: zod.string().optional().meta({
6803
- description: "Obligation id cursor",
6804
- example: "0x1234567890123456789012345678901234567890123456789012345678901234"
6983
+ cursor: zod.string().optional().refine((val) => {
6984
+ if (!val) return true;
6985
+ return isValidBase64urlJson(val);
6986
+ }, { message: "Invalid cursor format. Must be a valid base64url-encoded cursor object" }).meta({
6987
+ description: "Pagination cursor in base64url-encoded format.",
6988
+ example: "eyJzb3J0IjpbImlkIl0sImlkIjoiMHgxMjM0NTY3ODkwMTIzNDU2Nzg5MDEyMzQ1Njc4OTAxMjM0NTY3ODkwMTIzNDU2Nzg5MDEyMzQ1Njc4OTAiLCJhc2siOiIwIiwiYmlkIjoiMCIsIm1hdHVyaXR5IjoxNzYxOTIyODAwfQ"
6805
6989
  }),
6806
6990
  chains: csvArray(zod.string().regex(/^[1-9]\d*$/, { message: "Chain must be a positive integer" }).transform((val) => Number.parseInt(val, 10))).meta({
6807
6991
  description: "Filter by chain IDs (comma-separated).",
@@ -6818,18 +7002,35 @@ const GetObligationsQueryParams = zod.object({
6818
7002
  maturities: csvArray(zod.string().regex(/^[1-9]\d*$/, { message: "Maturity must be a positive integer" }).transform((val) => Number.parseInt(val, 10))).meta({
6819
7003
  description: "Filter by exact maturity timestamps (comma-separated, unix seconds).",
6820
7004
  example: "1761922800,1764524800"
7005
+ }),
7006
+ sort: csvArray(zod.string().regex(OBLIGATION_SORT_ENTRY_REGEX, { message: "Sort entries must be one of: id, ask, bid, maturity (optionally prefixed with '-')" })).refine((entries) => entries === void 0 || entries.length <= MAX_OBLIGATION_SORT_FIELDS, { message: `Sort cannot include more than ${MAX_OBLIGATION_SORT_FIELDS} fields` }).superRefine((entries, ctx) => {
7007
+ if (!entries) return;
7008
+ const seen = /* @__PURE__ */ new Set();
7009
+ for (const entry of entries) {
7010
+ const field = entry.startsWith("-") ? entry.slice(1) : entry;
7011
+ if (seen.has(field)) {
7012
+ ctx.addIssue({
7013
+ code: "custom",
7014
+ message: `Duplicate sort field: ${field}`
7015
+ });
7016
+ return;
7017
+ }
7018
+ seen.add(field);
7019
+ }
7020
+ }).meta({
7021
+ description: "Sort order as comma-separated fields. Prefix a field with '-' for descending order. Max 3 fields.",
7022
+ example: "-ask,bid,maturity"
6821
7023
  })
6822
7024
  });
6823
7025
  const GetObligationParams = zod.object({ obligation_id: zod.string({ error: "Obligation id is required and must be a valid 32-byte hex string" }).regex(/^0x[a-fA-F0-9]{64}$/, { error: "Obligation id must be a valid 32-byte hex string" }).transform((val) => val.toLowerCase()).meta({
6824
7026
  description: "Obligation id",
6825
7027
  example: "0x1234567890123456789012345678901234567890123456789012345678901234"
6826
7028
  }) });
6827
- /** Validate a book cursor format: {side, lastPrice, offersCursor} */
7029
+ /** Validate a book cursor format: {side, lastTick, offersCursor} */
6828
7030
  function isValidBookCursor(cursorString) {
6829
- const isNumericString = (value) => typeof value === "string" && /^-?\d+$/.test(value);
6830
7031
  try {
6831
7032
  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");
7033
+ return (v?.side === "buy" || v?.side === "sell") && typeof v?.lastTick === "number" && Number.isInteger(v.lastTick) && (v?.offersCursor === null || typeof v?.offersCursor === "string");
6833
7034
  } catch {
6834
7035
  return false;
6835
7036
  }
@@ -6842,8 +7043,8 @@ const BookPaginationQueryParams = zod.object({
6842
7043
  description: "Pagination cursor in base64url-encoded format for book levels",
6843
7044
  example: "eyJzaWRlIjoiYnV5IiwibGFzdFJhdGUiOiIxMDAwMDAwMDAwMDAwMDAwMDAwIiwib2ZmZXJzQ3Vyc29yIjpudWxsfQ"
6844
7045
  }),
6845
- limit: zod.string().regex(/^[1-9]\d*$/, { message: "Limit must be a positive integer" }).transform((val) => Number.parseInt(val, 10)).pipe(zod.number().max(MAX_LIMIT, { message: `Limit cannot exceed ${MAX_LIMIT}` })).optional().default(DEFAULT_LIMIT$4).meta({
6846
- description: `Limit maximum: ${MAX_LIMIT}. Default: ${DEFAULT_LIMIT$4}`,
7046
+ limit: zod.string().regex(/^[1-9]\d*$/, { message: "Limit must be a positive integer" }).transform((val) => Number.parseInt(val, 10)).pipe(zod.number().max(MAX_LIMIT, { message: `Limit cannot exceed ${MAX_LIMIT}` })).optional().default(DEFAULT_LIMIT$5).meta({
7047
+ description: `Limit maximum: ${MAX_LIMIT}. Default: ${DEFAULT_LIMIT$5}`,
6847
7048
  example: 10
6848
7049
  })
6849
7050
  });
@@ -6927,7 +7128,7 @@ async function getBook(params, db) {
6927
7128
  side: query.side,
6928
7129
  levels_count: levels.length,
6929
7130
  has_next_cursor: nextCursor != null,
6930
- first_level_price: firstLevel?.price.toString() ?? null,
7131
+ first_level_tick: firstLevel?.tick ?? null,
6931
7132
  first_level_assets: firstLevel?.assets.toString() ?? null,
6932
7133
  first_level_count: firstLevel?.count ?? null
6933
7134
  });
@@ -7020,7 +7221,7 @@ async function getConfigContracts(query, chainRegistry) {
7020
7221
  }
7021
7222
  function parseCursor$1(cursor) {
7022
7223
  const [chain, address] = cursor.split(":", 2);
7023
- if (!chain || !address) throw new BadRequestError("Cursor must be in the format chain_id:0x...");
7224
+ if (!chain || !address) throw new BadRequestError$1("Cursor must be in the format chain_id:0x...");
7024
7225
  return {
7025
7226
  chain_id: Number.parseInt(chain, 10),
7026
7227
  address: address.toLowerCase()
@@ -7079,6 +7280,33 @@ const assets = {
7079
7280
  "0x2260FAC5E5542a773Aa44fBCfeDf7C193bc2C599"
7080
7281
  ]
7081
7282
  };
7283
+ const collateralAssets = {
7284
+ [ChainId.ETHEREUM.toString()]: [
7285
+ "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
7286
+ "0x1aBaEA1f7C830bD89Acc67eC4af516284b1bC33c",
7287
+ "0x2260FAC5E5542a773Aa44fBCfeDf7C193bc2C599",
7288
+ "0x7f39C581F595B53c5cb19bD0b3f8dA6c935E2Ca0"
7289
+ ],
7290
+ [ChainId.BASE.toString()]: [
7291
+ "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
7292
+ "0x4200000000000000000000000000000000000006",
7293
+ "0xcbB7C0000aB88B473b1f5aFd9ef808440eed33Bf",
7294
+ "0xc1CBa3fCea344f92D9239c08C0568f6F2F0ee452",
7295
+ "0x60a3E35Cc302bFA44Cb288Bc5a4F316Fdb1adb42"
7296
+ ],
7297
+ [ChainId["ETHEREUM-VIRTUAL-TESTNET"].toString()]: [
7298
+ "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
7299
+ "0x1aBaEA1f7C830bD89Acc67eC4af516284b1bC33c",
7300
+ "0x2260FAC5E5542a773Aa44fBCfeDf7C193bc2C599",
7301
+ "0x7f39C581F595B53c5cb19bD0b3f8dA6c935E2Ca0"
7302
+ ],
7303
+ [ChainId.ANVIL.toString()]: [
7304
+ "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
7305
+ "0x1aBaEA1f7C830bD89Acc67eC4af516284b1bC33c",
7306
+ "0x2260FAC5E5542a773Aa44fBCfeDf7C193bc2C599",
7307
+ "0x7f39C581F595B53c5cb19bD0b3f8dA6c935E2Ca0"
7308
+ ]
7309
+ };
7082
7310
  const oracles = {
7083
7311
  [ChainId.ETHEREUM.toString()]: [
7084
7312
  "0xDddd770BADd886dF3864029e4B377B5F6a2B6b83",
@@ -7119,26 +7347,26 @@ const oracles = {
7119
7347
  const configs = {
7120
7348
  ethereum: {
7121
7349
  callbacks: [{ type: Type$1.BuyWithEmptyCallback }, { type: Type$1.SellWithEmptyCallback }],
7122
- maturities: [MaturityType.EndOfMonth, MaturityType.EndOfNextMonth]
7350
+ maturities: [MaturityType.EndOfWeek, MaturityType.EndOfNextWeek]
7123
7351
  },
7124
7352
  base: {
7125
7353
  callbacks: [{ type: Type$1.BuyWithEmptyCallback }, { type: Type$1.SellWithEmptyCallback }],
7126
- maturities: [MaturityType.EndOfMonth, MaturityType.EndOfNextMonth]
7354
+ maturities: [MaturityType.EndOfWeek, MaturityType.EndOfNextWeek]
7127
7355
  },
7128
7356
  "ethereum-virtual-testnet": {
7129
7357
  callbacks: [{ type: Type$1.BuyWithEmptyCallback }, { type: Type$1.SellWithEmptyCallback }],
7130
- maturities: [MaturityType.EndOfMonth, MaturityType.EndOfNextMonth]
7358
+ maturities: [MaturityType.EndOfWeek, MaturityType.EndOfNextWeek]
7131
7359
  },
7132
7360
  anvil: {
7133
7361
  callbacks: [{ type: Type$1.BuyWithEmptyCallback }, { type: Type$1.SellWithEmptyCallback }],
7134
- maturities: [MaturityType.EndOfMonth, MaturityType.EndOfNextMonth]
7362
+ maturities: [MaturityType.EndOfWeek, MaturityType.EndOfNextWeek]
7135
7363
  }
7136
7364
  };
7137
7365
 
7138
7366
  //#endregion
7139
7367
  //#region src/gatekeeper/ConfigRules.ts
7140
7368
  /**
7141
- * Build the configured rules (maturities + callback addresses + loan tokens + oracles) for the provided chains.
7369
+ * Build the configured rules (maturities + callback addresses + loan tokens + collateral tokens + oracles) for the provided chains.
7142
7370
  * @param chains - Chains to include in the configured rules.
7143
7371
  * @returns Sorted list of config rules.
7144
7372
  */
@@ -7158,6 +7386,12 @@ function buildConfigRules(chains) {
7158
7386
  chain_id: chain.id,
7159
7387
  address: normalizeAddress(address)
7160
7388
  });
7389
+ const collateralTokens = collateralAssets[chain.id.toString()] ?? [];
7390
+ for (const address of collateralTokens) rules.push({
7391
+ type: "collateral_token",
7392
+ chain_id: chain.id,
7393
+ address: normalizeAddress(address)
7394
+ });
7161
7395
  const oracles$2 = oracles[chain.id.toString()] ?? [];
7162
7396
  for (const address of oracles$2) rules.push({
7163
7397
  type: "oracle",
@@ -7185,6 +7419,10 @@ function buildConfigRulesChecksum(rules) {
7185
7419
  hash.update(`callback:${rule.chain_id}:${rule.callback_type}:${rule.address}\n`);
7186
7420
  continue;
7187
7421
  }
7422
+ if (rule.type === "collateral_token") {
7423
+ hash.update(`collateral_token:${rule.chain_id}:${rule.address}\n`);
7424
+ continue;
7425
+ }
7188
7426
  if (rule.type === "oracle") {
7189
7427
  hash.update(`oracle:${rule.chain_id}:${rule.address}\n`);
7190
7428
  continue;
@@ -7205,6 +7443,7 @@ function compareConfigRules(left, right) {
7205
7443
  return left.address.localeCompare(right.address);
7206
7444
  }
7207
7445
  if (left.type === "loan_token" && right.type === "loan_token") return left.address.localeCompare(right.address);
7446
+ if (left.type === "collateral_token" && right.type === "collateral_token") return left.address.localeCompare(right.address);
7208
7447
  if (left.type === "oracle" && right.type === "oracle") return left.address.localeCompare(right.address);
7209
7448
  return 0;
7210
7449
  }
@@ -7235,8 +7474,8 @@ async function getConfigRules(query, chains) {
7235
7474
  } catch (err) {
7236
7475
  return failure(err);
7237
7476
  }
7238
- if (cursorRule && typeFilter && !typeFilter.has(cursorRule.type)) return failure(new BadRequestError("Cursor type must match requested rule types"));
7239
- if (cursorRule && chainFilter && !chainFilter.has(cursorRule.chain_id)) return failure(new BadRequestError("Cursor chain_id must match requested chains"));
7477
+ if (cursorRule && typeFilter && !typeFilter.has(cursorRule.type)) return failure(new BadRequestError$1("Cursor type must match requested rule types"));
7478
+ if (cursorRule && chainFilter && !chainFilter.has(cursorRule.chain_id)) return failure(new BadRequestError$1("Cursor chain_id must match requested chains"));
7240
7479
  const startIndex = cursorRule ? findStartIndex(filteredRules, cursorRule) : 0;
7241
7480
  const page = filteredRules.slice(startIndex, startIndex + limit);
7242
7481
  const nextCursor = startIndex + limit < filteredRules.length && page.length > 0 ? formatCursor(page.at(-1)) : null;
@@ -7251,19 +7490,20 @@ function formatCursor(rule) {
7251
7490
  if (rule.type === "maturity") return `maturity:${rule.chain_id}:${rule.timestamp}:${rule.name}`;
7252
7491
  if (rule.type === "callback") return `callback:${rule.chain_id}:${rule.callback_type}:${rule.address.toLowerCase()}`;
7253
7492
  if (rule.type === "oracle") return `oracle:${rule.chain_id}:${rule.address.toLowerCase()}`;
7493
+ if (rule.type === "collateral_token") return `collateral_token:${rule.chain_id}:${rule.address.toLowerCase()}`;
7254
7494
  return `loan_token:${rule.chain_id}:${rule.address.toLowerCase()}`;
7255
7495
  }
7256
7496
  function parseCursor(cursor) {
7257
7497
  const [type, chain, ...rest] = cursor.split(":");
7258
- if (!type || !chain || rest.length === 0) throw new BadRequestError("Cursor must be in the format type:chain_id:<value>");
7259
- if (!isConfigRuleType(type)) throw new BadRequestError("Cursor has an invalid rule type");
7498
+ if (!type || !chain || rest.length === 0) throw new BadRequestError$1("Cursor must be in the format type:chain_id:<value>");
7499
+ if (!isConfigRuleType(type)) throw new BadRequestError$1("Cursor has an invalid rule type");
7260
7500
  const chain_id = Number.parseInt(chain, 10);
7261
- if (!Number.isFinite(chain_id)) throw new BadRequestError("Cursor has an invalid chain_id");
7501
+ if (!Number.isFinite(chain_id)) throw new BadRequestError$1("Cursor has an invalid chain_id");
7262
7502
  if (type === "maturity") {
7263
7503
  const timestampValue = Number.parseInt(rest[0] ?? "", 10);
7264
7504
  const nameValue = rest.slice(1).join(":");
7265
- if (!Number.isFinite(timestampValue) || nameValue.length === 0) throw new BadRequestError("Cursor must be in the format maturity:chain_id:timestamp:name");
7266
- if (!isMaturityType(nameValue)) throw new BadRequestError("Cursor has an invalid maturity name");
7505
+ if (!Number.isFinite(timestampValue) || nameValue.length === 0) throw new BadRequestError$1("Cursor must be in the format maturity:chain_id:timestamp:name");
7506
+ if (!isMaturityType(nameValue)) throw new BadRequestError$1("Cursor has an invalid maturity name");
7267
7507
  return {
7268
7508
  type,
7269
7509
  chain_id,
@@ -7274,8 +7514,8 @@ function parseCursor(cursor) {
7274
7514
  if (type === "callback") {
7275
7515
  const callbackTypeValue = rest[0] ?? "";
7276
7516
  const addressValue = rest.slice(1).join(":");
7277
- if (!callbackTypeValue || !addressValue) throw new BadRequestError("Cursor must be in the format callback:chain_id:callback_type:address");
7278
- if (!isCallbackType(callbackTypeValue)) throw new BadRequestError("Cursor has an invalid callback type");
7517
+ if (!callbackTypeValue || !addressValue) throw new BadRequestError$1("Cursor must be in the format callback:chain_id:callback_type:address");
7518
+ if (!isCallbackType(callbackTypeValue)) throw new BadRequestError$1("Cursor has an invalid callback type");
7279
7519
  return {
7280
7520
  type,
7281
7521
  chain_id,
@@ -7283,16 +7523,16 @@ function parseCursor(cursor) {
7283
7523
  address: parseAddress(addressValue, "Cursor address")
7284
7524
  };
7285
7525
  }
7286
- if (type === "loan_token" || type === "oracle") {
7526
+ if (type === "loan_token" || type === "collateral_token" || type === "oracle") {
7287
7527
  const addressValue = rest.join(":");
7288
- if (!addressValue) throw new BadRequestError(`Cursor must be in the format ${type}:chain_id:address`);
7528
+ if (!addressValue) throw new BadRequestError$1(`Cursor must be in the format ${type}:chain_id:address`);
7289
7529
  return {
7290
7530
  type,
7291
7531
  chain_id,
7292
7532
  address: parseAddress(addressValue, "Cursor address")
7293
7533
  };
7294
7534
  }
7295
- throw new BadRequestError("Cursor has an invalid rule type");
7535
+ throw new BadRequestError$1("Cursor has an invalid rule type");
7296
7536
  }
7297
7537
  function findStartIndex(rules, cursor) {
7298
7538
  let low = 0;
@@ -7306,11 +7546,11 @@ function findStartIndex(rules, cursor) {
7306
7546
  return low;
7307
7547
  }
7308
7548
  function parseAddress(address, label) {
7309
- if (!/^0x[a-fA-F0-9]{40}$/.test(address)) throw new BadRequestError(`${label} must be a valid 20-byte address`);
7549
+ if (!/^0x[a-fA-F0-9]{40}$/.test(address)) throw new BadRequestError$1(`${label} must be a valid 20-byte address`);
7310
7550
  return address.toLowerCase();
7311
7551
  }
7312
7552
  function isConfigRuleType(value) {
7313
- return value === "maturity" || value === "callback" || value === "loan_token" || value === "oracle";
7553
+ return value === "maturity" || value === "callback" || value === "loan_token" || value === "collateral_token" || value === "oracle";
7314
7554
  }
7315
7555
  function isMaturityType(value) {
7316
7556
  return Object.values(MaturityType).includes(value);
@@ -7319,7 +7559,7 @@ function parseMaturity(value) {
7319
7559
  try {
7320
7560
  return from$16(value);
7321
7561
  } catch (err) {
7322
- throw new BadRequestError(err instanceof Error ? err.message : "Invalid maturity timestamp");
7562
+ throw new BadRequestError$1(err instanceof Error ? err.message : "Invalid maturity timestamp");
7323
7563
  }
7324
7564
  }
7325
7565
  function isCallbackType(value) {
@@ -7415,7 +7655,7 @@ async function getHealth(query, db, chainRegistry) {
7415
7655
  try {
7416
7656
  const parsed = safeParse("get_health", query);
7417
7657
  if (!parsed.success) return failure(parsed.error);
7418
- const snapshot = await create$16({
7658
+ const snapshot = await create$17({
7419
7659
  db,
7420
7660
  chainRegistry
7421
7661
  }).getSnapshot();
@@ -7444,7 +7684,7 @@ async function getHealthChains(query, db, healthClients, chainRegistry) {
7444
7684
  try {
7445
7685
  const parsed = safeParse("get_health_chains", query);
7446
7686
  if (!parsed.success) return failure(parsed.error);
7447
- const snapshot = await create$16({
7687
+ const snapshot = await create$17({
7448
7688
  db,
7449
7689
  healthClients,
7450
7690
  chainRegistry
@@ -7476,7 +7716,7 @@ async function getHealthCollectors(query, db, chainRegistry) {
7476
7716
  try {
7477
7717
  const parsed = safeParse("get_health_collectors", query);
7478
7718
  if (!parsed.success) return failure(parsed.error);
7479
- const snapshot = await create$16({
7719
+ const snapshot = await create$17({
7480
7720
  db,
7481
7721
  chainRegistry
7482
7722
  }).getSnapshot();
@@ -7505,39 +7745,274 @@ async function getHealthCollectors(query, db, chainRegistry) {
7505
7745
  }
7506
7746
  }
7507
7747
 
7748
+ //#endregion
7749
+ //#region src/database/readers/ObligationsListing.ts
7750
+ const SORT_FIELDS = [
7751
+ "id",
7752
+ "ask",
7753
+ "bid",
7754
+ "maturity"
7755
+ ];
7756
+ const CURSOR_ID_REGEX = /^0x[a-f0-9]{64}$/i;
7757
+ const INT32_MIN = -2147483648;
7758
+ const INT32_MAX = 2147483647;
7759
+ const INT32_MAX_BIGINT = BigInt(INT32_MAX);
7760
+ const MAX_CURSOR_SORT_FIELDS = 4;
7761
+ const DEFAULT_LIMIT$4 = 20;
7762
+ var BadRequestError = class extends Error {
7763
+ constructor(message) {
7764
+ super(message);
7765
+ this.name = "ObligationsListingBadRequestError";
7766
+ }
7767
+ };
7768
+ /**
7769
+ * Creates the obligations listing reader facade.
7770
+ * @param parameters - Reader dependencies.
7771
+ * @returns Obligations listing reader.
7772
+ */
7773
+ function create$16(parameters) {
7774
+ const { db } = parameters;
7775
+ return { list: async (queryParameters) => {
7776
+ const { ids, chainId: chainIds, loanToken: loanTokens, collateralToken: collateralTokens, maturity: maturities, sort: sortTokens, cursor: encodedCursor, limit: requestedLimit } = queryParameters ?? {};
7777
+ const limit = requestedLimit ?? DEFAULT_LIMIT$4;
7778
+ if (!Number.isInteger(limit) || limit <= 0) throw new BadRequestError("Limit must be a positive integer");
7779
+ const cursorPayload = encodedCursor ? decodeCursorPayload(encodedCursor) : void 0;
7780
+ const requestedSort = normalizeSort(sortTokens);
7781
+ const cursorSort = cursorPayload ? normalizeSort(cursorPayload.sort) : void 0;
7782
+ if (cursorSort !== void 0 && sortTokens !== void 0 && !hasSameSort(requestedSort, cursorSort)) throw new BadRequestError("Cursor sort does not match requested sort");
7783
+ const sort = sortTokens !== void 0 ? requestedSort : cursorSort ?? requestedSort;
7784
+ const cursorValues = cursorPayload ? cursorValuesFromPayload(cursorPayload) : void 0;
7785
+ const now$3 = now();
7786
+ const loanTokenFilter = loanTokens !== void 0 && loanTokens.length > 0 ? drizzle_orm.sql`(${drizzle_orm.sql.join(loanTokens.map((token) => drizzle_orm.sql`LOWER(${obligations.loanToken}) = ${token.toLowerCase()}`), drizzle_orm.sql` OR `)})` : void 0;
7787
+ const collateralFilter = collateralTokens !== void 0 && collateralTokens.length > 0 ? drizzle_orm.sql`EXISTS (
7788
+ SELECT 1 FROM ${obligationCollateralsV2} oc
7789
+ WHERE oc.obligation_id = ${obligations.obligationId}
7790
+ AND (${drizzle_orm.sql.join(collateralTokens.map((token) => drizzle_orm.sql`LOWER(oc.asset) = ${token.toLowerCase()}`), drizzle_orm.sql` OR `)})
7791
+ )` : void 0;
7792
+ const bestAskTick = db.select({ askTick: offers.tick }).from(offers).innerJoin(groups, (0, drizzle_orm.and)((0, drizzle_orm.eq)(offers.groupChainId, groups.chainId), (0, drizzle_orm.eq)(offers.groupMaker, groups.maker), (0, drizzle_orm.eq)(offers.group, groups.group))).leftJoin(validations, (0, drizzle_orm.eq)(offers.hash, validations.offerHash)).leftJoin(status, (0, drizzle_orm.eq)(validations.statusId, status.id)).where((0, drizzle_orm.and)((0, drizzle_orm.eq)(offers.obligationId, obligations.obligationId), (0, drizzle_orm.eq)(offers.buy, false), (0, drizzle_orm.gte)(offers.expiry, now$3), (0, drizzle_orm.gte)(offers.maturity, now$3), (0, drizzle_orm.lte)(offers.start, now$3), drizzle_orm.sql`(${status.code} IS NULL OR ${status.code} = ${Status.VALID})`)).orderBy((0, drizzle_orm.desc)(offers.tick)).limit(1).as("best_ask_tick");
7793
+ const bestBidTick = db.select({ bidTick: offers.tick }).from(offers).innerJoin(groups, (0, drizzle_orm.and)((0, drizzle_orm.eq)(offers.groupChainId, groups.chainId), (0, drizzle_orm.eq)(offers.groupMaker, groups.maker), (0, drizzle_orm.eq)(offers.group, groups.group))).leftJoin(validations, (0, drizzle_orm.eq)(offers.hash, validations.offerHash)).leftJoin(status, (0, drizzle_orm.eq)(validations.statusId, status.id)).where((0, drizzle_orm.and)((0, drizzle_orm.eq)(offers.obligationId, obligations.obligationId), (0, drizzle_orm.eq)(offers.buy, true), (0, drizzle_orm.gte)(offers.expiry, now$3), (0, drizzle_orm.gte)(offers.maturity, now$3), (0, drizzle_orm.lte)(offers.start, now$3), drizzle_orm.sql`(${status.code} IS NULL OR ${status.code} = ${Status.VALID})`)).orderBy((0, drizzle_orm.asc)(offers.tick)).limit(1).as("best_bid_tick");
7794
+ const obligationsWithQuotes = db.select({
7795
+ obligationId: obligations.obligationId,
7796
+ chainId: obligations.chainId,
7797
+ loanToken: obligations.loanToken,
7798
+ collaterals: drizzle_orm.sql`ARRAY_AGG(jsonb_build_object('asset', ${obligationCollateralsV2.asset}, 'oracle', ${oracles$1.address}, 'lltv', ${obligationCollateralsV2.lltv}))`.as("collaterals"),
7799
+ maturity: obligations.maturity,
7800
+ askTick: drizzle_orm.sql`MAX(${bestAskTick.askTick})`.as("ask_tick"),
7801
+ bidTick: drizzle_orm.sql`MAX(${bestBidTick.bidTick})`.as("bid_tick"),
7802
+ ask: drizzle_orm.sql`COALESCE(MAX(${bestAskTick.askTick}) + 1, 0)`.as("ask"),
7803
+ bid: drizzle_orm.sql`COALESCE(MAX(${bestBidTick.bidTick}) + 1, 0)`.as("bid")
7804
+ }).from(obligations).innerJoin(obligationCollateralsV2, (0, drizzle_orm.eq)(obligations.obligationId, obligationCollateralsV2.obligationId)).innerJoin(oracles$1, drizzle_orm.sql`${obligationCollateralsV2.oracleChainId} = ${oracles$1.chainId}
7805
+ AND ${obligationCollateralsV2.oracleAddress} = ${oracles$1.address}`).leftJoinLateral(bestAskTick, drizzle_orm.sql`true`).leftJoinLateral(bestBidTick, drizzle_orm.sql`true`).groupBy(obligations.obligationId).where((0, drizzle_orm.and)(ids !== void 0 && ids.length > 0 ? (0, drizzle_orm.inArray)(obligations.obligationId, ids) : void 0, chainIds !== void 0 && chainIds.length > 0 ? (0, drizzle_orm.inArray)(obligations.chainId, chainIds) : void 0, loanTokenFilter, maturities !== void 0 && maturities.length > 0 ? (0, drizzle_orm.inArray)(obligations.maturity, maturities) : (0, drizzle_orm.gte)(obligations.maturity, now$3), collateralFilter)).as("obligations_with_quotes");
7806
+ const sortColumns = {
7807
+ id: obligationsWithQuotes.obligationId,
7808
+ ask: obligationsWithQuotes.ask,
7809
+ bid: obligationsWithQuotes.bid,
7810
+ maturity: obligationsWithQuotes.maturity
7811
+ };
7812
+ const rows = await db.select({
7813
+ obligationId: obligationsWithQuotes.obligationId,
7814
+ chainId: obligationsWithQuotes.chainId,
7815
+ loanToken: obligationsWithQuotes.loanToken,
7816
+ collaterals: obligationsWithQuotes.collaterals,
7817
+ maturity: obligationsWithQuotes.maturity,
7818
+ askTick: obligationsWithQuotes.askTick,
7819
+ bidTick: obligationsWithQuotes.bidTick,
7820
+ ask: obligationsWithQuotes.ask,
7821
+ bid: obligationsWithQuotes.bid
7822
+ }).from(obligationsWithQuotes).where(buildCursorFilter(sortColumns, sort, cursorValues)).orderBy(...buildOrderBy(sortColumns, sort)).limit(limit + 1);
7823
+ const hasMore = rows.length > limit;
7824
+ const listedRows = (hasMore ? rows.slice(0, limit) : rows).map((row) => {
7825
+ return {
7826
+ obligation: from$15({
7827
+ chainId: row.chainId,
7828
+ loanToken: row.loanToken,
7829
+ collaterals: row.collaterals.sort((left, right) => left.asset.localeCompare(right.asset)).map((collateral) => from$17({
7830
+ asset: collateral.asset,
7831
+ oracle: collateral.oracle,
7832
+ lltv: from$18(BigInt(collateral.lltv))
7833
+ })),
7834
+ maturity: row.maturity
7835
+ }),
7836
+ quote: from$11({
7837
+ obligationId: row.obligationId,
7838
+ ask: { tick: row.askTick },
7839
+ bid: { tick: row.bidTick }
7840
+ }),
7841
+ cursorValues: {
7842
+ id: row.obligationId,
7843
+ ask: toBigInt(row.ask),
7844
+ bid: toBigInt(row.bid),
7845
+ maturity: row.maturity
7846
+ }
7847
+ };
7848
+ });
7849
+ const nextCursor = hasMore && listedRows.length > 0 ? encodeCursorPayload({
7850
+ sort: sortToTokens(sort),
7851
+ id: listedRows[listedRows.length - 1].cursorValues.id,
7852
+ ask: listedRows[listedRows.length - 1].cursorValues.ask.toString(),
7853
+ bid: listedRows[listedRows.length - 1].cursorValues.bid.toString(),
7854
+ maturity: listedRows[listedRows.length - 1].cursorValues.maturity
7855
+ }) : null;
7856
+ return {
7857
+ obligations: listedRows.map((row) => ({
7858
+ obligation: row.obligation,
7859
+ quote: row.quote
7860
+ })),
7861
+ nextCursor
7862
+ };
7863
+ } };
7864
+ }
7865
+ function isSortField(value) {
7866
+ return SORT_FIELDS.includes(value);
7867
+ }
7868
+ function parseSortToken(token) {
7869
+ const direction = token.startsWith("-") ? "desc" : "asc";
7870
+ const rawField = token.startsWith("-") ? token.slice(1) : token;
7871
+ if (!isSortField(rawField)) throw new BadRequestError(`Invalid sort field: ${rawField}`);
7872
+ return {
7873
+ field: rawField,
7874
+ direction
7875
+ };
7876
+ }
7877
+ function normalizeSort(sortTokens) {
7878
+ const parsed = sortTokens?.length ? sortTokens.map(parseSortToken) : [{
7879
+ field: "id",
7880
+ direction: "asc"
7881
+ }];
7882
+ return parsed.some((entry) => entry.field === "id") ? parsed : [...parsed, {
7883
+ field: "id",
7884
+ direction: "asc"
7885
+ }];
7886
+ }
7887
+ function sortToTokens(sortEntries) {
7888
+ return sortEntries.map((entry) => entry.direction === "desc" ? `-${entry.field}` : entry.field);
7889
+ }
7890
+ function hasSameSort(left, right) {
7891
+ if (left.length !== right.length) return false;
7892
+ return left.every((sortEntry, index) => sortEntry.field === right[index]?.field && sortEntry.direction === right[index]?.direction);
7893
+ }
7894
+ function decodeCursorPayload(cursor) {
7895
+ let decoded;
7896
+ try {
7897
+ decoded = JSON.parse(Buffer.from(cursor, "base64url").toString("utf8"));
7898
+ } catch {
7899
+ throw new BadRequestError("Invalid cursor format");
7900
+ }
7901
+ if (decoded === null || typeof decoded !== "object") throw new BadRequestError("Invalid cursor payload");
7902
+ const payload = decoded;
7903
+ const sortTokens = parseCursorSortTokens(payload.sort);
7904
+ if (typeof payload.id !== "string" || !CURSOR_ID_REGEX.test(payload.id)) throw new BadRequestError("Invalid cursor obligation id");
7905
+ const ask = parseCursorNonNegativeInt32(payload.ask, "ask");
7906
+ const bid = parseCursorNonNegativeInt32(payload.bid, "bid");
7907
+ if (!isInt32(payload.maturity)) throw new BadRequestError("Invalid cursor maturity value");
7908
+ return {
7909
+ sort: sortTokens,
7910
+ id: payload.id,
7911
+ ask,
7912
+ bid,
7913
+ maturity: payload.maturity
7914
+ };
7915
+ }
7916
+ function parseCursorSortTokens(value) {
7917
+ if (!Array.isArray(value) || value.length === 0 || value.length > MAX_CURSOR_SORT_FIELDS) throw new BadRequestError("Invalid cursor sort");
7918
+ const sortEntries = value.map((token) => {
7919
+ if (typeof token !== "string") throw new BadRequestError("Invalid cursor sort");
7920
+ try {
7921
+ return parseSortToken(token);
7922
+ } catch {
7923
+ throw new BadRequestError("Invalid cursor sort");
7924
+ }
7925
+ });
7926
+ if (new Set(sortEntries.map((entry) => entry.field)).size !== sortEntries.length) throw new BadRequestError("Invalid cursor sort");
7927
+ return sortToTokens(sortEntries);
7928
+ }
7929
+ function parseCursorNonNegativeInt32(value, field) {
7930
+ if (typeof value !== "string" || !/^\d+$/.test(value)) throw new BadRequestError(`Invalid cursor ${field} value`);
7931
+ if (BigInt(value) > INT32_MAX_BIGINT) throw new BadRequestError(`Invalid cursor ${field} value`);
7932
+ return value;
7933
+ }
7934
+ function isInt32(value) {
7935
+ return typeof value === "number" && Number.isSafeInteger(value) && value >= INT32_MIN && value <= INT32_MAX;
7936
+ }
7937
+ function encodeCursorPayload(payload) {
7938
+ return Buffer.from(JSON.stringify(payload), "utf8").toString("base64url");
7939
+ }
7940
+ function cursorValuesFromPayload(payload) {
7941
+ return {
7942
+ id: payload.id,
7943
+ ask: BigInt(payload.ask),
7944
+ bid: BigInt(payload.bid),
7945
+ maturity: payload.maturity
7946
+ };
7947
+ }
7948
+ function cursorComparisonValue(cursorValues, field) {
7949
+ switch (field) {
7950
+ case "id": return cursorValues.id;
7951
+ case "maturity": return cursorValues.maturity;
7952
+ case "ask": return cursorValues.ask.toString();
7953
+ case "bid": return cursorValues.bid.toString();
7954
+ }
7955
+ }
7956
+ function buildCursorFilter(columns, sortEntries, cursorValues) {
7957
+ if (cursorValues === void 0) return void 0;
7958
+ const comparisons = sortEntries.map((sortEntry, index) => {
7959
+ const equals = sortEntries.slice(0, index).map((previous) => {
7960
+ return drizzle_orm.sql`${columns[previous.field]} = ${cursorComparisonValue(cursorValues, previous.field)}`;
7961
+ });
7962
+ const comparison = sortEntry.direction === "asc" ? drizzle_orm.sql`${columns[sortEntry.field]} > ${cursorComparisonValue(cursorValues, sortEntry.field)}` : drizzle_orm.sql`${columns[sortEntry.field]} < ${cursorComparisonValue(cursorValues, sortEntry.field)}`;
7963
+ return equals.length > 0 ? drizzle_orm.sql`(${drizzle_orm.sql.join([...equals, comparison], drizzle_orm.sql` AND `)})` : drizzle_orm.sql`(${comparison})`;
7964
+ });
7965
+ return comparisons.length > 0 ? drizzle_orm.sql`(${drizzle_orm.sql.join(comparisons, drizzle_orm.sql` OR `)})` : void 0;
7966
+ }
7967
+ function buildOrderBy(columns, sortEntries) {
7968
+ return sortEntries.map((sortEntry) => sortEntry.direction === "asc" ? (0, drizzle_orm.asc)(columns[sortEntry.field]) : (0, drizzle_orm.desc)(columns[sortEntry.field]));
7969
+ }
7970
+ function toBigInt(value) {
7971
+ if (typeof value === "bigint") return value;
7972
+ if (typeof value === "number") return BigInt(value);
7973
+ return BigInt(value.split(".")[0] ?? "0");
7974
+ }
7975
+
7508
7976
  //#endregion
7509
7977
  //#region src/api/Controllers/getObligation.ts
7978
+ function toPayloadError$1(err) {
7979
+ if (err instanceof BadRequestError) return new BadRequestError$1(err.message);
7980
+ return err;
7981
+ }
7510
7982
  async function getObligation(params, db) {
7511
7983
  const logger = getLogger();
7512
7984
  const result = safeParse("get_obligation", params, (issue) => issue.message);
7513
7985
  if (!result.success) return failure(result.error);
7514
7986
  const query = result.data;
7515
7987
  try {
7516
- const { obligations } = await db.offers.getObligations({ ids: [query.obligation_id] });
7517
- if (obligations.length === 0) return failure(new NotFoundError("Obligation not found"));
7518
- const obligation = obligations[0];
7519
- const [quote] = await db.offers.getQuotes({ obligationIds: [id(obligation)] });
7988
+ const listing = await db.readers.obligations.list({
7989
+ ids: [query.obligation_id],
7990
+ limit: 1
7991
+ });
7992
+ if (listing.obligations.length === 0) return failure(new NotFoundError("Obligation not found"));
7993
+ const obligation = listing.obligations[0];
7520
7994
  return success({
7521
- data: from$4(obligation, quote ?? {
7522
- obligationId: id(obligation),
7523
- ask: { price: 0n },
7524
- bid: { price: 0n }
7525
- }),
7995
+ data: from$4(obligation.obligation, obligation.quote),
7526
7996
  cursor: null
7527
7997
  });
7528
7998
  } catch (err) {
7999
+ const payloadError = toPayloadError$1(err);
7529
8000
  logger.error({
7530
- err,
8001
+ err: payloadError,
7531
8002
  msg: "Error get obligation",
7532
- errorMessage: err instanceof Error ? err.message : String(err),
7533
- errorStack: err instanceof Error ? err.stack : void 0
8003
+ errorMessage: payloadError instanceof Error ? payloadError.message : String(payloadError),
8004
+ errorStack: payloadError instanceof Error ? payloadError.stack : void 0
7534
8005
  });
7535
- return failure(err);
8006
+ return failure(payloadError);
7536
8007
  }
7537
8008
  }
7538
8009
 
7539
8010
  //#endregion
7540
8011
  //#region src/api/Controllers/getObligations.ts
8012
+ function toPayloadError(err) {
8013
+ if (err instanceof BadRequestError) return new BadRequestError$1(err.message);
8014
+ return err;
8015
+ }
7541
8016
  async function getObligations$1(queryParameters, db) {
7542
8017
  const logger = getLogger();
7543
8018
  const result = safeParse("get_obligations", queryParameters, (issue) => issue.message);
@@ -7548,31 +8023,28 @@ async function getObligations$1(queryParameters, db) {
7548
8023
  const loanTokens = query.loan_tokens?.length ? query.loan_tokens : void 0;
7549
8024
  const collateralTokens = query.collateral_tokens?.length ? query.collateral_tokens : void 0;
7550
8025
  const maturities = query.maturities?.length ? query.maturities : void 0;
7551
- const { obligations, nextCursor } = await db.offers.getObligations({
7552
- cursor: query.cursor,
7553
- limit: query.limit,
8026
+ const listing = await db.readers.obligations.list({
7554
8027
  chainId: chainIds,
7555
8028
  loanToken: loanTokens,
7556
8029
  collateralToken: collateralTokens,
7557
- maturity: maturities
8030
+ maturity: maturities,
8031
+ sort: query.sort,
8032
+ cursor: query.cursor,
8033
+ limit: query.limit
7558
8034
  });
7559
- const quotes = await db.offers.getQuotes({ obligationIds: obligations.map((o) => id(o)) });
7560
8035
  return success({
7561
- data: obligations.map((o) => from$4(o, quotes.find((q) => q.obligationId === id(o)) ?? {
7562
- obligationId: id(o),
7563
- ask: { price: 0n },
7564
- bid: { price: 0n }
7565
- })),
7566
- cursor: nextCursor ?? null
8036
+ data: listing.obligations.map((item) => from$4(item.obligation, item.quote)),
8037
+ cursor: listing.nextCursor
7567
8038
  });
7568
8039
  } catch (err) {
8040
+ const payloadError = toPayloadError(err);
7569
8041
  logger.error({
7570
- err,
8042
+ err: payloadError,
7571
8043
  msg: "Error get obligations",
7572
- errorMessage: err instanceof Error ? err.message : String(err),
7573
- errorStack: err instanceof Error ? err.stack : void 0
8044
+ errorMessage: payloadError instanceof Error ? payloadError.message : String(payloadError),
8045
+ errorStack: payloadError instanceof Error ? payloadError.stack : void 0
7574
8046
  });
7575
- return failure(err);
8047
+ return failure(payloadError);
7576
8048
  }
7577
8049
  }
7578
8050
 
@@ -7606,6 +8078,7 @@ function create$15(config) {
7606
8078
  groupMaker: offer.maker.toLowerCase(),
7607
8079
  callbackAddress: offer.callback.address.toLowerCase(),
7608
8080
  callbackData: offer.callback.data,
8081
+ receiverIfMakerIsSeller: offer.receiverIfMakerIsSeller.toLowerCase(),
7609
8082
  blockNumber
7610
8083
  })));
7611
8084
  if (offersRows.length === 0) return [];
@@ -7657,7 +8130,7 @@ function create$15(config) {
7657
8130
  assets: offers.assets,
7658
8131
  obligationUnits: offers.obligationUnits,
7659
8132
  obligationShares: offers.obligationShares,
7660
- price: offers.price,
8133
+ tick: offers.tick,
7661
8134
  maturity: offers.maturity,
7662
8135
  expiry: offers.expiry,
7663
8136
  start: offers.start,
@@ -7668,16 +8141,18 @@ function create$15(config) {
7668
8141
  loanToken: obligations.loanToken,
7669
8142
  callbackAddress: offers.callbackAddress,
7670
8143
  callbackData: offers.callbackData,
8144
+ receiverIfMakerIsSeller: offers.receiverIfMakerIsSeller,
7671
8145
  collaterals: collateralsLateral.collaterals,
7672
8146
  blockNumber: offers.blockNumber
7673
8147
  }).from(offers).innerJoin(obligations, (0, drizzle_orm.eq)(offers.obligationId, obligations.obligationId)).innerJoinLateral(collateralsLateral, drizzle_orm.sql`true`).where((0, drizzle_orm.and)(cursor !== null && cursor !== void 0 ? (0, drizzle_orm.gt)(offers.hash, cursor) : void 0, maker !== void 0 ? (0, drizzle_orm.eq)(offers.groupMaker, maker.toLowerCase()) : void 0)).orderBy((0, drizzle_orm.asc)(offers.hash)).limit(limit)).map((row) => {
8148
+ const receiverIfMakerIsSeller = (row.receiverIfMakerIsSeller ?? row.maker).toLowerCase();
7674
8149
  return {
7675
8150
  hash: row.hash,
7676
8151
  maker: row.maker,
7677
8152
  assets: BigInt(row.assets),
7678
8153
  obligationUnits: BigInt(row.obligationUnits),
7679
8154
  obligationShares: BigInt(row.obligationShares),
7680
- price: BigInt(row.price),
8155
+ tick: row.tick,
7681
8156
  maturity: from$16(row.maturity),
7682
8157
  expiry: row.expiry,
7683
8158
  start: row.start,
@@ -7695,6 +8170,7 @@ function create$15(config) {
7695
8170
  address: row.callbackAddress,
7696
8171
  data: row.callbackData
7697
8172
  },
8173
+ receiverIfMakerIsSeller,
7698
8174
  consumed: 0n,
7699
8175
  available: 0n,
7700
8176
  takeable: 0n,
@@ -7719,64 +8195,30 @@ function create$15(config) {
7719
8195
  }
7720
8196
  throw new Error("Invalid parameters");
7721
8197
  },
7722
- getObligations: async (parameters) => {
7723
- const { ids, chainId: chainIds, loanToken: loanTokens, collateralToken: collateralTokens, maturity: maturities, cursor, limit = DEFAULT_LIMIT$3 } = parameters ?? {};
7724
- const now$1 = now();
7725
- const loanTokenFilter = loanTokens !== void 0 && loanTokens.length > 0 ? drizzle_orm.sql`(${drizzle_orm.sql.join(loanTokens.map((token) => drizzle_orm.sql`LOWER(${obligations.loanToken}) = ${token.toLowerCase()}`), drizzle_orm.sql` OR `)})` : void 0;
7726
- const collateralFilter = collateralTokens !== void 0 && collateralTokens.length > 0 ? drizzle_orm.sql`EXISTS (
7727
- SELECT 1 FROM ${obligationCollateralsV2} oc
7728
- WHERE oc.obligation_id = ${obligations.obligationId}
7729
- AND (${drizzle_orm.sql.join(collateralTokens.map((token) => drizzle_orm.sql`LOWER(oc.asset) = ${token.toLowerCase()}`), drizzle_orm.sql` OR `)})
7730
- )` : void 0;
7731
- const result = await db.select({
7732
- obligationId: obligations.obligationId,
7733
- chainId: obligations.chainId,
7734
- loanToken: obligations.loanToken,
7735
- collaterals: drizzle_orm.sql`ARRAY_AGG(jsonb_build_object('asset', ${obligationCollateralsV2.asset}, 'oracle', ${oracles$1.address}, 'lltv', ${obligationCollateralsV2.lltv}))`.as("collaterals"),
7736
- maturity: obligations.maturity
7737
- }).from(obligations).innerJoin(obligationCollateralsV2, (0, drizzle_orm.eq)(obligations.obligationId, obligationCollateralsV2.obligationId)).innerJoin(oracles$1, drizzle_orm.sql`${obligationCollateralsV2.oracleChainId} = ${oracles$1.chainId}
7738
- AND ${obligationCollateralsV2.oracleAddress} = ${oracles$1.address}`).groupBy(obligations.obligationId).where((0, drizzle_orm.and)(cursor !== null && cursor !== void 0 ? (0, drizzle_orm.gt)(obligations.obligationId, cursor) : drizzle_orm.sql`true`, ids !== void 0 && ids.length > 0 ? (0, drizzle_orm.inArray)(obligations.obligationId, ids) : void 0, chainIds !== void 0 && chainIds.length > 0 ? (0, drizzle_orm.inArray)(obligations.chainId, chainIds) : void 0, loanTokenFilter, maturities !== void 0 && maturities.length > 0 ? (0, drizzle_orm.inArray)(obligations.maturity, maturities) : (0, drizzle_orm.gte)(obligations.maturity, now$1), collateralFilter)).orderBy((0, drizzle_orm.asc)(obligations.obligationId)).limit(limit);
7739
- const items = [];
7740
- for (const row of result) items.push(from$15({
7741
- chainId: row.chainId,
7742
- loanToken: row.loanToken,
7743
- collaterals: row.collaterals.sort((a, b) => a.asset.localeCompare(b.asset)).map((c) => from$17({
7744
- asset: c.asset,
7745
- oracle: c.oracle,
7746
- lltv: from$18(BigInt(c.lltv))
7747
- })),
7748
- maturity: row.maturity
7749
- }));
7750
- const returnedItems = Array.from(items.values());
7751
- return {
7752
- obligations: returnedItems,
7753
- nextCursor: returnedItems.length === limit && returnedItems.length > 0 ? result[result.length - 1].obligationId : null
7754
- };
7755
- },
7756
8198
  getQuotes: async (parameters) => {
7757
8199
  const { obligationIds } = parameters;
7758
8200
  if (obligationIds.length === 0) return [];
7759
8201
  const now$2 = now();
7760
8202
  const query = ({ side }) => db.selectDistinctOn([offers.obligationId], {
7761
8203
  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`);
8204
+ tick: offers.tick
8205
+ }).from(offers).innerJoin(groups, (0, drizzle_orm.and)((0, drizzle_orm.eq)(offers.groupChainId, groups.chainId), (0, drizzle_orm.eq)(offers.groupMaker, groups.maker), (0, drizzle_orm.eq)(offers.group, groups.group))).leftJoin(validations, (0, drizzle_orm.eq)(offers.hash, validations.offerHash)).leftJoin(status, (0, drizzle_orm.eq)(validations.statusId, status.id)).where((0, drizzle_orm.and)((0, drizzle_orm.inArray)(offers.obligationId, obligationIds), (0, drizzle_orm.eq)(offers.buy, side === "buy"), (0, drizzle_orm.gte)(offers.expiry, now$2), (0, drizzle_orm.gte)(offers.maturity, now$2), (0, drizzle_orm.lte)(offers.start, now$2), drizzle_orm.sql`(${status.code} IS NULL OR ${status.code} = ${Status.VALID})`)).orderBy(offers.obligationId, side === "buy" ? drizzle_orm.sql`${offers.tick} ASC` : drizzle_orm.sql`${offers.tick} DESC`);
7764
8206
  const [bestBuys, bestSells] = await Promise.all([query({ side: "buy" }), query({ side: "sell" })]);
7765
8207
  const quotes = /* @__PURE__ */ new Map();
7766
8208
  for (const row of bestSells) quotes.set(row.obligationId, {
7767
- ask: { price: BigInt(row.price) },
7768
- bid: { price: 0n }
8209
+ ask: { tick: row.tick },
8210
+ bid: { tick: null }
7769
8211
  });
7770
8212
  for (const row of bestBuys) {
7771
8213
  const quote = quotes.get(row.obligationId);
7772
8214
  if (!quote) {
7773
8215
  quotes.set(row.obligationId, {
7774
- ask: { price: 0n },
7775
- bid: { price: BigInt(row.price) }
8216
+ ask: { tick: null },
8217
+ bid: { tick: row.tick }
7776
8218
  });
7777
8219
  continue;
7778
8220
  }
7779
- quote.bid = { price: BigInt(row.price) };
8221
+ quote.bid = { tick: row.tick };
7780
8222
  }
7781
8223
  return Array.from(quotes.entries()).map(([id, quote]) => {
7782
8224
  return from$11({
@@ -7876,7 +8318,7 @@ async function getOffersQuery(db, parameters) {
7876
8318
  obligationUnits: offers.obligationUnits,
7877
8319
  obligationShares: offers.obligationShares,
7878
8320
  consumed: groups.consumed,
7879
- price: offers.price,
8321
+ tick: offers.tick,
7880
8322
  maturity: offers.maturity,
7881
8323
  expiry: offers.expiry,
7882
8324
  start: offers.start,
@@ -7887,6 +8329,7 @@ async function getOffersQuery(db, parameters) {
7887
8329
  loanToken: obligations.loanToken,
7888
8330
  callbackAddress: offers.callbackAddress,
7889
8331
  callbackData: offers.callbackData,
8332
+ receiverIfMakerIsSeller: offers.receiverIfMakerIsSeller,
7890
8333
  collaterals: collateralsLateral.collaterals,
7891
8334
  blockNumber: offers.blockNumber,
7892
8335
  available: drizzle_orm.sql`${availableExpr}::numeric`.as("available"),
@@ -7908,13 +8351,14 @@ async function getOffersQuery(db, parameters) {
7908
8351
  )
7909
8352
  END
7910
8353
  ) > 0` : void 0)).orderBy((0, drizzle_orm.asc)(offers.hash)).limit(limit)).map((row) => {
8354
+ const receiverIfMakerIsSeller = (row.receiverIfMakerIsSeller ?? row.maker).toLowerCase();
7911
8355
  return {
7912
8356
  hash: row.hash,
7913
8357
  maker: row.maker,
7914
8358
  assets: BigInt(row.assets),
7915
8359
  obligationUnits: BigInt(row.obligationUnits),
7916
8360
  obligationShares: BigInt(row.obligationShares),
7917
- price: BigInt(row.price),
8361
+ tick: row.tick,
7918
8362
  maturity: from$16(row.maturity),
7919
8363
  expiry: row.expiry,
7920
8364
  start: row.start,
@@ -7932,6 +8376,7 @@ async function getOffersQuery(db, parameters) {
7932
8376
  address: row.callbackAddress,
7933
8377
  data: row.callbackData
7934
8378
  },
8379
+ receiverIfMakerIsSeller,
7935
8380
  consumed: BigInt(row.consumed),
7936
8381
  available: BigInt(String(row.available ?? "0").split(".")[0] ?? "0"),
7937
8382
  takeable: BigInt(String(row.takeable ?? "0").split(".")[0] ?? "0"),
@@ -8022,7 +8467,7 @@ async function getUserPositions(queryParameters, db) {
8022
8467
  async function validateOffers(body, gatekeeper) {
8023
8468
  const logger = getLogger();
8024
8469
  const result = safeParse("validate_offers", body, (issue) => issue.message);
8025
- if (!result.success) return failure(new BadRequestError(result.error.issues[0]?.message ?? "Invalid request body"));
8470
+ if (!result.success) return failure(new BadRequestError$1(result.error.issues[0]?.message ?? "Invalid request body"));
8026
8471
  const { offers: rawOffers } = result.data;
8027
8472
  const parsedOffers = [];
8028
8473
  const offerIndexByHash = /* @__PURE__ */ new Map();
@@ -8038,7 +8483,7 @@ async function validateOffers(body, gatekeeper) {
8038
8483
  } catch (err) {
8039
8484
  let message = err instanceof Error ? err.message : String(err);
8040
8485
  if (err instanceof InvalidOfferError) message = err.formattedMessage;
8041
- return failure(new BadRequestError(`Offer at index ${i} failed to parse: ${message}`));
8486
+ return failure(new BadRequestError$1(`Offer at index ${i} failed to parse: ${message}`));
8042
8487
  }
8043
8488
  }
8044
8489
  try {
@@ -8313,7 +8758,7 @@ async function getOffers(apiClient, parameters) {
8313
8758
  assets: offerData.assets,
8314
8759
  obligation_units: offerData.obligation_units,
8315
8760
  obligation_shares: offerData.obligation_shares,
8316
- price: offerData.price,
8761
+ tick: offerData.tick,
8317
8762
  maturity: from$16(offerData.obligation.maturity),
8318
8763
  expiry: offerData.expiry,
8319
8764
  start: offerData.start,
@@ -8330,7 +8775,8 @@ async function getOffers(apiClient, parameters) {
8330
8775
  callback: {
8331
8776
  address: offerData.callback,
8332
8777
  data: offerData.callback_data
8333
- }
8778
+ },
8779
+ receiver_if_maker_is_seller: offerData.receiver_if_maker_is_seller
8334
8780
  }),
8335
8781
  hash: item.offer_hash,
8336
8782
  consumed: BigInt(item.consumed),
@@ -8347,13 +8793,15 @@ async function getOffers(apiClient, parameters) {
8347
8793
  };
8348
8794
  }
8349
8795
  async function getObligations(apiClient, parameters) {
8796
+ const sort = parameters?.sort?.length ? parameters.sort.join(",") : void 0;
8350
8797
  const { data, error, response } = await apiClient.GET("/v1/obligations", { params: { query: {
8351
8798
  cursor: parameters?.cursor,
8352
8799
  limit: parameters?.limit,
8353
8800
  chains: parameters?.chainIds,
8354
8801
  loan_tokens: parameters?.loanTokens,
8355
8802
  collateral_tokens: parameters?.collateralTokens,
8356
- maturities: parameters?.maturities
8803
+ maturities: parameters?.maturities,
8804
+ sort
8357
8805
  } } });
8358
8806
  if (error !== void 0) {
8359
8807
  switch (response.status) {
@@ -8377,10 +8825,10 @@ async function getObligations(apiClient, parameters) {
8377
8825
  const { obligationId: _, ...returned } = {
8378
8826
  id: () => id(obligation),
8379
8827
  ...obligation,
8380
- ...fromSnakeCase({
8381
- obligation_id: item.id,
8382
- ask: item.ask,
8383
- bid: item.bid
8828
+ ...from$11({
8829
+ obligationId: item.id,
8830
+ ask: { tick: item.ask.tick },
8831
+ bid: { tick: item.bid.tick }
8384
8832
  })
8385
8833
  };
8386
8834
  return returned;
@@ -8672,6 +9120,7 @@ function create$12(config) {
8672
9120
  return {
8673
9121
  get: async (parameters) => {
8674
9122
  const { side, obligationId, cursor: cursorString, limit = DEFAULT_LIMIT$2 } = parameters;
9123
+ const tickSortDirection = side === "sell" ? "asc" : "desc";
8675
9124
  const inputCursor = LevelCursor.decode(cursorString, logger);
8676
9125
  if (cursorString != null && inputCursor === null) return {
8677
9126
  levels: [],
@@ -8686,23 +9135,23 @@ function create$12(config) {
8686
9135
  cursor: inputCursor?.offersCursor ?? void 0,
8687
9136
  limit: fetchLimit
8688
9137
  });
8689
- const priceMap = /* @__PURE__ */ new Map();
9138
+ const tickMap = /* @__PURE__ */ new Map();
8690
9139
  for (const row of rows) {
8691
- const priceKey = row.price.toString();
8692
- const existing = priceMap.get(priceKey);
9140
+ const existing = tickMap.get(row.tick);
8693
9141
  if (existing) {
8694
9142
  existing.assets += row.takeable;
8695
9143
  existing.count += 1;
8696
- } else priceMap.set(priceKey, {
9144
+ } else tickMap.set(row.tick, {
8697
9145
  assets: row.takeable,
8698
9146
  count: 1
8699
9147
  });
8700
9148
  }
8701
- const levels = Array.from(priceMap.entries()).map(([price, data]) => ({
8702
- price: BigInt(price),
8703
- assets: data.assets,
8704
- count: data.count
9149
+ const levels = Array.from(tickMap.entries()).map(([tick, level]) => ({
9150
+ tick,
9151
+ assets: level.assets,
9152
+ count: level.count
8705
9153
  }));
9154
+ levels.sort((a, b) => tickSortDirection === "asc" ? a.tick - b.tick : b.tick - a.tick);
8706
9155
  const paginatedLevels = levels.slice(0, limit);
8707
9156
  const hasMore = levels.length > limit || offersNextCursor !== null;
8708
9157
  const lastLevel = paginatedLevels[paginatedLevels.length - 1];
@@ -8748,14 +9197,14 @@ async function _getOffers(db, params) {
8748
9197
  AND (s.code IS NULL OR s.code = ${Status.VALID})
8749
9198
  ORDER BY
8750
9199
  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
9200
+ o.tick ${priceSortDirection === "asc" ? drizzle_orm.sql`ASC` : drizzle_orm.sql`DESC`}, o.block_number ASC, o.assets DESC, o.hash ASC
8752
9201
  ),
8753
9202
  enriched AS (
8754
9203
  SELECT
8755
9204
  w.*,
8756
9205
  g.consumed, g.chain_id, obl.loan_token,
8757
9206
  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,
9207
+ THEN w.tick ELSE -w.tick END AS tick_norm,
8759
9208
  w.block_number AS block_norm,
8760
9209
  -w.assets AS assets_norm,
8761
9210
  w.hash AS hash_norm
@@ -8772,33 +9221,35 @@ async function _getOffers(db, params) {
8772
9221
  FROM enriched e
8773
9222
  ${cursor != null ? drizzle_orm.sql`
8774
9223
  WHERE
8775
- (e.price_norm, e.block_norm, e.assets_norm, e.hash_norm)
9224
+ (e.tick_norm, e.block_norm, e.assets_norm, e.hash_norm)
8776
9225
  > (
8777
9226
  CASE WHEN ${priceSortDirection === "asc" ? drizzle_orm.sql`TRUE` : drizzle_orm.sql`FALSE`}
8778
- THEN ${cursor.price}::numeric ELSE -${cursor.price}::numeric END,
9227
+ THEN ${cursor.tick}::integer ELSE -${cursor.tick}::integer END,
8779
9228
  ${cursor.blockNumber},
8780
9229
  -${cursor.assets}::numeric,
8781
9230
  ${cursor.hash}
8782
9231
  )` : 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
9232
+ 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
9233
  LIMIT ${limit}
8785
9234
  ),
8786
- -- Compute sum of offsets per position
9235
+ -- Compute sum of offsets per position and obligation
8787
9236
  position_offsets AS (
8788
9237
  SELECT
8789
9238
  chain_id,
8790
9239
  "user",
8791
9240
  contract,
9241
+ obligation_id,
8792
9242
  SUM(value::numeric) AS total_offset
8793
9243
  FROM ${offsets}
8794
- GROUP BY chain_id, "user", contract
9244
+ GROUP BY chain_id, "user", contract, obligation_id
8795
9245
  ),
8796
- -- Compute position_consumed: sum of consumed from all groups with lots on each position (converted to lot terms)
9246
+ -- Compute position_consumed: sum of consumed from all groups with lots on each position+obligation (converted to lot terms)
8797
9247
  position_consumed AS (
8798
9248
  SELECT
8799
9249
  l.chain_id,
8800
9250
  l.contract,
8801
9251
  l."user",
9252
+ l.obligation_id,
8802
9253
  SUM(
8803
9254
  CASE
8804
9255
  WHEN wo.assets::numeric > 0
@@ -8815,7 +9266,7 @@ async function _getOffers(db, params) {
8815
9266
  ON wo.group_chain_id = g.chain_id
8816
9267
  AND LOWER(wo.group_maker) = LOWER(g.maker)
8817
9268
  AND wo.group_group = g."group"
8818
- GROUP BY l.chain_id, l.contract, l."user"
9269
+ GROUP BY l.chain_id, l.contract, l."user", l.obligation_id
8819
9270
  ),
8820
9271
  -- Compute callback contributions with lot balance
8821
9272
  callback_contributions AS (
@@ -8823,7 +9274,7 @@ async function _getOffers(db, params) {
8823
9274
  p.hash,
8824
9275
  p.obligation_id,
8825
9276
  p.assets,
8826
- p.price,
9277
+ p.tick,
8827
9278
  p.obligation_units,
8828
9279
  p.obligation_shares,
8829
9280
  p.maturity,
@@ -8833,6 +9284,7 @@ async function _getOffers(db, params) {
8833
9284
  p.buy,
8834
9285
  p.callback_address,
8835
9286
  p.callback_data,
9287
+ p.receiver_if_maker_is_seller,
8836
9288
  p.block_number,
8837
9289
  p.group_chain_id,
8838
9290
  p.group_maker,
@@ -8868,6 +9320,7 @@ async function _getOffers(db, params) {
8868
9320
  AND LOWER(l.contract) = LOWER(c.position_contract)
8869
9321
  AND LOWER(l."user") = LOWER(c.position_user)
8870
9322
  AND l."group" = p.group_group
9323
+ AND l.obligation_id = p.obligation_id
8871
9324
  LEFT JOIN ${positions} pos
8872
9325
  ON pos.chain_id = c.position_chain_id
8873
9326
  AND LOWER(pos.contract) = LOWER(c.position_contract)
@@ -8876,10 +9329,12 @@ async function _getOffers(db, params) {
8876
9329
  ON pos_offsets.chain_id = c.position_chain_id
8877
9330
  AND LOWER(pos_offsets.contract) = LOWER(c.position_contract)
8878
9331
  AND LOWER(pos_offsets."user") = LOWER(c.position_user)
9332
+ AND pos_offsets.obligation_id = p.obligation_id
8879
9333
  LEFT JOIN position_consumed pc
8880
9334
  ON pc.chain_id = c.position_chain_id
8881
9335
  AND LOWER(pc.contract) = LOWER(c.position_contract)
8882
9336
  AND LOWER(pc."user") = LOWER(c.position_user)
9337
+ AND pc.obligation_id = p.obligation_id
8883
9338
  ),
8884
9339
  -- Compute contribution per callback in loan terms (loan token only — collateral positions are not indexed)
8885
9340
  callback_loan_contribution AS (
@@ -8897,7 +9352,7 @@ async function _getOffers(db, params) {
8897
9352
  hash,
8898
9353
  obligation_id,
8899
9354
  assets,
8900
- price,
9355
+ tick,
8901
9356
  obligation_units,
8902
9357
  obligation_shares,
8903
9358
  maturity,
@@ -8907,6 +9362,7 @@ async function _getOffers(db, params) {
8907
9362
  buy,
8908
9363
  callback_address,
8909
9364
  callback_data,
9365
+ receiver_if_maker_is_seller,
8910
9366
  block_number,
8911
9367
  group_chain_id,
8912
9368
  group_maker,
@@ -8923,16 +9379,17 @@ async function _getOffers(db, params) {
8923
9379
  WHERE clc.callback_id IS NOT NULL
8924
9380
  ORDER BY clc.hash, clc.position_chain_id, clc.position_contract, clc.position_user, clc.contribution_in_loan DESC
8925
9381
  ) deduped
8926
- GROUP BY hash, obligation_id, assets, price, obligation_units, obligation_shares, maturity, expiry, start, group_group, buy,
8927
- callback_address, callback_data, block_number, group_chain_id, group_maker,
8928
- consumed, chain_id, loan_token, session
9382
+ GROUP BY hash, obligation_id, assets, tick, obligation_units, obligation_shares, maturity, expiry, start, group_group, buy,
9383
+ callback_address, callback_data, block_number, group_chain_id, group_maker,
9384
+ consumed, chain_id, loan_token, session, receiver_if_maker_is_seller
8929
9385
  UNION ALL
8930
9386
  -- Sell offers without callbacks: collateral positions not indexed, takeable = assets - consumed
8931
9387
  SELECT
8932
- p.hash, p.obligation_id, p.assets, p.price,
9388
+ p.hash, p.obligation_id, p.assets, p.tick,
8933
9389
  p.obligation_units, p.obligation_shares,
8934
9390
  p.maturity, p.expiry, p.start, p.group_group,
8935
9391
  p.buy, p.callback_address, p.callback_data,
9392
+ p.receiver_if_maker_is_seller,
8936
9393
  p.block_number, p.group_chain_id, p.group_maker,
8937
9394
  p.consumed, p.chain_id, p.loan_token, p.session,
8938
9395
  0 AS total_available
@@ -8951,7 +9408,7 @@ async function _getOffers(db, params) {
8951
9408
  oc.obligation_units,
8952
9409
  oc.obligation_shares,
8953
9410
  oc.consumed,
8954
- oc.price,
9411
+ oc.tick,
8955
9412
  oc.maturity,
8956
9413
  oc.expiry,
8957
9414
  oc.start,
@@ -8961,6 +9418,7 @@ async function _getOffers(db, params) {
8961
9418
  oc.loan_token,
8962
9419
  oc.callback_address,
8963
9420
  oc.callback_data,
9421
+ oc.receiver_if_maker_is_seller,
8964
9422
  oc.block_number,
8965
9423
  oc.session,
8966
9424
  COALESCE(oc.total_available, 0) AS available,
@@ -8983,20 +9441,21 @@ async function _getOffers(db, params) {
8983
9441
  ))
8984
9442
  END > 0
8985
9443
  ORDER BY
8986
- oc.price::numeric ${priceSortDirection === "asc" ? drizzle_orm.sql`ASC` : drizzle_orm.sql`DESC`},
9444
+ oc.tick ${priceSortDirection === "asc" ? drizzle_orm.sql`ASC` : drizzle_orm.sql`DESC`},
8987
9445
  oc.block_number ASC,
8988
9446
  oc.assets DESC,
8989
9447
  oc.hash ASC;
8990
9448
  `);
8991
9449
  return {
8992
9450
  rows: raw.rows.map((row) => {
9451
+ const receiverIfMakerIsSeller = (row.receiver_if_maker_is_seller ?? row.group_maker).toLowerCase();
8993
9452
  return {
8994
9453
  hash: row.hash,
8995
9454
  maker: row.group_maker,
8996
9455
  assets: BigInt(row.assets),
8997
9456
  obligationUnits: BigInt(row.obligation_units ?? 0),
8998
9457
  obligationShares: BigInt(row.obligation_shares ?? 0),
8999
- price: BigInt(row.price),
9458
+ tick: row.tick,
9000
9459
  maturity: row.maturity,
9001
9460
  expiry: row.expiry,
9002
9461
  start: row.start,
@@ -9014,6 +9473,7 @@ async function _getOffers(db, params) {
9014
9473
  address: row.callback_address,
9015
9474
  data: row.callback_data
9016
9475
  },
9476
+ receiverIfMakerIsSeller,
9017
9477
  blockNumber: row.block_number,
9018
9478
  consumed: BigInt(row.consumed ?? 0),
9019
9479
  available: BigInt(String(row.available ?? "0").split(".")[0] ?? "0"),
@@ -9028,7 +9488,7 @@ let Cursor;
9028
9488
  function encode(row, totalReturned, now, side) {
9029
9489
  return Buffer.from(JSON.stringify({
9030
9490
  side,
9031
- price: row.price.toString(),
9491
+ tick: row.tick,
9032
9492
  blockNumber: row.blockNumber,
9033
9493
  assets: row.assets.toString(),
9034
9494
  hash: row.hash,
@@ -9039,10 +9499,9 @@ let Cursor;
9039
9499
  _Cursor.encode = encode;
9040
9500
  function decode(cursorString, logger) {
9041
9501
  if (cursorString == null) return null;
9042
- const isNumericString = (value) => typeof value === "string" && /^-?\d+$/.test(value);
9043
9502
  try {
9044
9503
  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;
9504
+ 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
9505
  throw new Error("Invalid cursor");
9047
9506
  } catch {
9048
9507
  logger.error({
@@ -9060,7 +9519,7 @@ let LevelCursor;
9060
9519
  function encode(lastLevel, offersCursor, side, now) {
9061
9520
  return Buffer.from(JSON.stringify({
9062
9521
  side,
9063
- lastPrice: lastLevel.price.toString(),
9522
+ lastTick: lastLevel.tick,
9064
9523
  now,
9065
9524
  offersCursor
9066
9525
  })).toString("base64url");
@@ -9068,10 +9527,9 @@ let LevelCursor;
9068
9527
  _LevelCursor.encode = encode;
9069
9528
  function decode(cursorString, logger) {
9070
9529
  if (cursorString == null) return null;
9071
- const isNumericString = (value) => typeof value === "string" && /^-?\d+$/.test(value);
9072
9530
  try {
9073
9531
  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;
9532
+ 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
9533
  throw new Error("Invalid book cursor");
9076
9534
  } catch {
9077
9535
  logger.error({
@@ -9214,31 +9672,33 @@ function create$9(db) {
9214
9672
  function create$8(db) {
9215
9673
  return {
9216
9674
  get: async (parameters) => {
9217
- const { chainId, user, contract, group } = parameters ?? {};
9675
+ const { chainId, user, contract, group, obligationId } = parameters ?? {};
9218
9676
  const conditions = [];
9219
9677
  if (chainId !== void 0) conditions.push((0, drizzle_orm.eq)(lots.chainId, chainId));
9220
9678
  if (user !== void 0) conditions.push((0, drizzle_orm.eq)(lots.user, user.toLowerCase()));
9221
9679
  if (contract !== void 0) conditions.push((0, drizzle_orm.eq)(lots.contract, contract.toLowerCase()));
9222
9680
  if (group !== void 0) conditions.push((0, drizzle_orm.eq)(lots.group, group));
9681
+ if (obligationId !== void 0) conditions.push((0, drizzle_orm.eq)(lots.obligationId, obligationId));
9223
9682
  return (await db.select().from(lots).where(conditions.length > 0 ? (0, drizzle_orm.and)(...conditions) : void 0)).map((row) => ({
9224
9683
  chainId: row.chainId,
9225
9684
  user: row.user,
9226
9685
  contract: row.contract,
9227
9686
  group: row.group,
9687
+ obligationId: row.obligationId,
9228
9688
  lower: BigInt(row.lower),
9229
9689
  upper: BigInt(row.upper)
9230
9690
  }));
9231
9691
  },
9232
9692
  create: async (parameters) => {
9233
9693
  if (parameters.length === 0) return;
9234
- const lotsByPositionGroup = /* @__PURE__ */ new Map();
9694
+ const lotsByKey = /* @__PURE__ */ new Map();
9235
9695
  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);
9696
+ const key = `${offer.positionChainId}-${offer.positionContract}-${offer.positionUser}-${offer.group}-${offer.obligationId}`.toLowerCase();
9697
+ const existing = lotsByKey.get(key);
9698
+ if (!existing || offer.size > existing.size) lotsByKey.set(key, offer);
9239
9699
  }
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())));
9700
+ for (const offer of lotsByKey.values()) if ((await db.select().from(lots).where((0, drizzle_orm.and)((0, drizzle_orm.eq)(lots.chainId, offer.positionChainId), (0, drizzle_orm.eq)(lots.contract, offer.positionContract.toLowerCase()), (0, drizzle_orm.eq)(lots.user, offer.positionUser.toLowerCase()), (0, drizzle_orm.eq)(lots.group, offer.group.toLowerCase()), (0, drizzle_orm.eq)(lots.obligationId, offer.obligationId.toLowerCase()))).limit(1)).length === 0) {
9701
+ 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
9702
  const newLower = BigInt(maxUpperResult[0]?.maxUpper ?? "0");
9243
9703
  const newUpper = newLower + offer.size;
9244
9704
  await db.insert(lots).values({
@@ -9246,6 +9706,7 @@ function create$8(db) {
9246
9706
  user: offer.positionUser.toLowerCase(),
9247
9707
  contract: offer.positionContract.toLowerCase(),
9248
9708
  group: offer.group.toLowerCase(),
9709
+ obligationId: offer.obligationId.toLowerCase(),
9249
9710
  lower: newLower.toString(),
9250
9711
  upper: newUpper.toString()
9251
9712
  });
@@ -9262,55 +9723,79 @@ function create$8(db) {
9262
9723
  * @returns Obligations domain. {@link ObligationsDomain}
9263
9724
  */
9264
9725
  function create$7(db) {
9265
- return { create: async (obligations$1) => {
9266
- if (obligations$1.length === 0) return;
9267
- const obligationsById = /* @__PURE__ */ new Map();
9268
- for (const obligation of obligations$1) {
9269
- const id$1 = id(obligation).toLowerCase();
9270
- if (!obligationsById.get(id$1)) obligationsById.set(id$1, obligation);
9271
- }
9272
- try {
9273
- await db.transaction(async (dbTx) => {
9274
- const obligationRows = obligations$1.map((obligation) => ({
9275
- obligationId: id(obligation),
9276
- chainId: obligation.chainId,
9277
- loanToken: obligation.loanToken.toLowerCase(),
9278
- maturity: obligation.maturity
9279
- }));
9280
- for (const batch of batch$1(obligationRows, DEFAULT_BATCH_SIZE$1)) await dbTx.insert(obligations).values(batch).onConflictDoNothing();
9281
- const collateralRows = obligations$1.flatMap((obligation) => {
9282
- return obligation.collaterals.map((collateral) => ({
9726
+ return {
9727
+ get: async (parameters) => {
9728
+ const chainIds = parameters?.chainId;
9729
+ const now$1 = now();
9730
+ return (await db.select({
9731
+ chainId: obligations.chainId,
9732
+ loanToken: obligations.loanToken,
9733
+ collaterals: drizzle_orm.sql`ARRAY_AGG(jsonb_build_object('asset', ${obligationCollateralsV2.asset}, 'oracle', ${oracles$1.address}, 'lltv', ${obligationCollateralsV2.lltv}))`.as("collaterals"),
9734
+ maturity: obligations.maturity
9735
+ }).from(obligations).innerJoin(obligationCollateralsV2, (0, drizzle_orm.eq)(obligations.obligationId, obligationCollateralsV2.obligationId)).innerJoin(oracles$1, drizzle_orm.sql`${obligationCollateralsV2.oracleChainId} = ${oracles$1.chainId}
9736
+ AND ${obligationCollateralsV2.oracleAddress} = ${oracles$1.address}`).groupBy(obligations.obligationId).where((0, drizzle_orm.and)(chainIds !== void 0 && chainIds.length > 0 ? (0, drizzle_orm.inArray)(obligations.chainId, chainIds) : void 0, (0, drizzle_orm.gte)(obligations.maturity, now$1))).orderBy((0, drizzle_orm.asc)(obligations.obligationId))).map((row) => from$15({
9737
+ chainId: row.chainId,
9738
+ loanToken: row.loanToken,
9739
+ collaterals: row.collaterals.sort((left, right) => left.asset.localeCompare(right.asset)).map((collateral) => from$17({
9740
+ asset: collateral.asset,
9741
+ oracle: collateral.oracle,
9742
+ lltv: from$18(BigInt(collateral.lltv))
9743
+ })),
9744
+ maturity: row.maturity
9745
+ }));
9746
+ },
9747
+ create: async (obligations$1) => {
9748
+ if (obligations$1.length === 0) return;
9749
+ const obligationsById = /* @__PURE__ */ new Map();
9750
+ for (const obligation of obligations$1) {
9751
+ const id$1 = id(obligation).toLowerCase();
9752
+ if (!obligationsById.get(id$1)) obligationsById.set(id$1, obligation);
9753
+ }
9754
+ try {
9755
+ await db.transaction(async (dbTx) => {
9756
+ const obligationRows = obligations$1.map((obligation) => ({
9283
9757
  obligationId: id(obligation),
9284
- asset: collateral.asset.toLowerCase(),
9285
- oracleChainId: obligation.chainId,
9286
- oracleAddress: collateral.oracle.toLowerCase(),
9287
- lltv: collateral.lltv
9758
+ chainId: obligation.chainId,
9759
+ loanToken: obligation.loanToken.toLowerCase(),
9760
+ maturity: obligation.maturity
9288
9761
  }));
9762
+ for (const batch of batch$1(obligationRows, DEFAULT_BATCH_SIZE$1)) await dbTx.insert(obligations).values(batch).onConflictDoNothing();
9763
+ const collateralRows = obligations$1.flatMap((obligation) => {
9764
+ return obligation.collaterals.map((collateral) => ({
9765
+ obligationId: id(obligation),
9766
+ asset: collateral.asset.toLowerCase(),
9767
+ oracleChainId: obligation.chainId,
9768
+ oracleAddress: collateral.oracle.toLowerCase(),
9769
+ lltv: collateral.lltv
9770
+ }));
9771
+ });
9772
+ for (const batch of batch$1(collateralRows, DEFAULT_BATCH_SIZE$1)) await dbTx.insert(obligationCollateralsV2).values(batch).onConflictDoNothing();
9289
9773
  });
9290
- for (const batch of batch$1(collateralRows, DEFAULT_BATCH_SIZE$1)) await dbTx.insert(obligationCollateralsV2).values(batch).onConflictDoNothing();
9291
- });
9292
- } catch (err) {
9293
- const error = err instanceof Error ? err : new Error(String(err));
9294
- throw new Error("Obligations.create failed. Ensure oracles exist before inserting obligations.", { cause: error });
9774
+ } catch (err) {
9775
+ const error = err instanceof Error ? err : new Error(String(err));
9776
+ throw new Error("Obligations.create failed. Ensure oracles exist before inserting obligations.", { cause: error });
9777
+ }
9295
9778
  }
9296
- } };
9779
+ };
9297
9780
  }
9298
9781
 
9299
9782
  //#endregion
9300
9783
  //#region src/database/domains/Offsets.ts
9301
9784
  function create$6(db) {
9302
9785
  return { get: async (parameters) => {
9303
- const { chainId, user, contract, group } = parameters ?? {};
9786
+ const { chainId, user, contract, group, obligationId } = parameters ?? {};
9304
9787
  const conditions = [];
9305
9788
  if (chainId !== void 0) conditions.push((0, drizzle_orm.eq)(offsets.chainId, chainId));
9306
9789
  if (user !== void 0) conditions.push((0, drizzle_orm.eq)(offsets.user, user.toLowerCase()));
9307
9790
  if (contract !== void 0) conditions.push((0, drizzle_orm.eq)(offsets.contract, contract.toLowerCase()));
9308
9791
  if (group !== void 0) conditions.push((0, drizzle_orm.eq)(offsets.group, group));
9792
+ if (obligationId !== void 0) conditions.push((0, drizzle_orm.eq)(offsets.obligationId, obligationId));
9309
9793
  return (await db.select().from(offsets).where(conditions.length > 0 ? (0, drizzle_orm.and)(...conditions) : void 0)).map((row) => ({
9310
9794
  chainId: row.chainId,
9311
9795
  user: row.user,
9312
9796
  contract: row.contract,
9313
9797
  group: row.group,
9798
+ obligationId: row.obligationId,
9314
9799
  value: BigInt(row.value)
9315
9800
  }));
9316
9801
  } };
@@ -9460,7 +9945,8 @@ const create$4 = (db) => {
9460
9945
  if (!parsed.chainId || !parsed.contract) throw new Error("Invalid cursor format");
9461
9946
  cursor = {
9462
9947
  chainId: parsed.chainId,
9463
- contract: parsed.contract
9948
+ contract: parsed.contract,
9949
+ obligationId: parsed.obligationId ?? null
9464
9950
  };
9465
9951
  }
9466
9952
  const raw = await db.execute(drizzle_orm.sql`
@@ -9469,16 +9955,18 @@ const create$4 = (db) => {
9469
9955
  chain_id,
9470
9956
  "user",
9471
9957
  contract,
9958
+ obligation_id,
9472
9959
  SUM(value::numeric) AS total_offset
9473
9960
  FROM ${offsets}
9474
9961
  WHERE LOWER("user") = LOWER(${user})
9475
- GROUP BY chain_id, "user", contract
9962
+ GROUP BY chain_id, "user", contract, obligation_id
9476
9963
  ),
9477
9964
  position_consumed AS (
9478
9965
  SELECT
9479
9966
  l.chain_id,
9480
9967
  l.contract,
9481
9968
  l."user",
9969
+ l.obligation_id,
9482
9970
  SUM(
9483
9971
  CASE
9484
9972
  WHEN offer_agg.assets > 0
@@ -9504,50 +9992,64 @@ const create$4 = (db) => {
9504
9992
  AND LOWER(offer_agg.group_maker) = LOWER(g.maker)
9505
9993
  AND offer_agg."group_group" = g."group"
9506
9994
  WHERE LOWER(l."user") = LOWER(${user})
9507
- GROUP BY l.chain_id, l.contract, l."user"
9995
+ GROUP BY l.chain_id, l.contract, l."user", l.obligation_id
9508
9996
  ),
9509
9997
  position_max_lot AS (
9510
9998
  SELECT
9511
9999
  chain_id,
9512
10000
  contract,
9513
10001
  "user",
10002
+ obligation_id,
9514
10003
  MAX(upper::numeric) AS max_upper
9515
10004
  FROM ${lots}
9516
10005
  WHERE LOWER("user") = LOWER(${user})
9517
- GROUP BY chain_id, contract, "user"
10006
+ GROUP BY chain_id, contract, "user", obligation_id
10007
+ ),
10008
+ per_obligation AS (
10009
+ SELECT
10010
+ pml.chain_id,
10011
+ pml.contract,
10012
+ pml."user",
10013
+ pml.obligation_id,
10014
+ GREATEST(0,
10015
+ COALESCE(pml.max_upper, 0)
10016
+ - COALESCE(po.total_offset, 0)
10017
+ - COALESCE(pc.consumed, 0)
10018
+ )::text AS reserved_balance
10019
+ FROM position_max_lot pml
10020
+ LEFT JOIN position_offsets po
10021
+ ON po.chain_id = pml.chain_id
10022
+ AND LOWER(po.contract) = LOWER(pml.contract)
10023
+ AND LOWER(po."user") = LOWER(pml."user")
10024
+ AND po.obligation_id = pml.obligation_id
10025
+ LEFT JOIN position_consumed pc
10026
+ ON pc.chain_id = pml.chain_id
10027
+ AND LOWER(pc.contract) = LOWER(pml.contract)
10028
+ AND LOWER(pc."user") = LOWER(pml."user")
10029
+ AND pc.obligation_id = pml.obligation_id
9518
10030
  )
9519
10031
  SELECT
9520
10032
  p.chain_id,
9521
10033
  p.contract,
9522
10034
  p."user",
9523
10035
  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
10036
+ po.obligation_id,
10037
+ COALESCE(po.reserved_balance, '0') AS reserved_balance
9529
10038
  FROM ${positions} p
9530
- LEFT JOIN position_offsets po
10039
+ LEFT JOIN per_obligation po
9531
10040
  ON po.chain_id = p.chain_id
9532
10041
  AND LOWER(po.contract) = LOWER(p.contract)
9533
10042
  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
10043
  WHERE LOWER(p."user") = LOWER(${user})
9543
10044
  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
10045
+ ${cursor !== null ? drizzle_orm.sql`AND (p.chain_id, p.contract, COALESCE(po.obligation_id, '')) > (${cursor.chainId}, ${cursor.contract}, ${cursor.obligationId ?? ""})` : drizzle_orm.sql``}
10046
+ ORDER BY p.chain_id ASC, p.contract ASC, po.obligation_id ASC NULLS FIRST
9546
10047
  LIMIT ${limit}
9547
10048
  `);
9548
10049
  const nextCursor = raw.rows.length === limit ? Buffer.from(JSON.stringify({
9549
10050
  chainId: raw.rows[raw.rows.length - 1].chain_id.toString(),
9550
- contract: raw.rows[raw.rows.length - 1].contract
10051
+ contract: raw.rows[raw.rows.length - 1].contract,
10052
+ obligationId: raw.rows[raw.rows.length - 1].obligation_id
9551
10053
  })).toString("base64url") : null;
9552
10054
  return {
9553
10055
  positions: raw.rows.map((row) => ({
@@ -9555,6 +10057,7 @@ const create$4 = (db) => {
9555
10057
  contract: row.contract,
9556
10058
  user: row.user,
9557
10059
  blockNumber: row.block_number,
10060
+ obligationId: row.obligation_id,
9558
10061
  reserved: BigInt(row.reserved_balance.split(".")[0] ?? "0")
9559
10062
  })),
9560
10063
  nextCursor
@@ -9833,7 +10336,10 @@ function create$1(db) {
9833
10336
 
9834
10337
  //#endregion
9835
10338
  //#region src/database/Database.ts
9836
- var Database_exports = /* @__PURE__ */ __exportAll({ connect: () => connect$1 });
10339
+ var Database_exports = /* @__PURE__ */ __exportAll({
10340
+ connect: () => connect$1,
10341
+ getSchemaNamesForMigration: () => getSchemaNamesForMigration
10342
+ });
9837
10343
  function createDomains(core, chainRegistry) {
9838
10344
  return {
9839
10345
  book: create$12({ db: core }),
@@ -9855,6 +10361,9 @@ function createDomains(core, chainRegistry) {
9855
10361
  transfers: create$3(core)
9856
10362
  };
9857
10363
  }
10364
+ function createReaders(core) {
10365
+ return { obligations: create$16({ db: core }) };
10366
+ }
9858
10367
  const AUGMENT_CACHE = /* @__PURE__ */ new WeakMap();
9859
10368
  function augmentWithDomains(base, chainRegistry) {
9860
10369
  const cached = AUGMENT_CACHE.get(base)?.get(chainRegistry);
@@ -9866,6 +10375,7 @@ function augmentWithDomains(base, chainRegistry) {
9866
10375
  });
9867
10376
  };
9868
10377
  const dms = createDomains(wrapped, chainRegistry);
10378
+ const readers = createReaders(wrapped);
9869
10379
  Object.defineProperties(wrapped, {
9870
10380
  book: {
9871
10381
  value: dms.book,
@@ -9922,6 +10432,10 @@ function augmentWithDomains(base, chainRegistry) {
9922
10432
  transfers: {
9923
10433
  value: dms.transfers,
9924
10434
  enumerable: true
10435
+ },
10436
+ readers: {
10437
+ value: readers,
10438
+ enumerable: true
9925
10439
  }
9926
10440
  });
9927
10441
  const chainRegistryMap = AUGMENT_CACHE.get(base);
@@ -9930,6 +10444,7 @@ function augmentWithDomains(base, chainRegistry) {
9930
10444
  return wrapped;
9931
10445
  }
9932
10446
  const InMemoryDbMap = /* @__PURE__ */ new Map();
10447
+ const LEGACY_SCHEMA_START_MINOR = 7;
9933
10448
  /**
9934
10449
  * Connect to the database.
9935
10450
  * @notice If no connection string is provided, an in-process PGLite database is created.
@@ -9984,9 +10499,26 @@ function applyMigrations(kind, driver) {
9984
10499
  async function preMigrate(driver) {
9985
10500
  const tracer = getTracer("db.preMigrate");
9986
10501
  await startActiveSpan(tracer, "db.preMigrate", async () => {
9987
- await driver.execute(`create schema if not exists "${VERSION}"`);
10502
+ const schemaNames = getSchemaNamesForMigration(VERSION);
10503
+ for (const schemaName of schemaNames) await driver.execute(`create schema if not exists "${schemaName}"`);
9988
10504
  });
9989
10505
  }
10506
+ /**
10507
+ * Build the list of router schemas that should exist before running migrations.
10508
+ * @param version - Current schema version (e.g. `router_v1.8`).
10509
+ * @returns Ordered schema names from `router_v1.7` to current, or just current if parsing fails.
10510
+ */
10511
+ function getSchemaNamesForMigration(version) {
10512
+ const parsed = /^router_v(?<major>\d+)\.(?<minor>\d+)$/.exec(version);
10513
+ if (!parsed?.groups?.major || !parsed.groups.minor) return [version];
10514
+ const major = Number.parseInt(parsed.groups.major, 10);
10515
+ const currentMinor = Number.parseInt(parsed.groups.minor, 10);
10516
+ if (!Number.isInteger(major) || !Number.isInteger(currentMinor)) return [version];
10517
+ if (currentMinor < LEGACY_SCHEMA_START_MINOR) return [version];
10518
+ const schemaNames = [];
10519
+ for (let minor = LEGACY_SCHEMA_START_MINOR; minor <= currentMinor; minor += 1) schemaNames.push(`router_v${major}.${minor}`);
10520
+ return schemaNames;
10521
+ }
9990
10522
  async function postMigrate(driver) {
9991
10523
  const tracer = getTracer("db.postMigrate");
9992
10524
  await startActiveSpan(tracer, "db.postMigrate", async () => {
@@ -10256,15 +10788,16 @@ async function postMigrate(driver) {
10256
10788
  RETURNS trigger
10257
10789
  LANGUAGE plpgsql AS $$
10258
10790
  BEGIN
10259
- INSERT INTO "${VERSION}"."offsets" (chain_id, "user", contract, "group", value)
10791
+ INSERT INTO "${VERSION}"."offsets" (chain_id, "user", contract, "group", obligation_id, value)
10260
10792
  VALUES (
10261
10793
  OLD.chain_id,
10262
10794
  OLD."user",
10263
10795
  OLD.contract,
10264
10796
  OLD."group",
10797
+ OLD.obligation_id,
10265
10798
  OLD.upper::numeric - OLD.lower::numeric
10266
10799
  )
10267
- ON CONFLICT (chain_id, "user", contract, "group") DO NOTHING;
10800
+ ON CONFLICT (chain_id, "user", contract, "group", obligation_id) DO NOTHING;
10268
10801
  RETURN OLD;
10269
10802
  END;
10270
10803
  $$;
@@ -10518,10 +11051,11 @@ var Rules_exports = /* @__PURE__ */ __exportAll({
10518
11051
  amountMutualExclusivity: () => amountMutualExclusivity,
10519
11052
  callback: () => callback,
10520
11053
  chains: () => chains,
11054
+ collateralToken: () => collateralToken,
11055
+ loanToken: () => loanToken,
10521
11056
  maturity: () => maturity,
10522
11057
  oracle: () => oracle,
10523
11058
  sameMaker: () => sameMaker,
10524
- token: () => token,
10525
11059
  validity: () => validity
10526
11060
  });
10527
11061
  /**
@@ -10541,7 +11075,7 @@ const chains = ({ chains }) => single("chain_ids", `Validates that offer chain i
10541
11075
  });
10542
11076
  const maturity = ({ maturities }) => single("maturity", `Validates that offer maturity is one of: [${maturities.join(", ")}]`, (offer) => {
10543
11077
  const allowedMaturities = maturities.map((m) => from$16(m));
10544
- if (!allowedMaturities.includes(offer.maturity)) return { message: `Maturity must be end of current month (${allowedMaturities[0]}) or end of next month (${allowedMaturities[1]}). Got: ${offer.maturity}` };
11078
+ if (!allowedMaturities.includes(offer.maturity)) return { message: `Maturity must be one of (${allowedMaturities.join(", ")}). Got: ${offer.maturity}` };
10545
11079
  });
10546
11080
  const callback = ({ callbacks }) => single("callback", `Validates callbacks: buy empty callback is ${callbacks.includes(Type$1.BuyWithEmptyCallback) ? "allowed" : "not allowed"}; sell empty callback is ${callbacks.includes(Type$1.SellWithEmptyCallback) ? "allowed" : "not allowed"}; non-empty callbacks are rejected`, (offer) => {
10547
11081
  if (!isEmptyCallback(offer)) return { message: "Non-empty callbacks are not supported." };
@@ -10549,15 +11083,25 @@ const callback = ({ callbacks }) => single("callback", `Validates callbacks: buy
10549
11083
  if (isEmptyCallback(offer) && !offer.buy && !callbacks.includes(Type$1.SellWithEmptyCallback)) return { message: "Sell offers with empty callback not allowed." };
10550
11084
  });
10551
11085
  /**
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.
11086
+ * A validation rule that checks if the offer's loan token is allowed for its chain.
11087
+ * @param assetsByChainId - Allowed loan tokens indexed by chain id.
10554
11088
  * @returns The issue that was found. If the offer is valid, this will be undefined.
10555
11089
  */
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" };
11090
+ const loanToken = ({ assetsByChainId }) => single("loan_token", "Validates that offer loan token is in the allowed token list for the offer chain", (offer) => {
11091
+ const allowedLoanTokens = assetsByChainId[offer.chainId]?.map((asset) => asset.toLowerCase());
11092
+ if (!allowedLoanTokens || allowedLoanTokens.length === 0) return { message: `No allowed loan tokens for chain ${offer.chainId}` };
11093
+ if (!allowedLoanTokens.includes(offer.loanToken.toLowerCase())) return { message: "Loan token is not allowed" };
11094
+ });
11095
+ /**
11096
+ * A validation rule that checks if the offer's collateral tokens are allowed for its chain.
11097
+ * @param collateralAssetsByChainId - Allowed collateral tokens indexed by chain id.
11098
+ * @returns The issue that was found. If the offer is valid, this will be undefined.
11099
+ */
11100
+ const collateralToken = ({ collateralAssetsByChainId }) => single("collateral_token", "Validates that offer collateral tokens are in the allowed token list for the offer chain", (offer) => {
11101
+ const allowedCollateralTokens = collateralAssetsByChainId[offer.chainId]?.map((asset) => asset.toLowerCase()) ?? [];
11102
+ if (allowedCollateralTokens.length === 0) return { message: `No allowed collateral tokens for chain ${offer.chainId}` };
11103
+ if (offer.collaterals.length === 0) return { message: "At least one collateral token is required" };
11104
+ if (offer.collaterals.some((collateral) => !allowedCollateralTokens.includes(collateral.asset.toLowerCase()))) return { message: "Collateral token is not allowed" };
10561
11105
  });
10562
11106
  /**
10563
11107
  * A validation rule that checks if the offer's oracle addresses are allowed for its chain.
@@ -10600,21 +11144,24 @@ const amountMutualExclusivity = () => single("amount_mutual_exclusivity", "Valid
10600
11144
  //#region src/gatekeeper/morphoRules.ts
10601
11145
  const morphoRules = (chains$3) => {
10602
11146
  const assetsByChainId = {};
11147
+ const collateralAssetsByChainId = {};
10603
11148
  const oraclesByChainId = {};
10604
11149
  for (const chain of chains$3) {
10605
11150
  assetsByChainId[chain.id] = assets[chain.id.toString()] ?? [];
11151
+ collateralAssetsByChainId[chain.id] = collateralAssets[chain.id.toString()] ?? [];
10606
11152
  oraclesByChainId[chain.id] = oracles[chain.id.toString()] ?? [];
10607
11153
  }
10608
11154
  return [
10609
11155
  sameMaker(),
10610
11156
  amountMutualExclusivity(),
10611
11157
  chains({ chains: chains$3 }),
10612
- maturity({ maturities: [MaturityType.EndOfMonth, MaturityType.EndOfNextMonth] }),
11158
+ maturity({ maturities: [MaturityType.EndOfWeek, MaturityType.EndOfNextWeek] }),
10613
11159
  callback({
10614
11160
  callbacks: [Type$1.BuyWithEmptyCallback, Type$1.SellWithEmptyCallback],
10615
11161
  allowedAddresses: []
10616
11162
  }),
10617
- token({ assetsByChainId }),
11163
+ loanToken({ assetsByChainId }),
11164
+ collateralToken({ collateralAssetsByChainId }),
10618
11165
  oracle({ oraclesByChainId })
10619
11166
  ];
10620
11167
  };
@@ -11026,6 +11573,12 @@ Object.defineProperty(exports, 'Rules', {
11026
11573
  return Rules_exports;
11027
11574
  }
11028
11575
  });
11576
+ Object.defineProperty(exports, 'Tick', {
11577
+ enumerable: true,
11578
+ get: function () {
11579
+ return Tick_exports;
11580
+ }
11581
+ });
11029
11582
  Object.defineProperty(exports, 'Time', {
11030
11583
  enumerable: true,
11031
11584
  get: function () {