@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
@@ -12,7 +12,7 @@ import "@opentelemetry/propagator-aws-xray";
12
12
  import "@opentelemetry/resources";
13
13
  import "@opentelemetry/sdk-trace-node";
14
14
  import "@opentelemetry/semantic-conventions";
15
- import { and, asc, eq, gt, gte, inArray, lte, ne, sql } from "drizzle-orm";
15
+ import { and, asc, desc, eq, gt, gte, inArray, lte, ne, sql } from "drizzle-orm";
16
16
  import { anvil, base, mainnet } from "viem/chains";
17
17
  import * as z$1 from "zod";
18
18
  import { StandardMerkleTree } from "@openzeppelin/merkle-tree";
@@ -566,7 +566,7 @@ var utils_exports = /* @__PURE__ */ __exportAll({
566
566
 
567
567
  //#endregion
568
568
  //#region src/indexer/collectors/Admin.ts
569
- function create$20(parameters) {
569
+ function create$21(parameters) {
570
570
  const collector = "admin";
571
571
  const { client, db, options: { maxBatchSize = 25, maxBlockNumber } = {} } = parameters;
572
572
  const maxBlockNumberBI = maxBlockNumber !== void 0 ? BigInt(maxBlockNumber) : void 0;
@@ -806,8 +806,8 @@ const names = [
806
806
  "positions",
807
807
  "prices"
808
808
  ];
809
- function create$19({ name, collect, client, db, options }) {
810
- const admin = create$20({
809
+ function create$20({ name, collect, client, db, options }) {
810
+ const admin = create$21({
811
811
  client,
812
812
  db,
813
813
  options
@@ -973,18 +973,18 @@ const MorphoV2 = parseAbi([
973
973
  "function setFeeSetter(address newFeeSetter)",
974
974
  "function setObligationTradingFee(bytes32 id, uint256 index, uint256 newTradingFee)",
975
975
  "function setOwner(address newOwner)",
976
- "function setTradingFeeRecipient(address recipient)",
976
+ "function setTradingFeeRecipient(address feeRecipient)",
977
977
  "function sharesOf(bytes32 id, address user) view returns (uint256)",
978
978
  "function shuffleSession()",
979
979
  "function supplyCollateral((address loanToken, (address token, uint256 lltv, address oracle)[] collaterals, uint256 maturity) obligation, address collateral, uint256 assets, address onBehalf)",
980
- "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)",
980
+ "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)",
981
981
  "function totalShares(bytes32 id) view returns (uint256)",
982
982
  "function totalUnits(bytes32 id) view returns (uint256)",
983
983
  "function touchObligation((address loanToken, (address token, uint256 lltv, address oracle)[] collaterals, uint256 maturity) obligation) returns (bytes32)",
984
984
  "function tradingFee(bytes32 id, uint256 timeToMaturity) view returns (uint256)",
985
985
  "function tradingFeeRecipient() view returns (address)",
986
- "function withdraw((address loanToken, (address token, uint256 lltv, address oracle)[] collaterals, uint256 maturity) obligation, uint256 obligationUnits, uint256 shares, address onBehalf) returns (uint256, uint256)",
987
- "function withdrawCollateral((address loanToken, (address token, uint256 lltv, address oracle)[] collaterals, uint256 maturity) obligation, address collateral, uint256 assets, address onBehalf)",
986
+ "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)",
987
+ "function withdrawCollateral((address loanToken, (address token, uint256 lltv, address oracle)[] collaterals, uint256 maturity) obligation, address collateral, uint256 assets, address onBehalf, address receiver)",
988
988
  "function withdrawable(bytes32 id) view returns (uint256)",
989
989
  "event Constructor(address indexed owner)",
990
990
  "event Consume(address indexed user, bytes32 indexed group, uint256 amount)",
@@ -996,12 +996,12 @@ const MorphoV2 = parseAbi([
996
996
  "event SetFeeSetter(address indexed feeSetter)",
997
997
  "event SetObligationTradingFee(bytes32 indexed id, uint256 indexed index, uint256 newTradingFee)",
998
998
  "event SetOwner(address indexed owner)",
999
- "event SetTradingFeeRecipient(address indexed recipient)",
999
+ "event SetTradingFeeRecipient(address indexed feeRecipient)",
1000
1000
  "event ShuffleSession(address indexed user, bytes32 session)",
1001
1001
  "event SupplyCollateral(address caller, bytes32 indexed id, address indexed collateral, uint256 assets, address indexed onBehalf)",
1002
- "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)",
1003
- "event Withdraw(address indexed caller, bytes32 indexed id, uint256 obligationUnits, uint256 shares, address indexed onBehalf)",
1004
- "event WithdrawCollateral(address caller, bytes32 indexed id, address indexed collateral, uint256 assets, address indexed onBehalf)"
1002
+ "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)",
1003
+ "event Withdraw(address caller, bytes32 indexed id, uint256 obligationUnits, uint256 shares, address indexed onBehalf, address indexed receiver)",
1004
+ "event WithdrawCollateral(address caller, bytes32 indexed id, address indexed collateral, uint256 assets, address indexed onBehalf, address receiver)"
1005
1005
  ]);
1006
1006
 
1007
1007
  //#endregion
@@ -1273,8 +1273,8 @@ const chains$2 = {
1273
1273
  name: "ethereum-virtual-testnet",
1274
1274
  custom: {
1275
1275
  morpho: {
1276
- address: "0x634b095371e4e45feed94c1a45c37798e173ea50",
1277
- blockCreated: 23226700
1276
+ address: "0xc9f3c65996fc46b9500608b2c9a9152c01c540f7",
1277
+ blockCreated: 23226871
1278
1278
  },
1279
1279
  morphoBlue: {
1280
1280
  address: "0xBBBBBbbBBb9cC5e90e3b3Af64bdAF62C37EEFFCb",
@@ -1420,13 +1420,13 @@ var MissingBlockNumberError = class extends BaseError {
1420
1420
 
1421
1421
  //#endregion
1422
1422
  //#region src/core/ChainRegistry.ts
1423
- var ChainRegistry_exports = /* @__PURE__ */ __exportAll({ create: () => create$18 });
1423
+ var ChainRegistry_exports = /* @__PURE__ */ __exportAll({ create: () => create$19 });
1424
1424
  /**
1425
1425
  * Creates a chain registry from a list of chains.
1426
1426
  * @param chains - Array of chain objects to register.
1427
1427
  * @returns A registry for looking up chains by ID. {@link ChainRegistry}
1428
1428
  */
1429
- function create$18(chains) {
1429
+ function create$19(chains) {
1430
1430
  const byId = /* @__PURE__ */ new Map();
1431
1431
  for (const chain of chains) byId.set(chain.id, chain);
1432
1432
  return {
@@ -2080,7 +2080,7 @@ const OfferSchema = () => {
2080
2080
  assets: z$1.bigint({ coerce: true }).min(0n).max(maxUint256),
2081
2081
  obligationUnits: z$1.bigint({ coerce: true }).min(0n).max(maxUint256).optional().default(0n),
2082
2082
  obligationShares: z$1.bigint({ coerce: true }).min(0n).max(maxUint256).optional().default(0n),
2083
- price: z$1.bigint({ coerce: true }).min(0n).max(maxUint256),
2083
+ tick: z$1.coerce.number().int().min(0).max(990),
2084
2084
  maturity: MaturitySchema,
2085
2085
  expiry: z$1.number().int().max(Number.MAX_SAFE_INTEGER),
2086
2086
  start: z$1.number().int().max(Number.MAX_SAFE_INTEGER),
@@ -2101,7 +2101,8 @@ const OfferSchema = () => {
2101
2101
  callback: z$1.object({
2102
2102
  address: z$1.string().transform(transformAddress),
2103
2103
  data: z$1.string().transform(transformHex)
2104
- })
2104
+ }),
2105
+ receiverIfMakerIsSeller: z$1.string().transform(transformAddress)
2105
2106
  }).refine((data) => data.start < data.expiry, {
2106
2107
  message: "start must be before expiry",
2107
2108
  path: ["start"]
@@ -2117,8 +2118,12 @@ const OfferSchema = () => {
2117
2118
  * @returns The created offer.
2118
2119
  */
2119
2120
  function from$14(input) {
2121
+ const normalizedInput = {
2122
+ ...input,
2123
+ receiverIfMakerIsSeller: input.receiverIfMakerIsSeller ?? input.maker
2124
+ };
2120
2125
  try {
2121
- return OfferSchema().parse(input);
2126
+ return OfferSchema().parse(normalizedInput);
2122
2127
  } catch (error) {
2123
2128
  throw new InvalidOfferError(error);
2124
2129
  }
@@ -2152,7 +2157,7 @@ const serialize = (offer) => ({
2152
2157
  assets: offer.assets.toString(),
2153
2158
  obligationUnits: offer.obligationUnits.toString(),
2154
2159
  obligationShares: offer.obligationShares.toString(),
2155
- price: offer.price.toString(),
2160
+ tick: offer.tick,
2156
2161
  maturity: Number(offer.maturity),
2157
2162
  expiry: Number(offer.expiry),
2158
2163
  start: Number(offer.start),
@@ -2170,6 +2175,7 @@ const serialize = (offer) => ({
2170
2175
  address: offer.callback.address,
2171
2176
  data: offer.callback.data
2172
2177
  },
2178
+ receiverIfMakerIsSeller: offer.receiverIfMakerIsSeller,
2173
2179
  hash: hash(offer)
2174
2180
  });
2175
2181
  /**
@@ -2197,14 +2203,13 @@ function random$1(config) {
2197
2203
  [.98, 2]
2198
2204
  ]));
2199
2205
  const buy = config?.buy !== void 0 ? config.buy : bool();
2200
- const ONE = 1000000000000000000n;
2201
- const qMin = buy ? 16 : 4;
2202
- const len = (buy ? 32 : 16) - qMin + 1;
2203
- const pricePairs = Array.from({ length: len }, (_, idx) => {
2204
- const q = qMin + idx;
2205
- return [BigInt(q) * (ONE / 4n), buy ? 1 + idx : 1 + (len - 1 - idx)];
2206
+ const tickMin = buy ? 0 : 495;
2207
+ const len = (buy ? 495 : 990) - tickMin + 1;
2208
+ const tickPairs = Array.from({ length: len }, (_, idx) => {
2209
+ const weight = buy ? 1 + idx : 1 + (len - 1 - idx);
2210
+ return [tickMin + idx, weight];
2206
2211
  });
2207
- const price = config?.price ?? weightedChoice(pricePairs);
2212
+ const tick = config?.tick ?? weightedChoice(tickPairs);
2208
2213
  const loanTokenDecimals = config?.assetsDecimals?.[loanToken] ?? 18;
2209
2214
  const unit = BigInt(10) ** BigInt(loanTokenDecimals);
2210
2215
  const amountBase = BigInt(100 + int(999901));
@@ -2213,12 +2218,13 @@ function random$1(config) {
2213
2218
  address: zeroAddress,
2214
2219
  data: "0x"
2215
2220
  };
2221
+ const maker = config?.maker ?? address();
2216
2222
  return from$14({
2217
- maker: config?.maker ?? address(),
2223
+ maker,
2218
2224
  assets: assetsScaled,
2219
2225
  obligationUnits: config?.obligationUnits ?? 0n,
2220
2226
  obligationShares: config?.obligationShares ?? 0n,
2221
- price,
2227
+ tick,
2222
2228
  maturity,
2223
2229
  expiry: config?.expiry ?? maturity - 1,
2224
2230
  start: config?.start ?? maturity - 10,
@@ -2231,7 +2237,8 @@ function random$1(config) {
2231
2237
  ...random$3(),
2232
2238
  lltv
2233
2239
  })).sort((a, b) => a.asset.localeCompare(b.asset)),
2234
- callback: config?.callback ?? emptyCallback
2240
+ callback: config?.callback ?? emptyCallback,
2241
+ receiverIfMakerIsSeller: config?.receiverIfMakerIsSeller ?? maker
2235
2242
  });
2236
2243
  }
2237
2244
  const weightedChoice = (pairs) => {
@@ -2283,7 +2290,7 @@ const types = {
2283
2290
  type: "uint256"
2284
2291
  },
2285
2292
  {
2286
- name: "price",
2293
+ name: "tick",
2287
2294
  type: "uint256"
2288
2295
  },
2289
2296
  {
@@ -2317,6 +2324,10 @@ const types = {
2317
2324
  {
2318
2325
  name: "callback",
2319
2326
  type: "Callback"
2327
+ },
2328
+ {
2329
+ name: "receiverIfMakerIsSeller",
2330
+ type: "address"
2320
2331
  }
2321
2332
  ],
2322
2333
  Collateral: [
@@ -2351,7 +2362,7 @@ function hash(offer) {
2351
2362
  assets: offer.assets,
2352
2363
  obligationUnits: offer.obligationUnits,
2353
2364
  obligationShares: offer.obligationShares,
2354
- price: offer.price,
2365
+ tick: BigInt(offer.tick),
2355
2366
  maturity: BigInt(offer.maturity),
2356
2367
  expiry: BigInt(offer.expiry),
2357
2368
  group: offer.group,
@@ -2362,7 +2373,8 @@ function hash(offer) {
2362
2373
  callback: {
2363
2374
  address: offer.callback.address.toLowerCase(),
2364
2375
  data: offer.callback.data
2365
- }
2376
+ },
2377
+ receiverIfMakerIsSeller: offer.receiverIfMakerIsSeller.toLowerCase()
2366
2378
  },
2367
2379
  primaryType: "Offer",
2368
2380
  types
@@ -2402,7 +2414,7 @@ const OfferAbi = [
2402
2414
  type: "uint256"
2403
2415
  },
2404
2416
  {
2405
- name: "price",
2417
+ name: "tick",
2406
2418
  type: "uint256"
2407
2419
  },
2408
2420
  {
@@ -2465,6 +2477,10 @@ const OfferAbi = [
2465
2477
  name: "data",
2466
2478
  type: "bytes"
2467
2479
  }]
2480
+ },
2481
+ {
2482
+ name: "receiverIfMakerIsSeller",
2483
+ type: "address"
2468
2484
  }
2469
2485
  ];
2470
2486
  function encode$1(offer) {
@@ -2473,7 +2489,7 @@ function encode$1(offer) {
2473
2489
  offer.assets,
2474
2490
  offer.obligationUnits,
2475
2491
  offer.obligationShares,
2476
- offer.price,
2492
+ BigInt(offer.tick),
2477
2493
  BigInt(offer.maturity),
2478
2494
  BigInt(offer.expiry),
2479
2495
  offer.group,
@@ -2483,7 +2499,8 @@ function encode$1(offer) {
2483
2499
  offer.loanToken,
2484
2500
  BigInt(offer.start),
2485
2501
  offer.collaterals,
2486
- offer.callback
2502
+ offer.callback,
2503
+ offer.receiverIfMakerIsSeller
2487
2504
  ]);
2488
2505
  }
2489
2506
  function decode$1(data) {
@@ -2498,7 +2515,7 @@ function decode$1(data) {
2498
2515
  assets: decoded[1],
2499
2516
  obligationUnits: decoded[2],
2500
2517
  obligationShares: decoded[3],
2501
- price: decoded[4],
2518
+ tick: Number(decoded[4]),
2502
2519
  maturity: from$16(Number(decoded[5])),
2503
2520
  expiry: Number(decoded[6]),
2504
2521
  group: decoded[7],
@@ -2517,7 +2534,8 @@ function decode$1(data) {
2517
2534
  callback: {
2518
2535
  address: decoded[14].address,
2519
2536
  data: decoded[14].data
2520
- }
2537
+ },
2538
+ receiverIfMakerIsSeller: decoded[15]
2521
2539
  });
2522
2540
  }
2523
2541
  /**
@@ -2593,6 +2611,12 @@ const takeEvent = {
2593
2611
  indexed: false,
2594
2612
  internalType: "bool"
2595
2613
  },
2614
+ {
2615
+ name: "sellerReceiver",
2616
+ type: "address",
2617
+ indexed: false,
2618
+ internalType: "address"
2619
+ },
2596
2620
  {
2597
2621
  name: "group",
2598
2622
  type: "bytes32",
@@ -2778,20 +2802,99 @@ function from$12(parameters) {
2778
2802
  };
2779
2803
  }
2780
2804
 
2805
+ //#endregion
2806
+ //#region src/core/Tick.ts
2807
+ var Tick_exports = /* @__PURE__ */ __exportAll({
2808
+ InvalidPriceError: () => InvalidPriceError,
2809
+ InvalidTickError: () => InvalidTickError,
2810
+ MAX_PRICE: () => MAX_PRICE,
2811
+ TICK_RANGE: () => TICK_RANGE,
2812
+ priceToTick: () => priceToTick,
2813
+ tickToPrice: () => tickToPrice
2814
+ });
2815
+ /** ln(1 + 0.025), scaled by 1e18. Matches TickLib onchain constant. */
2816
+ const LN_ONE_PLUS_DELTA = 24692612590371501n;
2817
+ /** ln(2), scaled by 1e18. Matches TickLib onchain constant. */
2818
+ const LN2 = 693147180559945309n;
2819
+ const WAD$1 = 10n ** 18n;
2820
+ const WAD_SQUARED = 10n ** 36n;
2821
+ const PRICE_STEP = 10n ** 13n;
2822
+ const HALF_TICK_RANGE = 495n;
2823
+ /** Tick domain supported by Morpho V2. */
2824
+ const TICK_RANGE = 990;
2825
+ /** Max allowed price (1e18 in wad). */
2826
+ const MAX_PRICE = WAD$1;
2827
+ /**
2828
+ * Converts a tick to a wad price using the same approximation and rounding as TickLib.
2829
+ * @param tick - Tick value in the inclusive range [0, 990].
2830
+ * @returns The price in wad units.
2831
+ * @throws {@link InvalidTickError} If tick is not an integer in range [0, 990].
2832
+ */
2833
+ function tickToPrice(tick) {
2834
+ assertTick(tick);
2835
+ return divHalfDownUnchecked(divHalfDownUnchecked(WAD_SQUARED, WAD$1 + wExp(LN_ONE_PLUS_DELTA * (HALF_TICK_RANGE - BigInt(tick)))), PRICE_STEP) * PRICE_STEP;
2836
+ }
2837
+ /**
2838
+ * Returns the lowest tick with a higher-or-equal price.
2839
+ * @param price - Price in wad units.
2840
+ * @returns The first tick whose {@link tickToPrice} is greater than or equal to `price`.
2841
+ * @throws {@link InvalidPriceError} If price is outside [0, 1e18].
2842
+ */
2843
+ function priceToTick(price) {
2844
+ assertPrice(price);
2845
+ let low = 0;
2846
+ let high = TICK_RANGE;
2847
+ while (low !== high) {
2848
+ const mid = Math.floor((low + high) / 2);
2849
+ if (tickToPrice(mid) < price) low = mid + 1;
2850
+ else high = mid;
2851
+ }
2852
+ return low;
2853
+ }
2854
+ function divHalfDownUnchecked(x, d) {
2855
+ return (x + (d - 1n) / 2n) / d;
2856
+ }
2857
+ function wExp(x) {
2858
+ if (x < 0n) return WAD_SQUARED / wExp(-x);
2859
+ const q = (x + LN2 / 2n) / LN2;
2860
+ const r = x - q * LN2;
2861
+ const secondTerm = r * r / (2n * WAD$1);
2862
+ const thirdTerm = secondTerm * r / (3n * WAD$1);
2863
+ return WAD$1 + r + secondTerm + thirdTerm << q;
2864
+ }
2865
+ function assertTick(tick) {
2866
+ if (!Number.isInteger(tick) || tick < 0 || tick > TICK_RANGE) throw new InvalidTickError(tick);
2867
+ }
2868
+ function assertPrice(price) {
2869
+ if (price < 0n || price > MAX_PRICE) throw new InvalidPriceError(price);
2870
+ }
2871
+ var InvalidTickError = class extends BaseError {
2872
+ name = "Tick.InvalidTickError";
2873
+ constructor(tick) {
2874
+ super(`Invalid tick: ${tick}. Tick must be an integer between 0 and ${TICK_RANGE}.`);
2875
+ }
2876
+ };
2877
+ var InvalidPriceError = class extends BaseError {
2878
+ name = "Tick.InvalidPriceError";
2879
+ constructor(price) {
2880
+ super(`Invalid price: ${price}. Price must be between 0 and ${MAX_PRICE}.`);
2881
+ }
2882
+ };
2883
+
2781
2884
  //#endregion
2782
2885
  //#region src/core/Quote.ts
2783
2886
  var Quote_exports = /* @__PURE__ */ __exportAll({
2784
2887
  InvalidQuoteError: () => InvalidQuoteError,
2785
- QuoteSchema: () => QuoteSchema,
2786
2888
  from: () => from$11,
2787
2889
  fromSnakeCase: () => fromSnakeCase,
2788
2890
  random: () => random
2789
2891
  });
2790
- const QuoteSchema = z$1.object({
2892
+ const SideInputSchema = z$1.object({ tick: z$1.number().int().min(0).max(TICK_RANGE).nullable() }).strict();
2893
+ const QuoteInputSchema = z$1.object({
2791
2894
  obligationId: z$1.string().transform(transformHex),
2792
- ask: z$1.object({ price: z$1.bigint({ coerce: true }).min(0n).max(maxUint256) }),
2793
- bid: z$1.object({ price: z$1.bigint({ coerce: true }).min(0n).max(maxUint256) })
2794
- });
2895
+ ask: SideInputSchema,
2896
+ bid: SideInputSchema
2897
+ }).strict();
2795
2898
  /**
2796
2899
  * Creates a quote for a given obligation.
2797
2900
  * @constructor
@@ -2801,16 +2904,16 @@ const QuoteSchema = z$1.object({
2801
2904
  *
2802
2905
  * @example
2803
2906
  * ```ts
2804
- * const quote = Quote.from({ obligationId: "0x123", ask: { price: 100n }, bid: { price: 100n } });
2907
+ * const quote = Quote.from({ obligationId: "0x123", ask: { tick: 500 }, bid: { tick: 510 } });
2805
2908
  * ```
2806
2909
  */
2807
2910
  function from$11(parameters) {
2808
2911
  try {
2809
- const parsedQuote = QuoteSchema.parse(parameters);
2912
+ const parsedQuote = QuoteInputSchema.parse(parameters);
2810
2913
  return {
2811
2914
  obligationId: parsedQuote.obligationId,
2812
- ask: parsedQuote.ask,
2813
- bid: parsedQuote.bid
2915
+ ask: sideFromTick(parsedQuote.ask),
2916
+ bid: sideFromTick(parsedQuote.bid)
2814
2917
  };
2815
2918
  } catch (error) {
2816
2919
  throw new InvalidQuoteError(error);
@@ -2837,8 +2940,8 @@ function fromSnakeCase(snake) {
2837
2940
  function random() {
2838
2941
  return from$11({
2839
2942
  obligationId: id(random$2()),
2840
- ask: { price: BigInt(int(1e6)) },
2841
- bid: { price: BigInt(int(1e6)) }
2943
+ ask: { tick: int(TICK_RANGE + 1) },
2944
+ bid: { tick: int(TICK_RANGE + 1) }
2842
2945
  });
2843
2946
  }
2844
2947
  var InvalidQuoteError = class extends BaseError {
@@ -2847,6 +2950,12 @@ var InvalidQuoteError = class extends BaseError {
2847
2950
  super("Invalid quote.", { cause: error });
2848
2951
  }
2849
2952
  };
2953
+ function sideFromTick(side) {
2954
+ return {
2955
+ tick: side.tick,
2956
+ price: side.tick === null ? 0n : tickToPrice(side.tick)
2957
+ };
2958
+ }
2850
2959
 
2851
2960
  //#endregion
2852
2961
  //#region src/core/TradingFee.ts
@@ -3361,7 +3470,7 @@ const BrandTypeId = Symbol.for("mempool/Brand");
3361
3470
 
3362
3471
  //#endregion
3363
3472
  //#region src/database/drizzle/VERSION.ts
3364
- const VERSION = "router_v1.6";
3473
+ const VERSION = "router_v1.8";
3365
3474
 
3366
3475
  //#endregion
3367
3476
  //#region src/database/drizzle/schema.ts
@@ -3513,10 +3622,7 @@ const offers = s.table(EnumTableName.OFFERS, {
3513
3622
  precision: 78,
3514
3623
  scale: 0
3515
3624
  }).notNull().default("0"),
3516
- price: numeric("price", {
3517
- precision: 78,
3518
- scale: 0
3519
- }).notNull(),
3625
+ tick: integer("tick").notNull(),
3520
3626
  maturity: integer("maturity").notNull(),
3521
3627
  expiry: integer("expiry").notNull(),
3522
3628
  start: integer("start").notNull(),
@@ -3527,6 +3633,7 @@ const offers = s.table(EnumTableName.OFFERS, {
3527
3633
  buy: boolean("buy").notNull(),
3528
3634
  callbackAddress: varchar("callback_address", { length: 42 }).notNull(),
3529
3635
  callbackData: text("callback_data").notNull(),
3636
+ receiverIfMakerIsSeller: varchar("receiver_if_maker_is_seller", { length: 42 }),
3530
3637
  blockNumber: bigint("block_number", { mode: "number" }).notNull(),
3531
3638
  updatedAt: timestamp("updated_at").defaultNow().notNull()
3532
3639
  }, (table) => [
@@ -3581,6 +3688,7 @@ const lots = s.table(EnumTableName.LOTS, {
3581
3688
  user: varchar("user", { length: 42 }).notNull(),
3582
3689
  contract: varchar("contract", { length: 42 }).notNull(),
3583
3690
  group: varchar("group", { length: 66 }).notNull(),
3691
+ obligationId: varchar("obligation_id", { length: 66 }).notNull(),
3584
3692
  lower: numeric("lower", {
3585
3693
  precision: 78,
3586
3694
  scale: 0
@@ -3595,7 +3703,8 @@ const lots = s.table(EnumTableName.LOTS, {
3595
3703
  table.chainId,
3596
3704
  table.user,
3597
3705
  table.contract,
3598
- table.group
3706
+ table.group,
3707
+ table.obligationId
3599
3708
  ],
3600
3709
  name: "lots_pk"
3601
3710
  }),
@@ -3631,6 +3740,7 @@ const offsets = s.table(EnumTableName.OFFSETS, {
3631
3740
  user: varchar("user", { length: 42 }).notNull(),
3632
3741
  contract: varchar("contract", { length: 42 }).notNull(),
3633
3742
  group: varchar("group", { length: 66 }).notNull(),
3743
+ obligationId: varchar("obligation_id", { length: 66 }).notNull(),
3634
3744
  value: numeric("value", {
3635
3745
  precision: 78,
3636
3746
  scale: 0
@@ -3640,7 +3750,8 @@ const offsets = s.table(EnumTableName.OFFSETS, {
3640
3750
  table.chainId,
3641
3751
  table.user,
3642
3752
  table.contract,
3643
- table.group
3753
+ table.group,
3754
+ table.obligationId
3644
3755
  ],
3645
3756
  name: "offsets_pk"
3646
3757
  }), foreignKey({
@@ -4231,6 +4342,7 @@ function decodeCallbacks(parameters) {
4231
4342
  positionContract: loanToken,
4232
4343
  positionUser: offer.maker,
4233
4344
  group: offer.group,
4345
+ obligationId: obligationId(offer),
4234
4346
  size: offer.assets
4235
4347
  });
4236
4348
  callbacks.push({
@@ -4903,7 +5015,7 @@ async function* collectPrices(parameters) {
4903
5015
  //#region src/indexer/collectors/CollectorBuilder.ts
4904
5016
  function createBuilder(parameters) {
4905
5017
  const { client, db, gatekeeper, options: { maxBlockNumber, blockWindow, interval } = {} } = parameters;
4906
- const createCollector = (name, collect) => create$19({
5018
+ const createCollector = (name, collect) => create$20({
4907
5019
  name,
4908
5020
  collect,
4909
5021
  client,
@@ -4995,7 +5107,7 @@ const from$7 = (parameters) => {
4995
5107
  //#endregion
4996
5108
  //#region src/indexer/Indexer.ts
4997
5109
  var Indexer_exports = /* @__PURE__ */ __exportAll({
4998
- create: () => create$17,
5110
+ create: () => create$18,
4999
5111
  from: () => from$6
5000
5112
  });
5001
5113
  function from$6(config) {
@@ -5011,7 +5123,7 @@ function from$6(config) {
5011
5123
  retryAttempts,
5012
5124
  retryDelayMs
5013
5125
  });
5014
- return create$17({
5126
+ return create$18({
5015
5127
  client,
5016
5128
  collectors: [
5017
5129
  offersCollector,
@@ -5021,7 +5133,7 @@ function from$6(config) {
5021
5133
  ]
5022
5134
  });
5023
5135
  }
5024
- function create$17(params) {
5136
+ function create$18(params) {
5025
5137
  const { collectors, client } = params;
5026
5138
  const indexerId = `${client.chain.id.toString()}.indexer`;
5027
5139
  const tracer = getTracer(`router.${indexerId}`);
@@ -5050,12 +5162,12 @@ function create$17(params) {
5050
5162
 
5051
5163
  //#endregion
5052
5164
  //#region src/api/Health.ts
5053
- var Health_exports = /* @__PURE__ */ __exportAll({ create: () => create$16 });
5165
+ var Health_exports = /* @__PURE__ */ __exportAll({ create: () => create$17 });
5054
5166
  const DEFAULT_MAX_ALLOWED_LAG = 5;
5055
5167
  /**
5056
5168
  * Create a health service that exposes collector and chain block numbers.
5057
5169
  */
5058
- function create$16(parameters) {
5170
+ function create$17(parameters) {
5059
5171
  const { db, maxAllowedLag = DEFAULT_MAX_ALLOWED_LAG, healthClients, chainRegistry } = parameters;
5060
5172
  const loadSnapshot = async () => {
5061
5173
  const [collectorRows, chainRows, remoteBlockByChainId] = await Promise.all([
@@ -5169,8 +5281,10 @@ async function getRemoteBlockNumbers(healthClients) {
5169
5281
  //#region src/api/Schema/BookResponse.ts
5170
5282
  var BookResponse_exports = /* @__PURE__ */ __exportAll({ from: () => from$5 });
5171
5283
  function from$5(level) {
5284
+ const price = tickToPrice(level.tick);
5172
5285
  return {
5173
- price: level.price.toString(),
5286
+ tick: level.tick,
5287
+ price: price.toString(),
5174
5288
  assets: level.assets.toString(),
5175
5289
  count: level.count
5176
5290
  };
@@ -5217,6 +5331,7 @@ var ObligationResponse_exports = /* @__PURE__ */ __exportAll({ from: () => from$
5217
5331
  * Creates an `ObligationResponse` from a `Obligation`.
5218
5332
  * @constructor
5219
5333
  * @param obligation - {@link Obligation}
5334
+ * @param quote - {@link Quote}
5220
5335
  * @returns The created `ObligationResponse`. {@link ObligationResponse}
5221
5336
  */
5222
5337
  function from$4(obligation, quote) {
@@ -5230,8 +5345,14 @@ function from$4(obligation, quote) {
5230
5345
  oracle: c.oracle
5231
5346
  })),
5232
5347
  maturity: obligation.maturity,
5233
- ask: { price: quote.ask.price.toString() },
5234
- bid: { price: quote.bid.price.toString() }
5348
+ ask: {
5349
+ tick: quote.ask.tick,
5350
+ price: quote.ask.price.toString()
5351
+ },
5352
+ bid: {
5353
+ tick: quote.bid.tick,
5354
+ price: quote.bid.price.toString()
5355
+ }
5235
5356
  };
5236
5357
  }
5237
5358
 
@@ -5275,11 +5396,12 @@ function from$3(input) {
5275
5396
  obligation_shares: input.obligationShares.toString(),
5276
5397
  start: input.start,
5277
5398
  expiry: input.expiry,
5278
- price: input.price.toString(),
5399
+ tick: input.tick,
5279
5400
  group: input.group,
5280
5401
  session: input.session,
5281
5402
  callback: input.callback.address,
5282
- callback_data: input.callback.data
5403
+ callback_data: input.callback.data,
5404
+ receiver_if_maker_is_seller: input.receiverIfMakerIsSeller
5283
5405
  },
5284
5406
  offer_hash: input.hash,
5285
5407
  obligation_id: id({
@@ -5346,7 +5468,7 @@ var InternalServerError = class extends APIError {
5346
5468
  super(STATUS_CODE.INTERNAL_SERVER_ERROR, message, "INTERNAL_SERVER_ERROR");
5347
5469
  }
5348
5470
  };
5349
- var BadRequestError = class extends APIError {
5471
+ var BadRequestError$1 = class extends APIError {
5350
5472
  constructor(message = "Invalid JSON format", details) {
5351
5473
  super(STATUS_CODE.BAD_REQUEST, message, "BAD_REQUEST", details);
5352
5474
  }
@@ -5368,7 +5490,7 @@ function success(args) {
5368
5490
  */
5369
5491
  function failure(err) {
5370
5492
  if (err instanceof APIError) return handleAPIError(err);
5371
- if (err instanceof SyntaxError) return handleAPIError(new BadRequestError(err.message));
5493
+ if (err instanceof SyntaxError) return handleAPIError(new BadRequestError$1(err.message));
5372
5494
  if (err instanceof z$1.ZodError) return handleAPIError(handleZodError(err));
5373
5495
  return handleAPIError(new InternalServerError());
5374
5496
  }
@@ -5418,7 +5540,7 @@ function __decorate(decorators, target, key, desc) {
5418
5540
  //#region src/api/Schema/openapi.ts
5419
5541
  const timestampExample = "2024-01-01T12:00:00.000Z";
5420
5542
  const offerCursorExample = "eyJvZmZzZXQiOjEwMH0";
5421
- const obligationCursorExample = "0x25690ae1aee324a005be565f3bcdd16dbf8daf7969b26c181c8b8f467dad9abc";
5543
+ const obligationCursorExample = "eyJzb3J0IjpbImlkIl0sImlkIjoiMHgxMjM0NTY3ODkwMTIzNDU2Nzg5MDEyMzQ1Njc4OTAxMjM0NTY3ODkwMTIzNDU2Nzg5MDEyMzQ1Njc4OTAiLCJhc2siOiIwIiwiYmlkIjoiMCIsIm1hdHVyaXR5IjoxNzYxOTIyODAwfQ";
5422
5544
  const offerExample = {
5423
5545
  offer: {
5424
5546
  obligation: {
@@ -5437,11 +5559,12 @@ const offerExample = {
5437
5559
  obligation_shares: "0",
5438
5560
  start: 1761922790,
5439
5561
  expiry: 1761922799,
5440
- price: "2750000000000000000",
5562
+ tick: 495,
5441
5563
  group: "0x000000000000000000000000000000000000000000000000000000000008b8f4",
5442
5564
  session: "0x0000000000000000000000000000000000000000000000000000000000000000",
5443
5565
  callback: "0x0000000000000000000000000000000000000000",
5444
- callback_data: "0x"
5566
+ callback_data: "0x",
5567
+ receiver_if_maker_is_seller: "0x7b093658BE7f90B63D7c359e8f408e503c2D9401"
5445
5568
  },
5446
5569
  offer_hash: "0xac4bd8318ec914f89f8af913f162230575b0ac0696a19256bc12138c5cfe1427",
5447
5570
  obligation_id: "0x25690ae1aee324a005be565f3bcdd16dbf8daf7969b26c181c8b8f467dad9abc",
@@ -5478,7 +5601,7 @@ const validateOfferExample = {
5478
5601
  assets: "369216000000000000000000",
5479
5602
  obligation_units: "0",
5480
5603
  obligation_shares: "0",
5481
- price: "2750000000000000000",
5604
+ tick: 495,
5482
5605
  maturity: 1761922799,
5483
5606
  expiry: 1761922799,
5484
5607
  start: 1761922790,
@@ -5495,7 +5618,8 @@ const validateOfferExample = {
5495
5618
  callback: {
5496
5619
  address: "0x0000000000000000000000000000000000000000",
5497
5620
  data: "0x"
5498
- }
5621
+ },
5622
+ receiver_if_maker_is_seller: "0x7b093658BE7f90B63D7c359e8f408e503c2D9401"
5499
5623
  };
5500
5624
  const routerStatusExample = {
5501
5625
  status: "live",
@@ -5566,11 +5690,23 @@ __decorate([ApiProperty({
5566
5690
  example: validateOfferExample.callback.data
5567
5691
  })], ValidateCallbackRequest.prototype, "data", void 0);
5568
5692
  var AskResponse = class {};
5693
+ __decorate([ApiProperty({
5694
+ type: "number",
5695
+ nullable: true,
5696
+ example: 500,
5697
+ description: "Best ask tick. Null when there is no active ask quote."
5698
+ })], AskResponse.prototype, "tick", void 0);
5569
5699
  __decorate([ApiProperty({
5570
5700
  type: "string",
5571
5701
  example: "1000000000000000000"
5572
5702
  })], AskResponse.prototype, "price", void 0);
5573
5703
  var BidResponse = class {};
5704
+ __decorate([ApiProperty({
5705
+ type: "number",
5706
+ nullable: true,
5707
+ example: 500,
5708
+ description: "Best bid tick. Null when there is no active bid quote."
5709
+ })], BidResponse.prototype, "tick", void 0);
5574
5710
  __decorate([ApiProperty({
5575
5711
  type: "string",
5576
5712
  example: "1000000000000000000"
@@ -5622,9 +5758,11 @@ __decorate([ApiProperty({
5622
5758
  example: offerExample.offer.expiry
5623
5759
  })], OfferDataResponse.prototype, "expiry", void 0);
5624
5760
  __decorate([ApiProperty({
5625
- type: "string",
5626
- example: offerExample.offer.price
5627
- })], OfferDataResponse.prototype, "price", void 0);
5761
+ type: "number",
5762
+ example: offerExample.offer.tick,
5763
+ minimum: 0,
5764
+ maximum: 990
5765
+ })], OfferDataResponse.prototype, "tick", void 0);
5628
5766
  __decorate([ApiProperty({
5629
5767
  type: "string",
5630
5768
  example: offerExample.offer.group
@@ -5641,6 +5779,10 @@ __decorate([ApiProperty({
5641
5779
  type: "string",
5642
5780
  example: offerExample.offer.callback_data
5643
5781
  })], OfferDataResponse.prototype, "callback_data", void 0);
5782
+ __decorate([ApiProperty({
5783
+ type: "string",
5784
+ example: offerExample.offer.receiver_if_maker_is_seller
5785
+ })], OfferDataResponse.prototype, "receiver_if_maker_is_seller", void 0);
5644
5786
  var OfferListItemResponse = class {};
5645
5787
  __decorate([ApiProperty({
5646
5788
  type: () => OfferDataResponse,
@@ -5865,9 +6007,11 @@ __decorate([ApiProperty({
5865
6007
  required: false
5866
6008
  })], ValidateOfferRequest.prototype, "obligation_shares", void 0);
5867
6009
  __decorate([ApiProperty({
5868
- type: "string",
5869
- example: validateOfferExample.price
5870
- })], ValidateOfferRequest.prototype, "price", void 0);
6010
+ type: "number",
6011
+ example: validateOfferExample.tick,
6012
+ minimum: 0,
6013
+ maximum: 990
6014
+ })], ValidateOfferRequest.prototype, "tick", void 0);
5871
6015
  __decorate([ApiProperty({
5872
6016
  type: "number",
5873
6017
  example: validateOfferExample.maturity
@@ -5908,6 +6052,10 @@ __decorate([ApiProperty({
5908
6052
  type: () => ValidateCallbackRequest,
5909
6053
  example: validateOfferExample.callback
5910
6054
  })], ValidateOfferRequest.prototype, "callback", void 0);
6055
+ __decorate([ApiProperty({
6056
+ type: "string",
6057
+ example: validateOfferExample.receiver_if_maker_is_seller
6058
+ })], ValidateOfferRequest.prototype, "receiver_if_maker_is_seller", void 0);
5911
6059
  var ValidateOffersRequest = class {};
5912
6060
  __decorate([ApiProperty({
5913
6061
  type: () => [ValidateOfferRequest],
@@ -5967,9 +6115,16 @@ __decorate([ApiProperty({
5967
6115
  description: "List of validation issues. Returned when any offer fails validation."
5968
6116
  })], ValidationFailureResponse.prototype, "data", void 0);
5969
6117
  var BookLevelResponse = class {};
6118
+ __decorate([ApiProperty({
6119
+ type: "number",
6120
+ example: 495,
6121
+ minimum: 0,
6122
+ maximum: 990
6123
+ })], BookLevelResponse.prototype, "tick", void 0);
5970
6124
  __decorate([ApiProperty({
5971
6125
  type: "string",
5972
- example: "2750000000000000000"
6126
+ example: "500000000000000000",
6127
+ description: "Price derived from tick, scaled by 1e18."
5973
6128
  })], BookLevelResponse.prototype, "price", void 0);
5974
6129
  __decorate([ApiProperty({
5975
6130
  type: "string",
@@ -5983,6 +6138,7 @@ const positionExample = {
5983
6138
  chain_id: 1,
5984
6139
  contract: "0xC9A9C45C0eB717f8b5F193Af6bAa05A1c0Ac5078",
5985
6140
  user: "0x7b093658BE7f90B63D7c359e8f408e503c2D9401",
6141
+ obligation_id: "0x12590ae1aee324a005be565f3bcdd16dbf8daf7969b26c181c8b8f467dad9f67",
5986
6142
  reserved: "200000000000000000000",
5987
6143
  block_number: 21345678
5988
6144
  };
@@ -5999,6 +6155,12 @@ __decorate([ApiProperty({
5999
6155
  type: "string",
6000
6156
  example: positionExample.user
6001
6157
  })], PositionListItemResponse.prototype, "user", void 0);
6158
+ __decorate([ApiProperty({
6159
+ type: "string",
6160
+ nullable: true,
6161
+ example: positionExample.obligation_id,
6162
+ description: "Obligation id this reserved amount belongs to, or null if no lots exist."
6163
+ })], PositionListItemResponse.prototype, "obligation_id", void 0);
6002
6164
  __decorate([ApiProperty({
6003
6165
  type: "string",
6004
6166
  example: positionExample.reserved
@@ -6026,7 +6188,7 @@ __decorate([ApiProperty({
6026
6188
  })], BookListResponse.prototype, "cursor", void 0);
6027
6189
  __decorate([ApiProperty({
6028
6190
  type: () => [BookLevelResponse],
6029
- description: "Aggregated book levels grouped by computed price."
6191
+ description: "Aggregated book levels grouped by offer tick."
6030
6192
  })], BookListResponse.prototype, "data", void 0);
6031
6193
  let BooksController = class BooksController {
6032
6194
  async getBook() {}
@@ -6036,7 +6198,7 @@ __decorate([
6036
6198
  methods: ["get"],
6037
6199
  path: "/v1/books/{obligationId}/{side}",
6038
6200
  summary: "Get aggregated book",
6039
- 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)."
6201
+ 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)."
6040
6202
  }),
6041
6203
  ApiParam({
6042
6204
  name: "obligationId",
@@ -6061,7 +6223,7 @@ __decorate([
6061
6223
  name: "limit",
6062
6224
  type: "number",
6063
6225
  example: 10,
6064
- description: "Maximum number of price levels to return."
6226
+ description: "Maximum number of tick levels to return."
6065
6227
  }),
6066
6228
  ApiResponse({
6067
6229
  status: 200,
@@ -6255,6 +6417,11 @@ const configRulesLoanTokenExample = {
6255
6417
  chain_id: 1,
6256
6418
  address: "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48"
6257
6419
  };
6420
+ const configRulesCollateralTokenExample = {
6421
+ type: "collateral_token",
6422
+ chain_id: 1,
6423
+ address: "0x7f39C581F595B53c5cb19bD0b3f8dA6c935E2Ca0"
6424
+ };
6258
6425
  const configRulesOracleExample = {
6259
6426
  type: "oracle",
6260
6427
  chain_id: 1,
@@ -6264,6 +6431,7 @@ const configRulesChecksumExample = "f1d2d2f924e986ac86fdf7b36c94bcdf";
6264
6431
  const configRulesPayloadExample = [
6265
6432
  configRulesMaturityExample,
6266
6433
  configRulesLoanTokenExample,
6434
+ configRulesCollateralTokenExample,
6267
6435
  configRulesOracleExample
6268
6436
  ];
6269
6437
  const configContractNames = [
@@ -6390,7 +6558,7 @@ __decorate([
6390
6558
  methods: ["get"],
6391
6559
  path: "/v1/config/rules",
6392
6560
  summary: "Get config rules",
6393
- description: "Returns configured rules (maturities, loan tokens, oracles) for supported chains."
6561
+ description: "Returns configured rules (maturities, loan tokens, collateral tokens, oracles) for supported chains."
6394
6562
  }),
6395
6563
  ApiQuery({
6396
6564
  name: "cursor",
@@ -6410,7 +6578,7 @@ __decorate([
6410
6578
  name: "types",
6411
6579
  type: ["string"],
6412
6580
  required: false,
6413
- example: "maturity,loan_token,oracle",
6581
+ example: "maturity,loan_token,collateral_token,oracle",
6414
6582
  description: "Filter by rule types (comma-separated).",
6415
6583
  style: "form",
6416
6584
  explode: false
@@ -6440,13 +6608,13 @@ __decorate([
6440
6608
  methods: ["get"],
6441
6609
  path: "/v1/obligations",
6442
6610
  summary: "List all obligations",
6443
- description: "Returns a list of obligations with their current best ask and bid. Obligations are sorted by their id in ascending order by default."
6611
+ 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."
6444
6612
  }),
6445
6613
  ApiQuery({
6446
6614
  name: "cursor",
6447
6615
  type: "string",
6448
6616
  example: obligationCursorExample,
6449
- description: "Obligation id cursor for pagination."
6617
+ description: "Pagination cursor in base64url-encoded format."
6450
6618
  }),
6451
6619
  ApiQuery({
6452
6620
  name: "limit",
@@ -6490,6 +6658,15 @@ __decorate([
6490
6658
  style: "form",
6491
6659
  explode: false
6492
6660
  }),
6661
+ ApiQuery({
6662
+ name: "sort",
6663
+ type: "string",
6664
+ required: false,
6665
+ example: "-ask,bid,maturity",
6666
+ description: "Sort order as comma-separated fields (`id`, `ask`, `bid`, `maturity`). Prefix with `-` for descending order. Max 3 fields.",
6667
+ style: "form",
6668
+ explode: false
6669
+ }),
6493
6670
  ApiResponse({
6494
6671
  status: 200,
6495
6672
  description: "Success",
@@ -6528,7 +6705,7 @@ __decorate([
6528
6705
  methods: ["get"],
6529
6706
  path: "/v1/users/{userAddress}/positions",
6530
6707
  summary: "Get user positions",
6531
- description: "Returns positions for a user with reserved balance. The reserved balance is the amount locked by active offers (max lot upper - offset - consumed)."
6708
+ 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."
6532
6709
  }),
6533
6710
  ApiParam({
6534
6711
  name: "userAddress",
@@ -6616,6 +6793,7 @@ function from$2(position) {
6616
6793
  chain_id: position.chainId,
6617
6794
  contract: position.contract,
6618
6795
  user: position.user,
6796
+ obligation_id: position.obligationId,
6619
6797
  reserved: position.reserved.toString(),
6620
6798
  block_number: position.blockNumber
6621
6799
  };
@@ -6624,11 +6802,13 @@ function from$2(position) {
6624
6802
  //#endregion
6625
6803
  //#region src/api/Schema/requests.ts
6626
6804
  const MAX_LIMIT = 100;
6627
- const DEFAULT_LIMIT$4 = 20;
6805
+ const DEFAULT_LIMIT$5 = 20;
6806
+ const MAX_OBLIGATION_SORT_FIELDS = 3;
6628
6807
  const CONFIG_RULES_MAX_LIMIT = 1e3;
6629
6808
  const CONFIG_RULES_DEFAULT_LIMIT = 100;
6630
6809
  const CONFIG_CONTRACTS_MAX_LIMIT = 1e3;
6631
6810
  const CONFIG_CONTRACTS_DEFAULT_LIMIT = 1e3;
6811
+ const OBLIGATION_SORT_ENTRY_REGEX = /^-?(id|ask|bid|maturity)$/;
6632
6812
  /** Validate cursor is a valid base64url-encoded JSON object.
6633
6813
  * Domain layer handles semantic validation of cursor fields. */
6634
6814
  function isValidBase64urlJson(val) {
@@ -6660,8 +6840,8 @@ const PaginationQueryParams = z$1.object({
6660
6840
  description: "Pagination cursor in base64url-encoded format",
6661
6841
  example: "eyJzaWRlIjoic2VsbCIsImN1cnJlbnRQcmljZSI6IjEwMDAwMDAwMDAwMDAwMDAwMDAiLCJibG9ja051bWJlciI6MSwiYXNzZXRzIjoiMTAwMDAwMDAwMDAwMDAwMDAwMCIsImhhc2giOiIweGRmZDY4NTllM2UwODJkMTkzODlhMWFlYzFiZGFkN2U4ZDkyZDk2YjFhYTc5NDBkYTkxYTMxMjVkMzFlM2JlNWIiLCJ0b3RhbFJldHVybmVkIjoxMCwibm93IjoxNjAwMDAwMDAwfQ"
6662
6842
  }),
6663
- limit: z$1.string().regex(/^[1-9]\d*$/, { message: "Limit must be a positive integer" }).transform((val) => Number.parseInt(val, 10)).pipe(z$1.number().max(MAX_LIMIT, { message: `Limit cannot exceed ${MAX_LIMIT}` })).optional().default(DEFAULT_LIMIT$4).meta({
6664
- description: `Limit maximum: ${MAX_LIMIT}. Default: ${DEFAULT_LIMIT$4}`,
6843
+ limit: z$1.string().regex(/^[1-9]\d*$/, { message: "Limit must be a positive integer" }).transform((val) => Number.parseInt(val, 10)).pipe(z$1.number().max(MAX_LIMIT, { message: `Limit cannot exceed ${MAX_LIMIT}` })).optional().default(DEFAULT_LIMIT$5).meta({
6844
+ description: `Limit maximum: ${MAX_LIMIT}. Default: ${DEFAULT_LIMIT$5}`,
6665
6845
  example: 10
6666
6846
  })
6667
6847
  });
@@ -6669,10 +6849,11 @@ const ConfigRuleTypes = z$1.enum([
6669
6849
  "maturity",
6670
6850
  "callback",
6671
6851
  "loan_token",
6852
+ "collateral_token",
6672
6853
  "oracle"
6673
6854
  ]);
6674
6855
  const GetConfigRulesQueryParams = z$1.object({
6675
- cursor: z$1.string().regex(/^(maturity|callback|loan_token|oracle):[1-9]\d*:.+$/, { message: "Cursor must be in the format type:chain_id:<value>" }).optional().meta({
6856
+ cursor: z$1.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({
6676
6857
  description: "Pagination cursor in type:chain_id:<value> format",
6677
6858
  example: "maturity:1:1730415600:end_of_next_month"
6678
6859
  }),
@@ -6682,7 +6863,7 @@ const GetConfigRulesQueryParams = z$1.object({
6682
6863
  }),
6683
6864
  types: csvArray(ConfigRuleTypes).meta({
6684
6865
  description: "Filter by rule types (comma-separated).",
6685
- example: "maturity,loan_token,oracle"
6866
+ example: "maturity,loan_token,collateral_token,oracle"
6686
6867
  }),
6687
6868
  chains: csvArray(z$1.string().regex(/^[1-9]\d*$/, { message: "Chain must be a positive integer" }).transform((val) => Number.parseInt(val, 10))).meta({
6688
6869
  description: "Filter by chain IDs (comma-separated).",
@@ -6757,9 +6938,12 @@ const GetOffersQueryParams = PaginationQueryParams.omit({ cursor: true }).extend
6757
6938
  });
6758
6939
  const GetObligationsQueryParams = z$1.object({
6759
6940
  ...PaginationQueryParams.shape,
6760
- cursor: z$1.string().optional().meta({
6761
- description: "Obligation id cursor",
6762
- example: "0x1234567890123456789012345678901234567890123456789012345678901234"
6941
+ cursor: z$1.string().optional().refine((val) => {
6942
+ if (!val) return true;
6943
+ return isValidBase64urlJson(val);
6944
+ }, { message: "Invalid cursor format. Must be a valid base64url-encoded cursor object" }).meta({
6945
+ description: "Pagination cursor in base64url-encoded format.",
6946
+ example: "eyJzb3J0IjpbImlkIl0sImlkIjoiMHgxMjM0NTY3ODkwMTIzNDU2Nzg5MDEyMzQ1Njc4OTAxMjM0NTY3ODkwMTIzNDU2Nzg5MDEyMzQ1Njc4OTAiLCJhc2siOiIwIiwiYmlkIjoiMCIsIm1hdHVyaXR5IjoxNzYxOTIyODAwfQ"
6763
6947
  }),
6764
6948
  chains: csvArray(z$1.string().regex(/^[1-9]\d*$/, { message: "Chain must be a positive integer" }).transform((val) => Number.parseInt(val, 10))).meta({
6765
6949
  description: "Filter by chain IDs (comma-separated).",
@@ -6776,18 +6960,35 @@ const GetObligationsQueryParams = z$1.object({
6776
6960
  maturities: csvArray(z$1.string().regex(/^[1-9]\d*$/, { message: "Maturity must be a positive integer" }).transform((val) => Number.parseInt(val, 10))).meta({
6777
6961
  description: "Filter by exact maturity timestamps (comma-separated, unix seconds).",
6778
6962
  example: "1761922800,1764524800"
6963
+ }),
6964
+ sort: csvArray(z$1.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) => {
6965
+ if (!entries) return;
6966
+ const seen = /* @__PURE__ */ new Set();
6967
+ for (const entry of entries) {
6968
+ const field = entry.startsWith("-") ? entry.slice(1) : entry;
6969
+ if (seen.has(field)) {
6970
+ ctx.addIssue({
6971
+ code: "custom",
6972
+ message: `Duplicate sort field: ${field}`
6973
+ });
6974
+ return;
6975
+ }
6976
+ seen.add(field);
6977
+ }
6978
+ }).meta({
6979
+ description: "Sort order as comma-separated fields. Prefix a field with '-' for descending order. Max 3 fields.",
6980
+ example: "-ask,bid,maturity"
6779
6981
  })
6780
6982
  });
6781
6983
  const GetObligationParams = z$1.object({ obligation_id: z$1.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({
6782
6984
  description: "Obligation id",
6783
6985
  example: "0x1234567890123456789012345678901234567890123456789012345678901234"
6784
6986
  }) });
6785
- /** Validate a book cursor format: {side, lastPrice, offersCursor} */
6987
+ /** Validate a book cursor format: {side, lastTick, offersCursor} */
6786
6988
  function isValidBookCursor(cursorString) {
6787
- const isNumericString = (value) => typeof value === "string" && /^-?\d+$/.test(value);
6788
6989
  try {
6789
6990
  const v = JSON.parse(Buffer.from(cursorString, "base64url").toString("utf8"));
6790
- return (v?.side === "buy" || v?.side === "sell") && isNumericString(v?.lastPrice) && (v?.offersCursor === null || typeof v?.offersCursor === "string");
6991
+ return (v?.side === "buy" || v?.side === "sell") && typeof v?.lastTick === "number" && Number.isInteger(v.lastTick) && (v?.offersCursor === null || typeof v?.offersCursor === "string");
6791
6992
  } catch {
6792
6993
  return false;
6793
6994
  }
@@ -6800,8 +7001,8 @@ const BookPaginationQueryParams = z$1.object({
6800
7001
  description: "Pagination cursor in base64url-encoded format for book levels",
6801
7002
  example: "eyJzaWRlIjoiYnV5IiwibGFzdFJhdGUiOiIxMDAwMDAwMDAwMDAwMDAwMDAwIiwib2ZmZXJzQ3Vyc29yIjpudWxsfQ"
6802
7003
  }),
6803
- limit: z$1.string().regex(/^[1-9]\d*$/, { message: "Limit must be a positive integer" }).transform((val) => Number.parseInt(val, 10)).pipe(z$1.number().max(MAX_LIMIT, { message: `Limit cannot exceed ${MAX_LIMIT}` })).optional().default(DEFAULT_LIMIT$4).meta({
6804
- description: `Limit maximum: ${MAX_LIMIT}. Default: ${DEFAULT_LIMIT$4}`,
7004
+ limit: z$1.string().regex(/^[1-9]\d*$/, { message: "Limit must be a positive integer" }).transform((val) => Number.parseInt(val, 10)).pipe(z$1.number().max(MAX_LIMIT, { message: `Limit cannot exceed ${MAX_LIMIT}` })).optional().default(DEFAULT_LIMIT$5).meta({
7005
+ description: `Limit maximum: ${MAX_LIMIT}. Default: ${DEFAULT_LIMIT$5}`,
6805
7006
  example: 10
6806
7007
  })
6807
7008
  });
@@ -6885,7 +7086,7 @@ async function getBook(params, db) {
6885
7086
  side: query.side,
6886
7087
  levels_count: levels.length,
6887
7088
  has_next_cursor: nextCursor != null,
6888
- first_level_price: firstLevel?.price.toString() ?? null,
7089
+ first_level_tick: firstLevel?.tick ?? null,
6889
7090
  first_level_assets: firstLevel?.assets.toString() ?? null,
6890
7091
  first_level_count: firstLevel?.count ?? null
6891
7092
  });
@@ -6978,7 +7179,7 @@ async function getConfigContracts(query, chainRegistry) {
6978
7179
  }
6979
7180
  function parseCursor$1(cursor) {
6980
7181
  const [chain, address] = cursor.split(":", 2);
6981
- if (!chain || !address) throw new BadRequestError("Cursor must be in the format chain_id:0x...");
7182
+ if (!chain || !address) throw new BadRequestError$1("Cursor must be in the format chain_id:0x...");
6982
7183
  return {
6983
7184
  chain_id: Number.parseInt(chain, 10),
6984
7185
  address: address.toLowerCase()
@@ -7037,6 +7238,33 @@ const assets = {
7037
7238
  "0x2260FAC5E5542a773Aa44fBCfeDf7C193bc2C599"
7038
7239
  ]
7039
7240
  };
7241
+ const collateralAssets = {
7242
+ [ChainId.ETHEREUM.toString()]: [
7243
+ "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
7244
+ "0x1aBaEA1f7C830bD89Acc67eC4af516284b1bC33c",
7245
+ "0x2260FAC5E5542a773Aa44fBCfeDf7C193bc2C599",
7246
+ "0x7f39C581F595B53c5cb19bD0b3f8dA6c935E2Ca0"
7247
+ ],
7248
+ [ChainId.BASE.toString()]: [
7249
+ "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
7250
+ "0x4200000000000000000000000000000000000006",
7251
+ "0xcbB7C0000aB88B473b1f5aFd9ef808440eed33Bf",
7252
+ "0xc1CBa3fCea344f92D9239c08C0568f6F2F0ee452",
7253
+ "0x60a3E35Cc302bFA44Cb288Bc5a4F316Fdb1adb42"
7254
+ ],
7255
+ [ChainId["ETHEREUM-VIRTUAL-TESTNET"].toString()]: [
7256
+ "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
7257
+ "0x1aBaEA1f7C830bD89Acc67eC4af516284b1bC33c",
7258
+ "0x2260FAC5E5542a773Aa44fBCfeDf7C193bc2C599",
7259
+ "0x7f39C581F595B53c5cb19bD0b3f8dA6c935E2Ca0"
7260
+ ],
7261
+ [ChainId.ANVIL.toString()]: [
7262
+ "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
7263
+ "0x1aBaEA1f7C830bD89Acc67eC4af516284b1bC33c",
7264
+ "0x2260FAC5E5542a773Aa44fBCfeDf7C193bc2C599",
7265
+ "0x7f39C581F595B53c5cb19bD0b3f8dA6c935E2Ca0"
7266
+ ]
7267
+ };
7040
7268
  const oracles = {
7041
7269
  [ChainId.ETHEREUM.toString()]: [
7042
7270
  "0xDddd770BADd886dF3864029e4B377B5F6a2B6b83",
@@ -7077,26 +7305,26 @@ const oracles = {
7077
7305
  const configs = {
7078
7306
  ethereum: {
7079
7307
  callbacks: [{ type: Type$1.BuyWithEmptyCallback }, { type: Type$1.SellWithEmptyCallback }],
7080
- maturities: [MaturityType.EndOfMonth, MaturityType.EndOfNextMonth]
7308
+ maturities: [MaturityType.EndOfWeek, MaturityType.EndOfNextWeek]
7081
7309
  },
7082
7310
  base: {
7083
7311
  callbacks: [{ type: Type$1.BuyWithEmptyCallback }, { type: Type$1.SellWithEmptyCallback }],
7084
- maturities: [MaturityType.EndOfMonth, MaturityType.EndOfNextMonth]
7312
+ maturities: [MaturityType.EndOfWeek, MaturityType.EndOfNextWeek]
7085
7313
  },
7086
7314
  "ethereum-virtual-testnet": {
7087
7315
  callbacks: [{ type: Type$1.BuyWithEmptyCallback }, { type: Type$1.SellWithEmptyCallback }],
7088
- maturities: [MaturityType.EndOfMonth, MaturityType.EndOfNextMonth]
7316
+ maturities: [MaturityType.EndOfWeek, MaturityType.EndOfNextWeek]
7089
7317
  },
7090
7318
  anvil: {
7091
7319
  callbacks: [{ type: Type$1.BuyWithEmptyCallback }, { type: Type$1.SellWithEmptyCallback }],
7092
- maturities: [MaturityType.EndOfMonth, MaturityType.EndOfNextMonth]
7320
+ maturities: [MaturityType.EndOfWeek, MaturityType.EndOfNextWeek]
7093
7321
  }
7094
7322
  };
7095
7323
 
7096
7324
  //#endregion
7097
7325
  //#region src/gatekeeper/ConfigRules.ts
7098
7326
  /**
7099
- * Build the configured rules (maturities + callback addresses + loan tokens + oracles) for the provided chains.
7327
+ * Build the configured rules (maturities + callback addresses + loan tokens + collateral tokens + oracles) for the provided chains.
7100
7328
  * @param chains - Chains to include in the configured rules.
7101
7329
  * @returns Sorted list of config rules.
7102
7330
  */
@@ -7116,6 +7344,12 @@ function buildConfigRules(chains) {
7116
7344
  chain_id: chain.id,
7117
7345
  address: normalizeAddress(address)
7118
7346
  });
7347
+ const collateralTokens = collateralAssets[chain.id.toString()] ?? [];
7348
+ for (const address of collateralTokens) rules.push({
7349
+ type: "collateral_token",
7350
+ chain_id: chain.id,
7351
+ address: normalizeAddress(address)
7352
+ });
7119
7353
  const oracles$2 = oracles[chain.id.toString()] ?? [];
7120
7354
  for (const address of oracles$2) rules.push({
7121
7355
  type: "oracle",
@@ -7143,6 +7377,10 @@ function buildConfigRulesChecksum(rules) {
7143
7377
  hash.update(`callback:${rule.chain_id}:${rule.callback_type}:${rule.address}\n`);
7144
7378
  continue;
7145
7379
  }
7380
+ if (rule.type === "collateral_token") {
7381
+ hash.update(`collateral_token:${rule.chain_id}:${rule.address}\n`);
7382
+ continue;
7383
+ }
7146
7384
  if (rule.type === "oracle") {
7147
7385
  hash.update(`oracle:${rule.chain_id}:${rule.address}\n`);
7148
7386
  continue;
@@ -7163,6 +7401,7 @@ function compareConfigRules(left, right) {
7163
7401
  return left.address.localeCompare(right.address);
7164
7402
  }
7165
7403
  if (left.type === "loan_token" && right.type === "loan_token") return left.address.localeCompare(right.address);
7404
+ if (left.type === "collateral_token" && right.type === "collateral_token") return left.address.localeCompare(right.address);
7166
7405
  if (left.type === "oracle" && right.type === "oracle") return left.address.localeCompare(right.address);
7167
7406
  return 0;
7168
7407
  }
@@ -7193,8 +7432,8 @@ async function getConfigRules(query, chains) {
7193
7432
  } catch (err) {
7194
7433
  return failure(err);
7195
7434
  }
7196
- if (cursorRule && typeFilter && !typeFilter.has(cursorRule.type)) return failure(new BadRequestError("Cursor type must match requested rule types"));
7197
- if (cursorRule && chainFilter && !chainFilter.has(cursorRule.chain_id)) return failure(new BadRequestError("Cursor chain_id must match requested chains"));
7435
+ if (cursorRule && typeFilter && !typeFilter.has(cursorRule.type)) return failure(new BadRequestError$1("Cursor type must match requested rule types"));
7436
+ if (cursorRule && chainFilter && !chainFilter.has(cursorRule.chain_id)) return failure(new BadRequestError$1("Cursor chain_id must match requested chains"));
7198
7437
  const startIndex = cursorRule ? findStartIndex(filteredRules, cursorRule) : 0;
7199
7438
  const page = filteredRules.slice(startIndex, startIndex + limit);
7200
7439
  const nextCursor = startIndex + limit < filteredRules.length && page.length > 0 ? formatCursor(page.at(-1)) : null;
@@ -7209,19 +7448,20 @@ function formatCursor(rule) {
7209
7448
  if (rule.type === "maturity") return `maturity:${rule.chain_id}:${rule.timestamp}:${rule.name}`;
7210
7449
  if (rule.type === "callback") return `callback:${rule.chain_id}:${rule.callback_type}:${rule.address.toLowerCase()}`;
7211
7450
  if (rule.type === "oracle") return `oracle:${rule.chain_id}:${rule.address.toLowerCase()}`;
7451
+ if (rule.type === "collateral_token") return `collateral_token:${rule.chain_id}:${rule.address.toLowerCase()}`;
7212
7452
  return `loan_token:${rule.chain_id}:${rule.address.toLowerCase()}`;
7213
7453
  }
7214
7454
  function parseCursor(cursor) {
7215
7455
  const [type, chain, ...rest] = cursor.split(":");
7216
- if (!type || !chain || rest.length === 0) throw new BadRequestError("Cursor must be in the format type:chain_id:<value>");
7217
- if (!isConfigRuleType(type)) throw new BadRequestError("Cursor has an invalid rule type");
7456
+ if (!type || !chain || rest.length === 0) throw new BadRequestError$1("Cursor must be in the format type:chain_id:<value>");
7457
+ if (!isConfigRuleType(type)) throw new BadRequestError$1("Cursor has an invalid rule type");
7218
7458
  const chain_id = Number.parseInt(chain, 10);
7219
- if (!Number.isFinite(chain_id)) throw new BadRequestError("Cursor has an invalid chain_id");
7459
+ if (!Number.isFinite(chain_id)) throw new BadRequestError$1("Cursor has an invalid chain_id");
7220
7460
  if (type === "maturity") {
7221
7461
  const timestampValue = Number.parseInt(rest[0] ?? "", 10);
7222
7462
  const nameValue = rest.slice(1).join(":");
7223
- if (!Number.isFinite(timestampValue) || nameValue.length === 0) throw new BadRequestError("Cursor must be in the format maturity:chain_id:timestamp:name");
7224
- if (!isMaturityType(nameValue)) throw new BadRequestError("Cursor has an invalid maturity name");
7463
+ if (!Number.isFinite(timestampValue) || nameValue.length === 0) throw new BadRequestError$1("Cursor must be in the format maturity:chain_id:timestamp:name");
7464
+ if (!isMaturityType(nameValue)) throw new BadRequestError$1("Cursor has an invalid maturity name");
7225
7465
  return {
7226
7466
  type,
7227
7467
  chain_id,
@@ -7232,8 +7472,8 @@ function parseCursor(cursor) {
7232
7472
  if (type === "callback") {
7233
7473
  const callbackTypeValue = rest[0] ?? "";
7234
7474
  const addressValue = rest.slice(1).join(":");
7235
- if (!callbackTypeValue || !addressValue) throw new BadRequestError("Cursor must be in the format callback:chain_id:callback_type:address");
7236
- if (!isCallbackType(callbackTypeValue)) throw new BadRequestError("Cursor has an invalid callback type");
7475
+ if (!callbackTypeValue || !addressValue) throw new BadRequestError$1("Cursor must be in the format callback:chain_id:callback_type:address");
7476
+ if (!isCallbackType(callbackTypeValue)) throw new BadRequestError$1("Cursor has an invalid callback type");
7237
7477
  return {
7238
7478
  type,
7239
7479
  chain_id,
@@ -7241,16 +7481,16 @@ function parseCursor(cursor) {
7241
7481
  address: parseAddress(addressValue, "Cursor address")
7242
7482
  };
7243
7483
  }
7244
- if (type === "loan_token" || type === "oracle") {
7484
+ if (type === "loan_token" || type === "collateral_token" || type === "oracle") {
7245
7485
  const addressValue = rest.join(":");
7246
- if (!addressValue) throw new BadRequestError(`Cursor must be in the format ${type}:chain_id:address`);
7486
+ if (!addressValue) throw new BadRequestError$1(`Cursor must be in the format ${type}:chain_id:address`);
7247
7487
  return {
7248
7488
  type,
7249
7489
  chain_id,
7250
7490
  address: parseAddress(addressValue, "Cursor address")
7251
7491
  };
7252
7492
  }
7253
- throw new BadRequestError("Cursor has an invalid rule type");
7493
+ throw new BadRequestError$1("Cursor has an invalid rule type");
7254
7494
  }
7255
7495
  function findStartIndex(rules, cursor) {
7256
7496
  let low = 0;
@@ -7264,11 +7504,11 @@ function findStartIndex(rules, cursor) {
7264
7504
  return low;
7265
7505
  }
7266
7506
  function parseAddress(address, label) {
7267
- if (!/^0x[a-fA-F0-9]{40}$/.test(address)) throw new BadRequestError(`${label} must be a valid 20-byte address`);
7507
+ if (!/^0x[a-fA-F0-9]{40}$/.test(address)) throw new BadRequestError$1(`${label} must be a valid 20-byte address`);
7268
7508
  return address.toLowerCase();
7269
7509
  }
7270
7510
  function isConfigRuleType(value) {
7271
- return value === "maturity" || value === "callback" || value === "loan_token" || value === "oracle";
7511
+ return value === "maturity" || value === "callback" || value === "loan_token" || value === "collateral_token" || value === "oracle";
7272
7512
  }
7273
7513
  function isMaturityType(value) {
7274
7514
  return Object.values(MaturityType).includes(value);
@@ -7277,7 +7517,7 @@ function parseMaturity(value) {
7277
7517
  try {
7278
7518
  return from$16(value);
7279
7519
  } catch (err) {
7280
- throw new BadRequestError(err instanceof Error ? err.message : "Invalid maturity timestamp");
7520
+ throw new BadRequestError$1(err instanceof Error ? err.message : "Invalid maturity timestamp");
7281
7521
  }
7282
7522
  }
7283
7523
  function isCallbackType(value) {
@@ -7373,7 +7613,7 @@ async function getHealth(query, db, chainRegistry) {
7373
7613
  try {
7374
7614
  const parsed = safeParse("get_health", query);
7375
7615
  if (!parsed.success) return failure(parsed.error);
7376
- const snapshot = await create$16({
7616
+ const snapshot = await create$17({
7377
7617
  db,
7378
7618
  chainRegistry
7379
7619
  }).getSnapshot();
@@ -7402,7 +7642,7 @@ async function getHealthChains(query, db, healthClients, chainRegistry) {
7402
7642
  try {
7403
7643
  const parsed = safeParse("get_health_chains", query);
7404
7644
  if (!parsed.success) return failure(parsed.error);
7405
- const snapshot = await create$16({
7645
+ const snapshot = await create$17({
7406
7646
  db,
7407
7647
  healthClients,
7408
7648
  chainRegistry
@@ -7434,7 +7674,7 @@ async function getHealthCollectors(query, db, chainRegistry) {
7434
7674
  try {
7435
7675
  const parsed = safeParse("get_health_collectors", query);
7436
7676
  if (!parsed.success) return failure(parsed.error);
7437
- const snapshot = await create$16({
7677
+ const snapshot = await create$17({
7438
7678
  db,
7439
7679
  chainRegistry
7440
7680
  }).getSnapshot();
@@ -7463,39 +7703,274 @@ async function getHealthCollectors(query, db, chainRegistry) {
7463
7703
  }
7464
7704
  }
7465
7705
 
7706
+ //#endregion
7707
+ //#region src/database/readers/ObligationsListing.ts
7708
+ const SORT_FIELDS = [
7709
+ "id",
7710
+ "ask",
7711
+ "bid",
7712
+ "maturity"
7713
+ ];
7714
+ const CURSOR_ID_REGEX = /^0x[a-f0-9]{64}$/i;
7715
+ const INT32_MIN = -2147483648;
7716
+ const INT32_MAX = 2147483647;
7717
+ const INT32_MAX_BIGINT = BigInt(INT32_MAX);
7718
+ const MAX_CURSOR_SORT_FIELDS = 4;
7719
+ const DEFAULT_LIMIT$4 = 20;
7720
+ var BadRequestError = class extends Error {
7721
+ constructor(message) {
7722
+ super(message);
7723
+ this.name = "ObligationsListingBadRequestError";
7724
+ }
7725
+ };
7726
+ /**
7727
+ * Creates the obligations listing reader facade.
7728
+ * @param parameters - Reader dependencies.
7729
+ * @returns Obligations listing reader.
7730
+ */
7731
+ function create$16(parameters) {
7732
+ const { db } = parameters;
7733
+ return { list: async (queryParameters) => {
7734
+ const { ids, chainId: chainIds, loanToken: loanTokens, collateralToken: collateralTokens, maturity: maturities, sort: sortTokens, cursor: encodedCursor, limit: requestedLimit } = queryParameters ?? {};
7735
+ const limit = requestedLimit ?? DEFAULT_LIMIT$4;
7736
+ if (!Number.isInteger(limit) || limit <= 0) throw new BadRequestError("Limit must be a positive integer");
7737
+ const cursorPayload = encodedCursor ? decodeCursorPayload(encodedCursor) : void 0;
7738
+ const requestedSort = normalizeSort(sortTokens);
7739
+ const cursorSort = cursorPayload ? normalizeSort(cursorPayload.sort) : void 0;
7740
+ if (cursorSort !== void 0 && sortTokens !== void 0 && !hasSameSort(requestedSort, cursorSort)) throw new BadRequestError("Cursor sort does not match requested sort");
7741
+ const sort = sortTokens !== void 0 ? requestedSort : cursorSort ?? requestedSort;
7742
+ const cursorValues = cursorPayload ? cursorValuesFromPayload(cursorPayload) : void 0;
7743
+ const now$3 = now();
7744
+ const loanTokenFilter = loanTokens !== void 0 && loanTokens.length > 0 ? sql`(${sql.join(loanTokens.map((token) => sql`LOWER(${obligations.loanToken}) = ${token.toLowerCase()}`), sql` OR `)})` : void 0;
7745
+ const collateralFilter = collateralTokens !== void 0 && collateralTokens.length > 0 ? sql`EXISTS (
7746
+ SELECT 1 FROM ${obligationCollateralsV2} oc
7747
+ WHERE oc.obligation_id = ${obligations.obligationId}
7748
+ AND (${sql.join(collateralTokens.map((token) => sql`LOWER(oc.asset) = ${token.toLowerCase()}`), sql` OR `)})
7749
+ )` : void 0;
7750
+ const bestAskTick = db.select({ askTick: offers.tick }).from(offers).innerJoin(groups, and(eq(offers.groupChainId, groups.chainId), eq(offers.groupMaker, groups.maker), eq(offers.group, groups.group))).leftJoin(validations, eq(offers.hash, validations.offerHash)).leftJoin(status, eq(validations.statusId, status.id)).where(and(eq(offers.obligationId, obligations.obligationId), eq(offers.buy, false), gte(offers.expiry, now$3), gte(offers.maturity, now$3), lte(offers.start, now$3), sql`(${status.code} IS NULL OR ${status.code} = ${Status.VALID})`)).orderBy(desc(offers.tick)).limit(1).as("best_ask_tick");
7751
+ const bestBidTick = db.select({ bidTick: offers.tick }).from(offers).innerJoin(groups, and(eq(offers.groupChainId, groups.chainId), eq(offers.groupMaker, groups.maker), eq(offers.group, groups.group))).leftJoin(validations, eq(offers.hash, validations.offerHash)).leftJoin(status, eq(validations.statusId, status.id)).where(and(eq(offers.obligationId, obligations.obligationId), eq(offers.buy, true), gte(offers.expiry, now$3), gte(offers.maturity, now$3), lte(offers.start, now$3), sql`(${status.code} IS NULL OR ${status.code} = ${Status.VALID})`)).orderBy(asc(offers.tick)).limit(1).as("best_bid_tick");
7752
+ const obligationsWithQuotes = db.select({
7753
+ obligationId: obligations.obligationId,
7754
+ chainId: obligations.chainId,
7755
+ loanToken: obligations.loanToken,
7756
+ collaterals: sql`ARRAY_AGG(jsonb_build_object('asset', ${obligationCollateralsV2.asset}, 'oracle', ${oracles$1.address}, 'lltv', ${obligationCollateralsV2.lltv}))`.as("collaterals"),
7757
+ maturity: obligations.maturity,
7758
+ askTick: sql`MAX(${bestAskTick.askTick})`.as("ask_tick"),
7759
+ bidTick: sql`MAX(${bestBidTick.bidTick})`.as("bid_tick"),
7760
+ ask: sql`COALESCE(MAX(${bestAskTick.askTick}) + 1, 0)`.as("ask"),
7761
+ bid: sql`COALESCE(MAX(${bestBidTick.bidTick}) + 1, 0)`.as("bid")
7762
+ }).from(obligations).innerJoin(obligationCollateralsV2, eq(obligations.obligationId, obligationCollateralsV2.obligationId)).innerJoin(oracles$1, sql`${obligationCollateralsV2.oracleChainId} = ${oracles$1.chainId}
7763
+ AND ${obligationCollateralsV2.oracleAddress} = ${oracles$1.address}`).leftJoinLateral(bestAskTick, sql`true`).leftJoinLateral(bestBidTick, sql`true`).groupBy(obligations.obligationId).where(and(ids !== void 0 && ids.length > 0 ? inArray(obligations.obligationId, ids) : void 0, chainIds !== void 0 && chainIds.length > 0 ? inArray(obligations.chainId, chainIds) : void 0, loanTokenFilter, maturities !== void 0 && maturities.length > 0 ? inArray(obligations.maturity, maturities) : gte(obligations.maturity, now$3), collateralFilter)).as("obligations_with_quotes");
7764
+ const sortColumns = {
7765
+ id: obligationsWithQuotes.obligationId,
7766
+ ask: obligationsWithQuotes.ask,
7767
+ bid: obligationsWithQuotes.bid,
7768
+ maturity: obligationsWithQuotes.maturity
7769
+ };
7770
+ const rows = await db.select({
7771
+ obligationId: obligationsWithQuotes.obligationId,
7772
+ chainId: obligationsWithQuotes.chainId,
7773
+ loanToken: obligationsWithQuotes.loanToken,
7774
+ collaterals: obligationsWithQuotes.collaterals,
7775
+ maturity: obligationsWithQuotes.maturity,
7776
+ askTick: obligationsWithQuotes.askTick,
7777
+ bidTick: obligationsWithQuotes.bidTick,
7778
+ ask: obligationsWithQuotes.ask,
7779
+ bid: obligationsWithQuotes.bid
7780
+ }).from(obligationsWithQuotes).where(buildCursorFilter(sortColumns, sort, cursorValues)).orderBy(...buildOrderBy(sortColumns, sort)).limit(limit + 1);
7781
+ const hasMore = rows.length > limit;
7782
+ const listedRows = (hasMore ? rows.slice(0, limit) : rows).map((row) => {
7783
+ return {
7784
+ obligation: from$15({
7785
+ chainId: row.chainId,
7786
+ loanToken: row.loanToken,
7787
+ collaterals: row.collaterals.sort((left, right) => left.asset.localeCompare(right.asset)).map((collateral) => from$17({
7788
+ asset: collateral.asset,
7789
+ oracle: collateral.oracle,
7790
+ lltv: from$18(BigInt(collateral.lltv))
7791
+ })),
7792
+ maturity: row.maturity
7793
+ }),
7794
+ quote: from$11({
7795
+ obligationId: row.obligationId,
7796
+ ask: { tick: row.askTick },
7797
+ bid: { tick: row.bidTick }
7798
+ }),
7799
+ cursorValues: {
7800
+ id: row.obligationId,
7801
+ ask: toBigInt(row.ask),
7802
+ bid: toBigInt(row.bid),
7803
+ maturity: row.maturity
7804
+ }
7805
+ };
7806
+ });
7807
+ const nextCursor = hasMore && listedRows.length > 0 ? encodeCursorPayload({
7808
+ sort: sortToTokens(sort),
7809
+ id: listedRows[listedRows.length - 1].cursorValues.id,
7810
+ ask: listedRows[listedRows.length - 1].cursorValues.ask.toString(),
7811
+ bid: listedRows[listedRows.length - 1].cursorValues.bid.toString(),
7812
+ maturity: listedRows[listedRows.length - 1].cursorValues.maturity
7813
+ }) : null;
7814
+ return {
7815
+ obligations: listedRows.map((row) => ({
7816
+ obligation: row.obligation,
7817
+ quote: row.quote
7818
+ })),
7819
+ nextCursor
7820
+ };
7821
+ } };
7822
+ }
7823
+ function isSortField(value) {
7824
+ return SORT_FIELDS.includes(value);
7825
+ }
7826
+ function parseSortToken(token) {
7827
+ const direction = token.startsWith("-") ? "desc" : "asc";
7828
+ const rawField = token.startsWith("-") ? token.slice(1) : token;
7829
+ if (!isSortField(rawField)) throw new BadRequestError(`Invalid sort field: ${rawField}`);
7830
+ return {
7831
+ field: rawField,
7832
+ direction
7833
+ };
7834
+ }
7835
+ function normalizeSort(sortTokens) {
7836
+ const parsed = sortTokens?.length ? sortTokens.map(parseSortToken) : [{
7837
+ field: "id",
7838
+ direction: "asc"
7839
+ }];
7840
+ return parsed.some((entry) => entry.field === "id") ? parsed : [...parsed, {
7841
+ field: "id",
7842
+ direction: "asc"
7843
+ }];
7844
+ }
7845
+ function sortToTokens(sortEntries) {
7846
+ return sortEntries.map((entry) => entry.direction === "desc" ? `-${entry.field}` : entry.field);
7847
+ }
7848
+ function hasSameSort(left, right) {
7849
+ if (left.length !== right.length) return false;
7850
+ return left.every((sortEntry, index) => sortEntry.field === right[index]?.field && sortEntry.direction === right[index]?.direction);
7851
+ }
7852
+ function decodeCursorPayload(cursor) {
7853
+ let decoded;
7854
+ try {
7855
+ decoded = JSON.parse(Buffer.from(cursor, "base64url").toString("utf8"));
7856
+ } catch {
7857
+ throw new BadRequestError("Invalid cursor format");
7858
+ }
7859
+ if (decoded === null || typeof decoded !== "object") throw new BadRequestError("Invalid cursor payload");
7860
+ const payload = decoded;
7861
+ const sortTokens = parseCursorSortTokens(payload.sort);
7862
+ if (typeof payload.id !== "string" || !CURSOR_ID_REGEX.test(payload.id)) throw new BadRequestError("Invalid cursor obligation id");
7863
+ const ask = parseCursorNonNegativeInt32(payload.ask, "ask");
7864
+ const bid = parseCursorNonNegativeInt32(payload.bid, "bid");
7865
+ if (!isInt32(payload.maturity)) throw new BadRequestError("Invalid cursor maturity value");
7866
+ return {
7867
+ sort: sortTokens,
7868
+ id: payload.id,
7869
+ ask,
7870
+ bid,
7871
+ maturity: payload.maturity
7872
+ };
7873
+ }
7874
+ function parseCursorSortTokens(value) {
7875
+ if (!Array.isArray(value) || value.length === 0 || value.length > MAX_CURSOR_SORT_FIELDS) throw new BadRequestError("Invalid cursor sort");
7876
+ const sortEntries = value.map((token) => {
7877
+ if (typeof token !== "string") throw new BadRequestError("Invalid cursor sort");
7878
+ try {
7879
+ return parseSortToken(token);
7880
+ } catch {
7881
+ throw new BadRequestError("Invalid cursor sort");
7882
+ }
7883
+ });
7884
+ if (new Set(sortEntries.map((entry) => entry.field)).size !== sortEntries.length) throw new BadRequestError("Invalid cursor sort");
7885
+ return sortToTokens(sortEntries);
7886
+ }
7887
+ function parseCursorNonNegativeInt32(value, field) {
7888
+ if (typeof value !== "string" || !/^\d+$/.test(value)) throw new BadRequestError(`Invalid cursor ${field} value`);
7889
+ if (BigInt(value) > INT32_MAX_BIGINT) throw new BadRequestError(`Invalid cursor ${field} value`);
7890
+ return value;
7891
+ }
7892
+ function isInt32(value) {
7893
+ return typeof value === "number" && Number.isSafeInteger(value) && value >= INT32_MIN && value <= INT32_MAX;
7894
+ }
7895
+ function encodeCursorPayload(payload) {
7896
+ return Buffer.from(JSON.stringify(payload), "utf8").toString("base64url");
7897
+ }
7898
+ function cursorValuesFromPayload(payload) {
7899
+ return {
7900
+ id: payload.id,
7901
+ ask: BigInt(payload.ask),
7902
+ bid: BigInt(payload.bid),
7903
+ maturity: payload.maturity
7904
+ };
7905
+ }
7906
+ function cursorComparisonValue(cursorValues, field) {
7907
+ switch (field) {
7908
+ case "id": return cursorValues.id;
7909
+ case "maturity": return cursorValues.maturity;
7910
+ case "ask": return cursorValues.ask.toString();
7911
+ case "bid": return cursorValues.bid.toString();
7912
+ }
7913
+ }
7914
+ function buildCursorFilter(columns, sortEntries, cursorValues) {
7915
+ if (cursorValues === void 0) return void 0;
7916
+ const comparisons = sortEntries.map((sortEntry, index) => {
7917
+ const equals = sortEntries.slice(0, index).map((previous) => {
7918
+ return sql`${columns[previous.field]} = ${cursorComparisonValue(cursorValues, previous.field)}`;
7919
+ });
7920
+ const comparison = sortEntry.direction === "asc" ? sql`${columns[sortEntry.field]} > ${cursorComparisonValue(cursorValues, sortEntry.field)}` : sql`${columns[sortEntry.field]} < ${cursorComparisonValue(cursorValues, sortEntry.field)}`;
7921
+ return equals.length > 0 ? sql`(${sql.join([...equals, comparison], sql` AND `)})` : sql`(${comparison})`;
7922
+ });
7923
+ return comparisons.length > 0 ? sql`(${sql.join(comparisons, sql` OR `)})` : void 0;
7924
+ }
7925
+ function buildOrderBy(columns, sortEntries) {
7926
+ return sortEntries.map((sortEntry) => sortEntry.direction === "asc" ? asc(columns[sortEntry.field]) : desc(columns[sortEntry.field]));
7927
+ }
7928
+ function toBigInt(value) {
7929
+ if (typeof value === "bigint") return value;
7930
+ if (typeof value === "number") return BigInt(value);
7931
+ return BigInt(value.split(".")[0] ?? "0");
7932
+ }
7933
+
7466
7934
  //#endregion
7467
7935
  //#region src/api/Controllers/getObligation.ts
7936
+ function toPayloadError$1(err) {
7937
+ if (err instanceof BadRequestError) return new BadRequestError$1(err.message);
7938
+ return err;
7939
+ }
7468
7940
  async function getObligation(params, db) {
7469
7941
  const logger = getLogger();
7470
7942
  const result = safeParse("get_obligation", params, (issue) => issue.message);
7471
7943
  if (!result.success) return failure(result.error);
7472
7944
  const query = result.data;
7473
7945
  try {
7474
- const { obligations } = await db.offers.getObligations({ ids: [query.obligation_id] });
7475
- if (obligations.length === 0) return failure(new NotFoundError("Obligation not found"));
7476
- const obligation = obligations[0];
7477
- const [quote] = await db.offers.getQuotes({ obligationIds: [id(obligation)] });
7946
+ const listing = await db.readers.obligations.list({
7947
+ ids: [query.obligation_id],
7948
+ limit: 1
7949
+ });
7950
+ if (listing.obligations.length === 0) return failure(new NotFoundError("Obligation not found"));
7951
+ const obligation = listing.obligations[0];
7478
7952
  return success({
7479
- data: from$4(obligation, quote ?? {
7480
- obligationId: id(obligation),
7481
- ask: { price: 0n },
7482
- bid: { price: 0n }
7483
- }),
7953
+ data: from$4(obligation.obligation, obligation.quote),
7484
7954
  cursor: null
7485
7955
  });
7486
7956
  } catch (err) {
7957
+ const payloadError = toPayloadError$1(err);
7487
7958
  logger.error({
7488
- err,
7959
+ err: payloadError,
7489
7960
  msg: "Error get obligation",
7490
- errorMessage: err instanceof Error ? err.message : String(err),
7491
- errorStack: err instanceof Error ? err.stack : void 0
7961
+ errorMessage: payloadError instanceof Error ? payloadError.message : String(payloadError),
7962
+ errorStack: payloadError instanceof Error ? payloadError.stack : void 0
7492
7963
  });
7493
- return failure(err);
7964
+ return failure(payloadError);
7494
7965
  }
7495
7966
  }
7496
7967
 
7497
7968
  //#endregion
7498
7969
  //#region src/api/Controllers/getObligations.ts
7970
+ function toPayloadError(err) {
7971
+ if (err instanceof BadRequestError) return new BadRequestError$1(err.message);
7972
+ return err;
7973
+ }
7499
7974
  async function getObligations$1(queryParameters, db) {
7500
7975
  const logger = getLogger();
7501
7976
  const result = safeParse("get_obligations", queryParameters, (issue) => issue.message);
@@ -7506,31 +7981,28 @@ async function getObligations$1(queryParameters, db) {
7506
7981
  const loanTokens = query.loan_tokens?.length ? query.loan_tokens : void 0;
7507
7982
  const collateralTokens = query.collateral_tokens?.length ? query.collateral_tokens : void 0;
7508
7983
  const maturities = query.maturities?.length ? query.maturities : void 0;
7509
- const { obligations, nextCursor } = await db.offers.getObligations({
7510
- cursor: query.cursor,
7511
- limit: query.limit,
7984
+ const listing = await db.readers.obligations.list({
7512
7985
  chainId: chainIds,
7513
7986
  loanToken: loanTokens,
7514
7987
  collateralToken: collateralTokens,
7515
- maturity: maturities
7988
+ maturity: maturities,
7989
+ sort: query.sort,
7990
+ cursor: query.cursor,
7991
+ limit: query.limit
7516
7992
  });
7517
- const quotes = await db.offers.getQuotes({ obligationIds: obligations.map((o) => id(o)) });
7518
7993
  return success({
7519
- data: obligations.map((o) => from$4(o, quotes.find((q) => q.obligationId === id(o)) ?? {
7520
- obligationId: id(o),
7521
- ask: { price: 0n },
7522
- bid: { price: 0n }
7523
- })),
7524
- cursor: nextCursor ?? null
7994
+ data: listing.obligations.map((item) => from$4(item.obligation, item.quote)),
7995
+ cursor: listing.nextCursor
7525
7996
  });
7526
7997
  } catch (err) {
7998
+ const payloadError = toPayloadError(err);
7527
7999
  logger.error({
7528
- err,
8000
+ err: payloadError,
7529
8001
  msg: "Error get obligations",
7530
- errorMessage: err instanceof Error ? err.message : String(err),
7531
- errorStack: err instanceof Error ? err.stack : void 0
8002
+ errorMessage: payloadError instanceof Error ? payloadError.message : String(payloadError),
8003
+ errorStack: payloadError instanceof Error ? payloadError.stack : void 0
7532
8004
  });
7533
- return failure(err);
8005
+ return failure(payloadError);
7534
8006
  }
7535
8007
  }
7536
8008
 
@@ -7564,6 +8036,7 @@ function create$15(config) {
7564
8036
  groupMaker: offer.maker.toLowerCase(),
7565
8037
  callbackAddress: offer.callback.address.toLowerCase(),
7566
8038
  callbackData: offer.callback.data,
8039
+ receiverIfMakerIsSeller: offer.receiverIfMakerIsSeller.toLowerCase(),
7567
8040
  blockNumber
7568
8041
  })));
7569
8042
  if (offersRows.length === 0) return [];
@@ -7615,7 +8088,7 @@ function create$15(config) {
7615
8088
  assets: offers.assets,
7616
8089
  obligationUnits: offers.obligationUnits,
7617
8090
  obligationShares: offers.obligationShares,
7618
- price: offers.price,
8091
+ tick: offers.tick,
7619
8092
  maturity: offers.maturity,
7620
8093
  expiry: offers.expiry,
7621
8094
  start: offers.start,
@@ -7626,16 +8099,18 @@ function create$15(config) {
7626
8099
  loanToken: obligations.loanToken,
7627
8100
  callbackAddress: offers.callbackAddress,
7628
8101
  callbackData: offers.callbackData,
8102
+ receiverIfMakerIsSeller: offers.receiverIfMakerIsSeller,
7629
8103
  collaterals: collateralsLateral.collaterals,
7630
8104
  blockNumber: offers.blockNumber
7631
8105
  }).from(offers).innerJoin(obligations, eq(offers.obligationId, obligations.obligationId)).innerJoinLateral(collateralsLateral, sql`true`).where(and(cursor !== null && cursor !== void 0 ? gt(offers.hash, cursor) : void 0, maker !== void 0 ? eq(offers.groupMaker, maker.toLowerCase()) : void 0)).orderBy(asc(offers.hash)).limit(limit)).map((row) => {
8106
+ const receiverIfMakerIsSeller = (row.receiverIfMakerIsSeller ?? row.maker).toLowerCase();
7632
8107
  return {
7633
8108
  hash: row.hash,
7634
8109
  maker: row.maker,
7635
8110
  assets: BigInt(row.assets),
7636
8111
  obligationUnits: BigInt(row.obligationUnits),
7637
8112
  obligationShares: BigInt(row.obligationShares),
7638
- price: BigInt(row.price),
8113
+ tick: row.tick,
7639
8114
  maturity: from$16(row.maturity),
7640
8115
  expiry: row.expiry,
7641
8116
  start: row.start,
@@ -7653,6 +8128,7 @@ function create$15(config) {
7653
8128
  address: row.callbackAddress,
7654
8129
  data: row.callbackData
7655
8130
  },
8131
+ receiverIfMakerIsSeller,
7656
8132
  consumed: 0n,
7657
8133
  available: 0n,
7658
8134
  takeable: 0n,
@@ -7677,64 +8153,30 @@ function create$15(config) {
7677
8153
  }
7678
8154
  throw new Error("Invalid parameters");
7679
8155
  },
7680
- getObligations: async (parameters) => {
7681
- const { ids, chainId: chainIds, loanToken: loanTokens, collateralToken: collateralTokens, maturity: maturities, cursor, limit = DEFAULT_LIMIT$3 } = parameters ?? {};
7682
- const now$1 = now();
7683
- const loanTokenFilter = loanTokens !== void 0 && loanTokens.length > 0 ? sql`(${sql.join(loanTokens.map((token) => sql`LOWER(${obligations.loanToken}) = ${token.toLowerCase()}`), sql` OR `)})` : void 0;
7684
- const collateralFilter = collateralTokens !== void 0 && collateralTokens.length > 0 ? sql`EXISTS (
7685
- SELECT 1 FROM ${obligationCollateralsV2} oc
7686
- WHERE oc.obligation_id = ${obligations.obligationId}
7687
- AND (${sql.join(collateralTokens.map((token) => sql`LOWER(oc.asset) = ${token.toLowerCase()}`), sql` OR `)})
7688
- )` : void 0;
7689
- const result = await db.select({
7690
- obligationId: obligations.obligationId,
7691
- chainId: obligations.chainId,
7692
- loanToken: obligations.loanToken,
7693
- collaterals: sql`ARRAY_AGG(jsonb_build_object('asset', ${obligationCollateralsV2.asset}, 'oracle', ${oracles$1.address}, 'lltv', ${obligationCollateralsV2.lltv}))`.as("collaterals"),
7694
- maturity: obligations.maturity
7695
- }).from(obligations).innerJoin(obligationCollateralsV2, eq(obligations.obligationId, obligationCollateralsV2.obligationId)).innerJoin(oracles$1, sql`${obligationCollateralsV2.oracleChainId} = ${oracles$1.chainId}
7696
- AND ${obligationCollateralsV2.oracleAddress} = ${oracles$1.address}`).groupBy(obligations.obligationId).where(and(cursor !== null && cursor !== void 0 ? gt(obligations.obligationId, cursor) : sql`true`, ids !== void 0 && ids.length > 0 ? inArray(obligations.obligationId, ids) : void 0, chainIds !== void 0 && chainIds.length > 0 ? inArray(obligations.chainId, chainIds) : void 0, loanTokenFilter, maturities !== void 0 && maturities.length > 0 ? inArray(obligations.maturity, maturities) : gte(obligations.maturity, now$1), collateralFilter)).orderBy(asc(obligations.obligationId)).limit(limit);
7697
- const items = [];
7698
- for (const row of result) items.push(from$15({
7699
- chainId: row.chainId,
7700
- loanToken: row.loanToken,
7701
- collaterals: row.collaterals.sort((a, b) => a.asset.localeCompare(b.asset)).map((c) => from$17({
7702
- asset: c.asset,
7703
- oracle: c.oracle,
7704
- lltv: from$18(BigInt(c.lltv))
7705
- })),
7706
- maturity: row.maturity
7707
- }));
7708
- const returnedItems = Array.from(items.values());
7709
- return {
7710
- obligations: returnedItems,
7711
- nextCursor: returnedItems.length === limit && returnedItems.length > 0 ? result[result.length - 1].obligationId : null
7712
- };
7713
- },
7714
8156
  getQuotes: async (parameters) => {
7715
8157
  const { obligationIds } = parameters;
7716
8158
  if (obligationIds.length === 0) return [];
7717
8159
  const now$2 = now();
7718
8160
  const query = ({ side }) => db.selectDistinctOn([offers.obligationId], {
7719
8161
  obligationId: offers.obligationId,
7720
- price: offers.price
7721
- }).from(offers).innerJoin(groups, and(eq(offers.groupChainId, groups.chainId), eq(offers.groupMaker, groups.maker), eq(offers.group, groups.group))).leftJoin(validations, eq(offers.hash, validations.offerHash)).leftJoin(status, eq(validations.statusId, status.id)).where(and(inArray(offers.obligationId, obligationIds), eq(offers.buy, side === "buy"), gte(offers.expiry, now$2), gte(offers.maturity, now$2), lte(offers.start, now$2), sql`(${status.code} IS NULL OR ${status.code} = ${Status.VALID})`)).orderBy(offers.obligationId, side === "buy" ? sql`${offers.price}::numeric ASC` : sql`${offers.price}::numeric DESC`);
8162
+ tick: offers.tick
8163
+ }).from(offers).innerJoin(groups, and(eq(offers.groupChainId, groups.chainId), eq(offers.groupMaker, groups.maker), eq(offers.group, groups.group))).leftJoin(validations, eq(offers.hash, validations.offerHash)).leftJoin(status, eq(validations.statusId, status.id)).where(and(inArray(offers.obligationId, obligationIds), eq(offers.buy, side === "buy"), gte(offers.expiry, now$2), gte(offers.maturity, now$2), lte(offers.start, now$2), sql`(${status.code} IS NULL OR ${status.code} = ${Status.VALID})`)).orderBy(offers.obligationId, side === "buy" ? sql`${offers.tick} ASC` : sql`${offers.tick} DESC`);
7722
8164
  const [bestBuys, bestSells] = await Promise.all([query({ side: "buy" }), query({ side: "sell" })]);
7723
8165
  const quotes = /* @__PURE__ */ new Map();
7724
8166
  for (const row of bestSells) quotes.set(row.obligationId, {
7725
- ask: { price: BigInt(row.price) },
7726
- bid: { price: 0n }
8167
+ ask: { tick: row.tick },
8168
+ bid: { tick: null }
7727
8169
  });
7728
8170
  for (const row of bestBuys) {
7729
8171
  const quote = quotes.get(row.obligationId);
7730
8172
  if (!quote) {
7731
8173
  quotes.set(row.obligationId, {
7732
- ask: { price: 0n },
7733
- bid: { price: BigInt(row.price) }
8174
+ ask: { tick: null },
8175
+ bid: { tick: row.tick }
7734
8176
  });
7735
8177
  continue;
7736
8178
  }
7737
- quote.bid = { price: BigInt(row.price) };
8179
+ quote.bid = { tick: row.tick };
7738
8180
  }
7739
8181
  return Array.from(quotes.entries()).map(([id, quote]) => {
7740
8182
  return from$11({
@@ -7834,7 +8276,7 @@ async function getOffersQuery(db, parameters) {
7834
8276
  obligationUnits: offers.obligationUnits,
7835
8277
  obligationShares: offers.obligationShares,
7836
8278
  consumed: groups.consumed,
7837
- price: offers.price,
8279
+ tick: offers.tick,
7838
8280
  maturity: offers.maturity,
7839
8281
  expiry: offers.expiry,
7840
8282
  start: offers.start,
@@ -7845,6 +8287,7 @@ async function getOffersQuery(db, parameters) {
7845
8287
  loanToken: obligations.loanToken,
7846
8288
  callbackAddress: offers.callbackAddress,
7847
8289
  callbackData: offers.callbackData,
8290
+ receiverIfMakerIsSeller: offers.receiverIfMakerIsSeller,
7848
8291
  collaterals: collateralsLateral.collaterals,
7849
8292
  blockNumber: offers.blockNumber,
7850
8293
  available: sql`${availableExpr}::numeric`.as("available"),
@@ -7866,13 +8309,14 @@ async function getOffersQuery(db, parameters) {
7866
8309
  )
7867
8310
  END
7868
8311
  ) > 0` : void 0)).orderBy(asc(offers.hash)).limit(limit)).map((row) => {
8312
+ const receiverIfMakerIsSeller = (row.receiverIfMakerIsSeller ?? row.maker).toLowerCase();
7869
8313
  return {
7870
8314
  hash: row.hash,
7871
8315
  maker: row.maker,
7872
8316
  assets: BigInt(row.assets),
7873
8317
  obligationUnits: BigInt(row.obligationUnits),
7874
8318
  obligationShares: BigInt(row.obligationShares),
7875
- price: BigInt(row.price),
8319
+ tick: row.tick,
7876
8320
  maturity: from$16(row.maturity),
7877
8321
  expiry: row.expiry,
7878
8322
  start: row.start,
@@ -7890,6 +8334,7 @@ async function getOffersQuery(db, parameters) {
7890
8334
  address: row.callbackAddress,
7891
8335
  data: row.callbackData
7892
8336
  },
8337
+ receiverIfMakerIsSeller,
7893
8338
  consumed: BigInt(row.consumed),
7894
8339
  available: BigInt(String(row.available ?? "0").split(".")[0] ?? "0"),
7895
8340
  takeable: BigInt(String(row.takeable ?? "0").split(".")[0] ?? "0"),
@@ -7980,7 +8425,7 @@ async function getUserPositions(queryParameters, db) {
7980
8425
  async function validateOffers(body, gatekeeper) {
7981
8426
  const logger = getLogger();
7982
8427
  const result = safeParse("validate_offers", body, (issue) => issue.message);
7983
- if (!result.success) return failure(new BadRequestError(result.error.issues[0]?.message ?? "Invalid request body"));
8428
+ if (!result.success) return failure(new BadRequestError$1(result.error.issues[0]?.message ?? "Invalid request body"));
7984
8429
  const { offers: rawOffers } = result.data;
7985
8430
  const parsedOffers = [];
7986
8431
  const offerIndexByHash = /* @__PURE__ */ new Map();
@@ -7996,7 +8441,7 @@ async function validateOffers(body, gatekeeper) {
7996
8441
  } catch (err) {
7997
8442
  let message = err instanceof Error ? err.message : String(err);
7998
8443
  if (err instanceof InvalidOfferError) message = err.formattedMessage;
7999
- return failure(new BadRequestError(`Offer at index ${i} failed to parse: ${message}`));
8444
+ return failure(new BadRequestError$1(`Offer at index ${i} failed to parse: ${message}`));
8000
8445
  }
8001
8446
  }
8002
8447
  try {
@@ -8271,7 +8716,7 @@ async function getOffers(apiClient, parameters) {
8271
8716
  assets: offerData.assets,
8272
8717
  obligation_units: offerData.obligation_units,
8273
8718
  obligation_shares: offerData.obligation_shares,
8274
- price: offerData.price,
8719
+ tick: offerData.tick,
8275
8720
  maturity: from$16(offerData.obligation.maturity),
8276
8721
  expiry: offerData.expiry,
8277
8722
  start: offerData.start,
@@ -8288,7 +8733,8 @@ async function getOffers(apiClient, parameters) {
8288
8733
  callback: {
8289
8734
  address: offerData.callback,
8290
8735
  data: offerData.callback_data
8291
- }
8736
+ },
8737
+ receiver_if_maker_is_seller: offerData.receiver_if_maker_is_seller
8292
8738
  }),
8293
8739
  hash: item.offer_hash,
8294
8740
  consumed: BigInt(item.consumed),
@@ -8305,13 +8751,15 @@ async function getOffers(apiClient, parameters) {
8305
8751
  };
8306
8752
  }
8307
8753
  async function getObligations(apiClient, parameters) {
8754
+ const sort = parameters?.sort?.length ? parameters.sort.join(",") : void 0;
8308
8755
  const { data, error, response } = await apiClient.GET("/v1/obligations", { params: { query: {
8309
8756
  cursor: parameters?.cursor,
8310
8757
  limit: parameters?.limit,
8311
8758
  chains: parameters?.chainIds,
8312
8759
  loan_tokens: parameters?.loanTokens,
8313
8760
  collateral_tokens: parameters?.collateralTokens,
8314
- maturities: parameters?.maturities
8761
+ maturities: parameters?.maturities,
8762
+ sort
8315
8763
  } } });
8316
8764
  if (error !== void 0) {
8317
8765
  switch (response.status) {
@@ -8335,10 +8783,10 @@ async function getObligations(apiClient, parameters) {
8335
8783
  const { obligationId: _, ...returned } = {
8336
8784
  id: () => id(obligation),
8337
8785
  ...obligation,
8338
- ...fromSnakeCase({
8339
- obligation_id: item.id,
8340
- ask: item.ask,
8341
- bid: item.bid
8786
+ ...from$11({
8787
+ obligationId: item.id,
8788
+ ask: { tick: item.ask.tick },
8789
+ bid: { tick: item.bid.tick }
8342
8790
  })
8343
8791
  };
8344
8792
  return returned;
@@ -8630,6 +9078,7 @@ function create$12(config) {
8630
9078
  return {
8631
9079
  get: async (parameters) => {
8632
9080
  const { side, obligationId, cursor: cursorString, limit = DEFAULT_LIMIT$2 } = parameters;
9081
+ const tickSortDirection = side === "sell" ? "asc" : "desc";
8633
9082
  const inputCursor = LevelCursor.decode(cursorString, logger);
8634
9083
  if (cursorString != null && inputCursor === null) return {
8635
9084
  levels: [],
@@ -8644,23 +9093,23 @@ function create$12(config) {
8644
9093
  cursor: inputCursor?.offersCursor ?? void 0,
8645
9094
  limit: fetchLimit
8646
9095
  });
8647
- const priceMap = /* @__PURE__ */ new Map();
9096
+ const tickMap = /* @__PURE__ */ new Map();
8648
9097
  for (const row of rows) {
8649
- const priceKey = row.price.toString();
8650
- const existing = priceMap.get(priceKey);
9098
+ const existing = tickMap.get(row.tick);
8651
9099
  if (existing) {
8652
9100
  existing.assets += row.takeable;
8653
9101
  existing.count += 1;
8654
- } else priceMap.set(priceKey, {
9102
+ } else tickMap.set(row.tick, {
8655
9103
  assets: row.takeable,
8656
9104
  count: 1
8657
9105
  });
8658
9106
  }
8659
- const levels = Array.from(priceMap.entries()).map(([price, data]) => ({
8660
- price: BigInt(price),
8661
- assets: data.assets,
8662
- count: data.count
9107
+ const levels = Array.from(tickMap.entries()).map(([tick, level]) => ({
9108
+ tick,
9109
+ assets: level.assets,
9110
+ count: level.count
8663
9111
  }));
9112
+ levels.sort((a, b) => tickSortDirection === "asc" ? a.tick - b.tick : b.tick - a.tick);
8664
9113
  const paginatedLevels = levels.slice(0, limit);
8665
9114
  const hasMore = levels.length > limit || offersNextCursor !== null;
8666
9115
  const lastLevel = paginatedLevels[paginatedLevels.length - 1];
@@ -8706,14 +9155,14 @@ async function _getOffers(db, params) {
8706
9155
  AND (s.code IS NULL OR s.code = ${Status.VALID})
8707
9156
  ORDER BY
8708
9157
  o.group_chain_id, o.group_maker, o."group_group",
8709
- o.price::numeric ${priceSortDirection === "asc" ? sql`ASC` : sql`DESC`}, o.block_number ASC, o.assets DESC, o.hash ASC
9158
+ o.tick ${priceSortDirection === "asc" ? sql`ASC` : sql`DESC`}, o.block_number ASC, o.assets DESC, o.hash ASC
8710
9159
  ),
8711
9160
  enriched AS (
8712
9161
  SELECT
8713
9162
  w.*,
8714
9163
  g.consumed, g.chain_id, obl.loan_token,
8715
9164
  CASE WHEN ${priceSortDirection === "asc" ? sql`TRUE` : sql`FALSE`}
8716
- THEN w.price::numeric ELSE -w.price::numeric END AS price_norm,
9165
+ THEN w.tick ELSE -w.tick END AS tick_norm,
8717
9166
  w.block_number AS block_norm,
8718
9167
  -w.assets AS assets_norm,
8719
9168
  w.hash AS hash_norm
@@ -8730,33 +9179,35 @@ async function _getOffers(db, params) {
8730
9179
  FROM enriched e
8731
9180
  ${cursor != null ? sql`
8732
9181
  WHERE
8733
- (e.price_norm, e.block_norm, e.assets_norm, e.hash_norm)
9182
+ (e.tick_norm, e.block_norm, e.assets_norm, e.hash_norm)
8734
9183
  > (
8735
9184
  CASE WHEN ${priceSortDirection === "asc" ? sql`TRUE` : sql`FALSE`}
8736
- THEN ${cursor.price}::numeric ELSE -${cursor.price}::numeric END,
9185
+ THEN ${cursor.tick}::integer ELSE -${cursor.tick}::integer END,
8737
9186
  ${cursor.blockNumber},
8738
9187
  -${cursor.assets}::numeric,
8739
9188
  ${cursor.hash}
8740
9189
  )` : sql``}
8741
- ORDER BY e.price::numeric ${priceSortDirection === "asc" ? sql`ASC` : sql`DESC`}, e.block_number ASC, e.assets DESC, e.hash ASC
9190
+ ORDER BY e.tick ${priceSortDirection === "asc" ? sql`ASC` : sql`DESC`}, e.block_number ASC, e.assets DESC, e.hash ASC
8742
9191
  LIMIT ${limit}
8743
9192
  ),
8744
- -- Compute sum of offsets per position
9193
+ -- Compute sum of offsets per position and obligation
8745
9194
  position_offsets AS (
8746
9195
  SELECT
8747
9196
  chain_id,
8748
9197
  "user",
8749
9198
  contract,
9199
+ obligation_id,
8750
9200
  SUM(value::numeric) AS total_offset
8751
9201
  FROM ${offsets}
8752
- GROUP BY chain_id, "user", contract
9202
+ GROUP BY chain_id, "user", contract, obligation_id
8753
9203
  ),
8754
- -- Compute position_consumed: sum of consumed from all groups with lots on each position (converted to lot terms)
9204
+ -- Compute position_consumed: sum of consumed from all groups with lots on each position+obligation (converted to lot terms)
8755
9205
  position_consumed AS (
8756
9206
  SELECT
8757
9207
  l.chain_id,
8758
9208
  l.contract,
8759
9209
  l."user",
9210
+ l.obligation_id,
8760
9211
  SUM(
8761
9212
  CASE
8762
9213
  WHEN wo.assets::numeric > 0
@@ -8773,7 +9224,7 @@ async function _getOffers(db, params) {
8773
9224
  ON wo.group_chain_id = g.chain_id
8774
9225
  AND LOWER(wo.group_maker) = LOWER(g.maker)
8775
9226
  AND wo.group_group = g."group"
8776
- GROUP BY l.chain_id, l.contract, l."user"
9227
+ GROUP BY l.chain_id, l.contract, l."user", l.obligation_id
8777
9228
  ),
8778
9229
  -- Compute callback contributions with lot balance
8779
9230
  callback_contributions AS (
@@ -8781,7 +9232,7 @@ async function _getOffers(db, params) {
8781
9232
  p.hash,
8782
9233
  p.obligation_id,
8783
9234
  p.assets,
8784
- p.price,
9235
+ p.tick,
8785
9236
  p.obligation_units,
8786
9237
  p.obligation_shares,
8787
9238
  p.maturity,
@@ -8791,6 +9242,7 @@ async function _getOffers(db, params) {
8791
9242
  p.buy,
8792
9243
  p.callback_address,
8793
9244
  p.callback_data,
9245
+ p.receiver_if_maker_is_seller,
8794
9246
  p.block_number,
8795
9247
  p.group_chain_id,
8796
9248
  p.group_maker,
@@ -8826,6 +9278,7 @@ async function _getOffers(db, params) {
8826
9278
  AND LOWER(l.contract) = LOWER(c.position_contract)
8827
9279
  AND LOWER(l."user") = LOWER(c.position_user)
8828
9280
  AND l."group" = p.group_group
9281
+ AND l.obligation_id = p.obligation_id
8829
9282
  LEFT JOIN ${positions} pos
8830
9283
  ON pos.chain_id = c.position_chain_id
8831
9284
  AND LOWER(pos.contract) = LOWER(c.position_contract)
@@ -8834,10 +9287,12 @@ async function _getOffers(db, params) {
8834
9287
  ON pos_offsets.chain_id = c.position_chain_id
8835
9288
  AND LOWER(pos_offsets.contract) = LOWER(c.position_contract)
8836
9289
  AND LOWER(pos_offsets."user") = LOWER(c.position_user)
9290
+ AND pos_offsets.obligation_id = p.obligation_id
8837
9291
  LEFT JOIN position_consumed pc
8838
9292
  ON pc.chain_id = c.position_chain_id
8839
9293
  AND LOWER(pc.contract) = LOWER(c.position_contract)
8840
9294
  AND LOWER(pc."user") = LOWER(c.position_user)
9295
+ AND pc.obligation_id = p.obligation_id
8841
9296
  ),
8842
9297
  -- Compute contribution per callback in loan terms (loan token only — collateral positions are not indexed)
8843
9298
  callback_loan_contribution AS (
@@ -8855,7 +9310,7 @@ async function _getOffers(db, params) {
8855
9310
  hash,
8856
9311
  obligation_id,
8857
9312
  assets,
8858
- price,
9313
+ tick,
8859
9314
  obligation_units,
8860
9315
  obligation_shares,
8861
9316
  maturity,
@@ -8865,6 +9320,7 @@ async function _getOffers(db, params) {
8865
9320
  buy,
8866
9321
  callback_address,
8867
9322
  callback_data,
9323
+ receiver_if_maker_is_seller,
8868
9324
  block_number,
8869
9325
  group_chain_id,
8870
9326
  group_maker,
@@ -8881,16 +9337,17 @@ async function _getOffers(db, params) {
8881
9337
  WHERE clc.callback_id IS NOT NULL
8882
9338
  ORDER BY clc.hash, clc.position_chain_id, clc.position_contract, clc.position_user, clc.contribution_in_loan DESC
8883
9339
  ) deduped
8884
- GROUP BY hash, obligation_id, assets, price, obligation_units, obligation_shares, maturity, expiry, start, group_group, buy,
8885
- callback_address, callback_data, block_number, group_chain_id, group_maker,
8886
- consumed, chain_id, loan_token, session
9340
+ GROUP BY hash, obligation_id, assets, tick, obligation_units, obligation_shares, maturity, expiry, start, group_group, buy,
9341
+ callback_address, callback_data, block_number, group_chain_id, group_maker,
9342
+ consumed, chain_id, loan_token, session, receiver_if_maker_is_seller
8887
9343
  UNION ALL
8888
9344
  -- Sell offers without callbacks: collateral positions not indexed, takeable = assets - consumed
8889
9345
  SELECT
8890
- p.hash, p.obligation_id, p.assets, p.price,
9346
+ p.hash, p.obligation_id, p.assets, p.tick,
8891
9347
  p.obligation_units, p.obligation_shares,
8892
9348
  p.maturity, p.expiry, p.start, p.group_group,
8893
9349
  p.buy, p.callback_address, p.callback_data,
9350
+ p.receiver_if_maker_is_seller,
8894
9351
  p.block_number, p.group_chain_id, p.group_maker,
8895
9352
  p.consumed, p.chain_id, p.loan_token, p.session,
8896
9353
  0 AS total_available
@@ -8909,7 +9366,7 @@ async function _getOffers(db, params) {
8909
9366
  oc.obligation_units,
8910
9367
  oc.obligation_shares,
8911
9368
  oc.consumed,
8912
- oc.price,
9369
+ oc.tick,
8913
9370
  oc.maturity,
8914
9371
  oc.expiry,
8915
9372
  oc.start,
@@ -8919,6 +9376,7 @@ async function _getOffers(db, params) {
8919
9376
  oc.loan_token,
8920
9377
  oc.callback_address,
8921
9378
  oc.callback_data,
9379
+ oc.receiver_if_maker_is_seller,
8922
9380
  oc.block_number,
8923
9381
  oc.session,
8924
9382
  COALESCE(oc.total_available, 0) AS available,
@@ -8941,20 +9399,21 @@ async function _getOffers(db, params) {
8941
9399
  ))
8942
9400
  END > 0
8943
9401
  ORDER BY
8944
- oc.price::numeric ${priceSortDirection === "asc" ? sql`ASC` : sql`DESC`},
9402
+ oc.tick ${priceSortDirection === "asc" ? sql`ASC` : sql`DESC`},
8945
9403
  oc.block_number ASC,
8946
9404
  oc.assets DESC,
8947
9405
  oc.hash ASC;
8948
9406
  `);
8949
9407
  return {
8950
9408
  rows: raw.rows.map((row) => {
9409
+ const receiverIfMakerIsSeller = (row.receiver_if_maker_is_seller ?? row.group_maker).toLowerCase();
8951
9410
  return {
8952
9411
  hash: row.hash,
8953
9412
  maker: row.group_maker,
8954
9413
  assets: BigInt(row.assets),
8955
9414
  obligationUnits: BigInt(row.obligation_units ?? 0),
8956
9415
  obligationShares: BigInt(row.obligation_shares ?? 0),
8957
- price: BigInt(row.price),
9416
+ tick: row.tick,
8958
9417
  maturity: row.maturity,
8959
9418
  expiry: row.expiry,
8960
9419
  start: row.start,
@@ -8972,6 +9431,7 @@ async function _getOffers(db, params) {
8972
9431
  address: row.callback_address,
8973
9432
  data: row.callback_data
8974
9433
  },
9434
+ receiverIfMakerIsSeller,
8975
9435
  blockNumber: row.block_number,
8976
9436
  consumed: BigInt(row.consumed ?? 0),
8977
9437
  available: BigInt(String(row.available ?? "0").split(".")[0] ?? "0"),
@@ -8986,7 +9446,7 @@ let Cursor;
8986
9446
  function encode(row, totalReturned, now, side) {
8987
9447
  return Buffer.from(JSON.stringify({
8988
9448
  side,
8989
- price: row.price.toString(),
9449
+ tick: row.tick,
8990
9450
  blockNumber: row.blockNumber,
8991
9451
  assets: row.assets.toString(),
8992
9452
  hash: row.hash,
@@ -8997,10 +9457,9 @@ let Cursor;
8997
9457
  _Cursor.encode = encode;
8998
9458
  function decode(cursorString, logger) {
8999
9459
  if (cursorString == null) return null;
9000
- const isNumericString = (value) => typeof value === "string" && /^-?\d+$/.test(value);
9001
9460
  try {
9002
9461
  const v = JSON.parse(Buffer.from(cursorString, "base64url").toString("utf8"));
9003
- if ((v?.side === "buy" || v?.side === "sell") && isNumericString(v?.price) && typeof v?.blockNumber === "number" && Number.isInteger(v.blockNumber) && isNumericString(v?.assets) && isHex(v?.hash) && typeof v?.totalReturned === "number" && Number.isInteger(v.totalReturned) && typeof v?.now === "number" && Number.isInteger(v.now)) return v;
9462
+ 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) && isHex(v?.hash) && typeof v?.totalReturned === "number" && Number.isInteger(v.totalReturned) && typeof v?.now === "number" && Number.isInteger(v.now)) return v;
9004
9463
  throw new Error("Invalid cursor");
9005
9464
  } catch {
9006
9465
  logger.error({
@@ -9018,7 +9477,7 @@ let LevelCursor;
9018
9477
  function encode(lastLevel, offersCursor, side, now) {
9019
9478
  return Buffer.from(JSON.stringify({
9020
9479
  side,
9021
- lastPrice: lastLevel.price.toString(),
9480
+ lastTick: lastLevel.tick,
9022
9481
  now,
9023
9482
  offersCursor
9024
9483
  })).toString("base64url");
@@ -9026,10 +9485,9 @@ let LevelCursor;
9026
9485
  _LevelCursor.encode = encode;
9027
9486
  function decode(cursorString, logger) {
9028
9487
  if (cursorString == null) return null;
9029
- const isNumericString = (value) => typeof value === "string" && /^-?\d+$/.test(value);
9030
9488
  try {
9031
9489
  const v = JSON.parse(Buffer.from(cursorString, "base64url").toString("utf8"));
9032
- 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;
9490
+ 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;
9033
9491
  throw new Error("Invalid book cursor");
9034
9492
  } catch {
9035
9493
  logger.error({
@@ -9172,31 +9630,33 @@ function create$9(db) {
9172
9630
  function create$8(db) {
9173
9631
  return {
9174
9632
  get: async (parameters) => {
9175
- const { chainId, user, contract, group } = parameters ?? {};
9633
+ const { chainId, user, contract, group, obligationId } = parameters ?? {};
9176
9634
  const conditions = [];
9177
9635
  if (chainId !== void 0) conditions.push(eq(lots.chainId, chainId));
9178
9636
  if (user !== void 0) conditions.push(eq(lots.user, user.toLowerCase()));
9179
9637
  if (contract !== void 0) conditions.push(eq(lots.contract, contract.toLowerCase()));
9180
9638
  if (group !== void 0) conditions.push(eq(lots.group, group));
9639
+ if (obligationId !== void 0) conditions.push(eq(lots.obligationId, obligationId));
9181
9640
  return (await db.select().from(lots).where(conditions.length > 0 ? and(...conditions) : void 0)).map((row) => ({
9182
9641
  chainId: row.chainId,
9183
9642
  user: row.user,
9184
9643
  contract: row.contract,
9185
9644
  group: row.group,
9645
+ obligationId: row.obligationId,
9186
9646
  lower: BigInt(row.lower),
9187
9647
  upper: BigInt(row.upper)
9188
9648
  }));
9189
9649
  },
9190
9650
  create: async (parameters) => {
9191
9651
  if (parameters.length === 0) return;
9192
- const lotsByPositionGroup = /* @__PURE__ */ new Map();
9652
+ const lotsByKey = /* @__PURE__ */ new Map();
9193
9653
  for (const offer of parameters) {
9194
- const key = `${offer.positionChainId}-${offer.positionContract}-${offer.positionUser}-${offer.group}`.toLowerCase();
9195
- const existing = lotsByPositionGroup.get(key);
9196
- if (!existing || offer.size > existing.size) lotsByPositionGroup.set(key, offer);
9654
+ const key = `${offer.positionChainId}-${offer.positionContract}-${offer.positionUser}-${offer.group}-${offer.obligationId}`.toLowerCase();
9655
+ const existing = lotsByKey.get(key);
9656
+ if (!existing || offer.size > existing.size) lotsByKey.set(key, offer);
9197
9657
  }
9198
- for (const offer of lotsByPositionGroup.values()) if ((await db.select().from(lots).where(and(eq(lots.chainId, offer.positionChainId), eq(lots.contract, offer.positionContract.toLowerCase()), eq(lots.user, offer.positionUser.toLowerCase()), eq(lots.group, offer.group.toLowerCase()))).limit(1)).length === 0) {
9199
- const maxUpperResult = await db.select({ maxUpper: sql`COALESCE(MAX(${lots.upper}::numeric), 0)` }).from(lots).where(and(eq(lots.chainId, offer.positionChainId), eq(lots.contract, offer.positionContract.toLowerCase()), eq(lots.user, offer.positionUser.toLowerCase())));
9658
+ for (const offer of lotsByKey.values()) if ((await db.select().from(lots).where(and(eq(lots.chainId, offer.positionChainId), eq(lots.contract, offer.positionContract.toLowerCase()), eq(lots.user, offer.positionUser.toLowerCase()), eq(lots.group, offer.group.toLowerCase()), eq(lots.obligationId, offer.obligationId.toLowerCase()))).limit(1)).length === 0) {
9659
+ const maxUpperResult = await db.select({ maxUpper: sql`COALESCE(MAX(${lots.upper}::numeric), 0)` }).from(lots).where(and(eq(lots.chainId, offer.positionChainId), eq(lots.contract, offer.positionContract.toLowerCase()), eq(lots.user, offer.positionUser.toLowerCase()), eq(lots.obligationId, offer.obligationId.toLowerCase())));
9200
9660
  const newLower = BigInt(maxUpperResult[0]?.maxUpper ?? "0");
9201
9661
  const newUpper = newLower + offer.size;
9202
9662
  await db.insert(lots).values({
@@ -9204,6 +9664,7 @@ function create$8(db) {
9204
9664
  user: offer.positionUser.toLowerCase(),
9205
9665
  contract: offer.positionContract.toLowerCase(),
9206
9666
  group: offer.group.toLowerCase(),
9667
+ obligationId: offer.obligationId.toLowerCase(),
9207
9668
  lower: newLower.toString(),
9208
9669
  upper: newUpper.toString()
9209
9670
  });
@@ -9220,55 +9681,79 @@ function create$8(db) {
9220
9681
  * @returns Obligations domain. {@link ObligationsDomain}
9221
9682
  */
9222
9683
  function create$7(db) {
9223
- return { create: async (obligations$1) => {
9224
- if (obligations$1.length === 0) return;
9225
- const obligationsById = /* @__PURE__ */ new Map();
9226
- for (const obligation of obligations$1) {
9227
- const id$1 = id(obligation).toLowerCase();
9228
- if (!obligationsById.get(id$1)) obligationsById.set(id$1, obligation);
9229
- }
9230
- try {
9231
- await db.transaction(async (dbTx) => {
9232
- const obligationRows = obligations$1.map((obligation) => ({
9233
- obligationId: id(obligation),
9234
- chainId: obligation.chainId,
9235
- loanToken: obligation.loanToken.toLowerCase(),
9236
- maturity: obligation.maturity
9237
- }));
9238
- for (const batch of batch$1(obligationRows, DEFAULT_BATCH_SIZE$1)) await dbTx.insert(obligations).values(batch).onConflictDoNothing();
9239
- const collateralRows = obligations$1.flatMap((obligation) => {
9240
- return obligation.collaterals.map((collateral) => ({
9684
+ return {
9685
+ get: async (parameters) => {
9686
+ const chainIds = parameters?.chainId;
9687
+ const now$1 = now();
9688
+ return (await db.select({
9689
+ chainId: obligations.chainId,
9690
+ loanToken: obligations.loanToken,
9691
+ collaterals: sql`ARRAY_AGG(jsonb_build_object('asset', ${obligationCollateralsV2.asset}, 'oracle', ${oracles$1.address}, 'lltv', ${obligationCollateralsV2.lltv}))`.as("collaterals"),
9692
+ maturity: obligations.maturity
9693
+ }).from(obligations).innerJoin(obligationCollateralsV2, eq(obligations.obligationId, obligationCollateralsV2.obligationId)).innerJoin(oracles$1, sql`${obligationCollateralsV2.oracleChainId} = ${oracles$1.chainId}
9694
+ AND ${obligationCollateralsV2.oracleAddress} = ${oracles$1.address}`).groupBy(obligations.obligationId).where(and(chainIds !== void 0 && chainIds.length > 0 ? inArray(obligations.chainId, chainIds) : void 0, gte(obligations.maturity, now$1))).orderBy(asc(obligations.obligationId))).map((row) => from$15({
9695
+ chainId: row.chainId,
9696
+ loanToken: row.loanToken,
9697
+ collaterals: row.collaterals.sort((left, right) => left.asset.localeCompare(right.asset)).map((collateral) => from$17({
9698
+ asset: collateral.asset,
9699
+ oracle: collateral.oracle,
9700
+ lltv: from$18(BigInt(collateral.lltv))
9701
+ })),
9702
+ maturity: row.maturity
9703
+ }));
9704
+ },
9705
+ create: async (obligations$1) => {
9706
+ if (obligations$1.length === 0) return;
9707
+ const obligationsById = /* @__PURE__ */ new Map();
9708
+ for (const obligation of obligations$1) {
9709
+ const id$1 = id(obligation).toLowerCase();
9710
+ if (!obligationsById.get(id$1)) obligationsById.set(id$1, obligation);
9711
+ }
9712
+ try {
9713
+ await db.transaction(async (dbTx) => {
9714
+ const obligationRows = obligations$1.map((obligation) => ({
9241
9715
  obligationId: id(obligation),
9242
- asset: collateral.asset.toLowerCase(),
9243
- oracleChainId: obligation.chainId,
9244
- oracleAddress: collateral.oracle.toLowerCase(),
9245
- lltv: collateral.lltv
9716
+ chainId: obligation.chainId,
9717
+ loanToken: obligation.loanToken.toLowerCase(),
9718
+ maturity: obligation.maturity
9246
9719
  }));
9720
+ for (const batch of batch$1(obligationRows, DEFAULT_BATCH_SIZE$1)) await dbTx.insert(obligations).values(batch).onConflictDoNothing();
9721
+ const collateralRows = obligations$1.flatMap((obligation) => {
9722
+ return obligation.collaterals.map((collateral) => ({
9723
+ obligationId: id(obligation),
9724
+ asset: collateral.asset.toLowerCase(),
9725
+ oracleChainId: obligation.chainId,
9726
+ oracleAddress: collateral.oracle.toLowerCase(),
9727
+ lltv: collateral.lltv
9728
+ }));
9729
+ });
9730
+ for (const batch of batch$1(collateralRows, DEFAULT_BATCH_SIZE$1)) await dbTx.insert(obligationCollateralsV2).values(batch).onConflictDoNothing();
9247
9731
  });
9248
- for (const batch of batch$1(collateralRows, DEFAULT_BATCH_SIZE$1)) await dbTx.insert(obligationCollateralsV2).values(batch).onConflictDoNothing();
9249
- });
9250
- } catch (err) {
9251
- const error = err instanceof Error ? err : new Error(String(err));
9252
- throw new Error("Obligations.create failed. Ensure oracles exist before inserting obligations.", { cause: error });
9732
+ } catch (err) {
9733
+ const error = err instanceof Error ? err : new Error(String(err));
9734
+ throw new Error("Obligations.create failed. Ensure oracles exist before inserting obligations.", { cause: error });
9735
+ }
9253
9736
  }
9254
- } };
9737
+ };
9255
9738
  }
9256
9739
 
9257
9740
  //#endregion
9258
9741
  //#region src/database/domains/Offsets.ts
9259
9742
  function create$6(db) {
9260
9743
  return { get: async (parameters) => {
9261
- const { chainId, user, contract, group } = parameters ?? {};
9744
+ const { chainId, user, contract, group, obligationId } = parameters ?? {};
9262
9745
  const conditions = [];
9263
9746
  if (chainId !== void 0) conditions.push(eq(offsets.chainId, chainId));
9264
9747
  if (user !== void 0) conditions.push(eq(offsets.user, user.toLowerCase()));
9265
9748
  if (contract !== void 0) conditions.push(eq(offsets.contract, contract.toLowerCase()));
9266
9749
  if (group !== void 0) conditions.push(eq(offsets.group, group));
9750
+ if (obligationId !== void 0) conditions.push(eq(offsets.obligationId, obligationId));
9267
9751
  return (await db.select().from(offsets).where(conditions.length > 0 ? and(...conditions) : void 0)).map((row) => ({
9268
9752
  chainId: row.chainId,
9269
9753
  user: row.user,
9270
9754
  contract: row.contract,
9271
9755
  group: row.group,
9756
+ obligationId: row.obligationId,
9272
9757
  value: BigInt(row.value)
9273
9758
  }));
9274
9759
  } };
@@ -9418,7 +9903,8 @@ const create$4 = (db) => {
9418
9903
  if (!parsed.chainId || !parsed.contract) throw new Error("Invalid cursor format");
9419
9904
  cursor = {
9420
9905
  chainId: parsed.chainId,
9421
- contract: parsed.contract
9906
+ contract: parsed.contract,
9907
+ obligationId: parsed.obligationId ?? null
9422
9908
  };
9423
9909
  }
9424
9910
  const raw = await db.execute(sql`
@@ -9427,16 +9913,18 @@ const create$4 = (db) => {
9427
9913
  chain_id,
9428
9914
  "user",
9429
9915
  contract,
9916
+ obligation_id,
9430
9917
  SUM(value::numeric) AS total_offset
9431
9918
  FROM ${offsets}
9432
9919
  WHERE LOWER("user") = LOWER(${user})
9433
- GROUP BY chain_id, "user", contract
9920
+ GROUP BY chain_id, "user", contract, obligation_id
9434
9921
  ),
9435
9922
  position_consumed AS (
9436
9923
  SELECT
9437
9924
  l.chain_id,
9438
9925
  l.contract,
9439
9926
  l."user",
9927
+ l.obligation_id,
9440
9928
  SUM(
9441
9929
  CASE
9442
9930
  WHEN offer_agg.assets > 0
@@ -9462,50 +9950,64 @@ const create$4 = (db) => {
9462
9950
  AND LOWER(offer_agg.group_maker) = LOWER(g.maker)
9463
9951
  AND offer_agg."group_group" = g."group"
9464
9952
  WHERE LOWER(l."user") = LOWER(${user})
9465
- GROUP BY l.chain_id, l.contract, l."user"
9953
+ GROUP BY l.chain_id, l.contract, l."user", l.obligation_id
9466
9954
  ),
9467
9955
  position_max_lot AS (
9468
9956
  SELECT
9469
9957
  chain_id,
9470
9958
  contract,
9471
9959
  "user",
9960
+ obligation_id,
9472
9961
  MAX(upper::numeric) AS max_upper
9473
9962
  FROM ${lots}
9474
9963
  WHERE LOWER("user") = LOWER(${user})
9475
- GROUP BY chain_id, contract, "user"
9964
+ GROUP BY chain_id, contract, "user", obligation_id
9965
+ ),
9966
+ per_obligation AS (
9967
+ SELECT
9968
+ pml.chain_id,
9969
+ pml.contract,
9970
+ pml."user",
9971
+ pml.obligation_id,
9972
+ GREATEST(0,
9973
+ COALESCE(pml.max_upper, 0)
9974
+ - COALESCE(po.total_offset, 0)
9975
+ - COALESCE(pc.consumed, 0)
9976
+ )::text AS reserved_balance
9977
+ FROM position_max_lot pml
9978
+ LEFT JOIN position_offsets po
9979
+ ON po.chain_id = pml.chain_id
9980
+ AND LOWER(po.contract) = LOWER(pml.contract)
9981
+ AND LOWER(po."user") = LOWER(pml."user")
9982
+ AND po.obligation_id = pml.obligation_id
9983
+ LEFT JOIN position_consumed pc
9984
+ ON pc.chain_id = pml.chain_id
9985
+ AND LOWER(pc.contract) = LOWER(pml.contract)
9986
+ AND LOWER(pc."user") = LOWER(pml."user")
9987
+ AND pc.obligation_id = pml.obligation_id
9476
9988
  )
9477
9989
  SELECT
9478
9990
  p.chain_id,
9479
9991
  p.contract,
9480
9992
  p."user",
9481
9993
  p.block_number,
9482
- GREATEST(0,
9483
- COALESCE(pml.max_upper, 0)
9484
- - COALESCE(po.total_offset, 0)
9485
- - COALESCE(pc.consumed, 0)
9486
- )::text AS reserved_balance
9994
+ po.obligation_id,
9995
+ COALESCE(po.reserved_balance, '0') AS reserved_balance
9487
9996
  FROM ${positions} p
9488
- LEFT JOIN position_offsets po
9997
+ LEFT JOIN per_obligation po
9489
9998
  ON po.chain_id = p.chain_id
9490
9999
  AND LOWER(po.contract) = LOWER(p.contract)
9491
10000
  AND LOWER(po."user") = LOWER(p."user")
9492
- LEFT JOIN position_consumed pc
9493
- ON pc.chain_id = p.chain_id
9494
- AND LOWER(pc.contract) = LOWER(p.contract)
9495
- AND LOWER(pc."user") = LOWER(p."user")
9496
- LEFT JOIN position_max_lot pml
9497
- ON pml.chain_id = p.chain_id
9498
- AND LOWER(pml.contract) = LOWER(p.contract)
9499
- AND LOWER(pml."user") = LOWER(p."user")
9500
10001
  WHERE LOWER(p."user") = LOWER(${user})
9501
10002
  AND p."user" != ${zeroAddress}
9502
- ${cursor !== null ? sql`AND (p.chain_id, p.contract) > (${cursor.chainId}, ${cursor.contract})` : sql``}
9503
- ORDER BY p.chain_id ASC, p.contract ASC
10003
+ ${cursor !== null ? sql`AND (p.chain_id, p.contract, COALESCE(po.obligation_id, '')) > (${cursor.chainId}, ${cursor.contract}, ${cursor.obligationId ?? ""})` : sql``}
10004
+ ORDER BY p.chain_id ASC, p.contract ASC, po.obligation_id ASC NULLS FIRST
9504
10005
  LIMIT ${limit}
9505
10006
  `);
9506
10007
  const nextCursor = raw.rows.length === limit ? Buffer.from(JSON.stringify({
9507
10008
  chainId: raw.rows[raw.rows.length - 1].chain_id.toString(),
9508
- contract: raw.rows[raw.rows.length - 1].contract
10009
+ contract: raw.rows[raw.rows.length - 1].contract,
10010
+ obligationId: raw.rows[raw.rows.length - 1].obligation_id
9509
10011
  })).toString("base64url") : null;
9510
10012
  return {
9511
10013
  positions: raw.rows.map((row) => ({
@@ -9513,6 +10015,7 @@ const create$4 = (db) => {
9513
10015
  contract: row.contract,
9514
10016
  user: row.user,
9515
10017
  blockNumber: row.block_number,
10018
+ obligationId: row.obligation_id,
9516
10019
  reserved: BigInt(row.reserved_balance.split(".")[0] ?? "0")
9517
10020
  })),
9518
10021
  nextCursor
@@ -9791,7 +10294,10 @@ function create$1(db) {
9791
10294
 
9792
10295
  //#endregion
9793
10296
  //#region src/database/Database.ts
9794
- var Database_exports = /* @__PURE__ */ __exportAll({ connect: () => connect$1 });
10297
+ var Database_exports = /* @__PURE__ */ __exportAll({
10298
+ connect: () => connect$1,
10299
+ getSchemaNamesForMigration: () => getSchemaNamesForMigration
10300
+ });
9795
10301
  function createDomains(core, chainRegistry) {
9796
10302
  return {
9797
10303
  book: create$12({ db: core }),
@@ -9813,6 +10319,9 @@ function createDomains(core, chainRegistry) {
9813
10319
  transfers: create$3(core)
9814
10320
  };
9815
10321
  }
10322
+ function createReaders(core) {
10323
+ return { obligations: create$16({ db: core }) };
10324
+ }
9816
10325
  const AUGMENT_CACHE = /* @__PURE__ */ new WeakMap();
9817
10326
  function augmentWithDomains(base, chainRegistry) {
9818
10327
  const cached = AUGMENT_CACHE.get(base)?.get(chainRegistry);
@@ -9824,6 +10333,7 @@ function augmentWithDomains(base, chainRegistry) {
9824
10333
  });
9825
10334
  };
9826
10335
  const dms = createDomains(wrapped, chainRegistry);
10336
+ const readers = createReaders(wrapped);
9827
10337
  Object.defineProperties(wrapped, {
9828
10338
  book: {
9829
10339
  value: dms.book,
@@ -9880,6 +10390,10 @@ function augmentWithDomains(base, chainRegistry) {
9880
10390
  transfers: {
9881
10391
  value: dms.transfers,
9882
10392
  enumerable: true
10393
+ },
10394
+ readers: {
10395
+ value: readers,
10396
+ enumerable: true
9883
10397
  }
9884
10398
  });
9885
10399
  const chainRegistryMap = AUGMENT_CACHE.get(base);
@@ -9888,6 +10402,7 @@ function augmentWithDomains(base, chainRegistry) {
9888
10402
  return wrapped;
9889
10403
  }
9890
10404
  const InMemoryDbMap = /* @__PURE__ */ new Map();
10405
+ const LEGACY_SCHEMA_START_MINOR = 7;
9891
10406
  /**
9892
10407
  * Connect to the database.
9893
10408
  * @notice If no connection string is provided, an in-process PGLite database is created.
@@ -9942,9 +10457,26 @@ function applyMigrations(kind, driver) {
9942
10457
  async function preMigrate(driver) {
9943
10458
  const tracer = getTracer("db.preMigrate");
9944
10459
  await startActiveSpan(tracer, "db.preMigrate", async () => {
9945
- await driver.execute(`create schema if not exists "${VERSION}"`);
10460
+ const schemaNames = getSchemaNamesForMigration(VERSION);
10461
+ for (const schemaName of schemaNames) await driver.execute(`create schema if not exists "${schemaName}"`);
9946
10462
  });
9947
10463
  }
10464
+ /**
10465
+ * Build the list of router schemas that should exist before running migrations.
10466
+ * @param version - Current schema version (e.g. `router_v1.8`).
10467
+ * @returns Ordered schema names from `router_v1.7` to current, or just current if parsing fails.
10468
+ */
10469
+ function getSchemaNamesForMigration(version) {
10470
+ const parsed = /^router_v(?<major>\d+)\.(?<minor>\d+)$/.exec(version);
10471
+ if (!parsed?.groups?.major || !parsed.groups.minor) return [version];
10472
+ const major = Number.parseInt(parsed.groups.major, 10);
10473
+ const currentMinor = Number.parseInt(parsed.groups.minor, 10);
10474
+ if (!Number.isInteger(major) || !Number.isInteger(currentMinor)) return [version];
10475
+ if (currentMinor < LEGACY_SCHEMA_START_MINOR) return [version];
10476
+ const schemaNames = [];
10477
+ for (let minor = LEGACY_SCHEMA_START_MINOR; minor <= currentMinor; minor += 1) schemaNames.push(`router_v${major}.${minor}`);
10478
+ return schemaNames;
10479
+ }
9948
10480
  async function postMigrate(driver) {
9949
10481
  const tracer = getTracer("db.postMigrate");
9950
10482
  await startActiveSpan(tracer, "db.postMigrate", async () => {
@@ -10214,15 +10746,16 @@ async function postMigrate(driver) {
10214
10746
  RETURNS trigger
10215
10747
  LANGUAGE plpgsql AS $$
10216
10748
  BEGIN
10217
- INSERT INTO "${VERSION}"."offsets" (chain_id, "user", contract, "group", value)
10749
+ INSERT INTO "${VERSION}"."offsets" (chain_id, "user", contract, "group", obligation_id, value)
10218
10750
  VALUES (
10219
10751
  OLD.chain_id,
10220
10752
  OLD."user",
10221
10753
  OLD.contract,
10222
10754
  OLD."group",
10755
+ OLD.obligation_id,
10223
10756
  OLD.upper::numeric - OLD.lower::numeric
10224
10757
  )
10225
- ON CONFLICT (chain_id, "user", contract, "group") DO NOTHING;
10758
+ ON CONFLICT (chain_id, "user", contract, "group", obligation_id) DO NOTHING;
10226
10759
  RETURN OLD;
10227
10760
  END;
10228
10761
  $$;
@@ -10476,10 +11009,11 @@ var Rules_exports = /* @__PURE__ */ __exportAll({
10476
11009
  amountMutualExclusivity: () => amountMutualExclusivity,
10477
11010
  callback: () => callback,
10478
11011
  chains: () => chains,
11012
+ collateralToken: () => collateralToken,
11013
+ loanToken: () => loanToken,
10479
11014
  maturity: () => maturity,
10480
11015
  oracle: () => oracle,
10481
11016
  sameMaker: () => sameMaker,
10482
- token: () => token,
10483
11017
  validity: () => validity
10484
11018
  });
10485
11019
  /**
@@ -10499,7 +11033,7 @@ const chains = ({ chains }) => single("chain_ids", `Validates that offer chain i
10499
11033
  });
10500
11034
  const maturity = ({ maturities }) => single("maturity", `Validates that offer maturity is one of: [${maturities.join(", ")}]`, (offer) => {
10501
11035
  const allowedMaturities = maturities.map((m) => from$16(m));
10502
- 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}` };
11036
+ if (!allowedMaturities.includes(offer.maturity)) return { message: `Maturity must be one of (${allowedMaturities.join(", ")}). Got: ${offer.maturity}` };
10503
11037
  });
10504
11038
  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) => {
10505
11039
  if (!isEmptyCallback(offer)) return { message: "Non-empty callbacks are not supported." };
@@ -10507,15 +11041,25 @@ const callback = ({ callbacks }) => single("callback", `Validates callbacks: buy
10507
11041
  if (isEmptyCallback(offer) && !offer.buy && !callbacks.includes(Type$1.SellWithEmptyCallback)) return { message: "Sell offers with empty callback not allowed." };
10508
11042
  });
10509
11043
  /**
10510
- * A validation rule that checks if the offer's tokens are allowed for its chain.
10511
- * @param assetsByChainId - Allowed assets indexed by chain id.
11044
+ * A validation rule that checks if the offer's loan token is allowed for its chain.
11045
+ * @param assetsByChainId - Allowed loan tokens indexed by chain id.
11046
+ * @returns The issue that was found. If the offer is valid, this will be undefined.
11047
+ */
11048
+ const loanToken = ({ assetsByChainId }) => single("loan_token", "Validates that offer loan token is in the allowed token list for the offer chain", (offer) => {
11049
+ const allowedLoanTokens = assetsByChainId[offer.chainId]?.map((asset) => asset.toLowerCase());
11050
+ if (!allowedLoanTokens || allowedLoanTokens.length === 0) return { message: `No allowed loan tokens for chain ${offer.chainId}` };
11051
+ if (!allowedLoanTokens.includes(offer.loanToken.toLowerCase())) return { message: "Loan token is not allowed" };
11052
+ });
11053
+ /**
11054
+ * A validation rule that checks if the offer's collateral tokens are allowed for its chain.
11055
+ * @param collateralAssetsByChainId - Allowed collateral tokens indexed by chain id.
10512
11056
  * @returns The issue that was found. If the offer is valid, this will be undefined.
10513
11057
  */
10514
- const token = ({ assetsByChainId }) => single("token", "Validates that offer loan token and collateral tokens are in the allowed assets list for the offer chain", (offer) => {
10515
- const allowedAssets = assetsByChainId[offer.chainId]?.map((asset) => asset.toLowerCase());
10516
- if (!allowedAssets || allowedAssets.length === 0) return { message: `No allowed assets for chain ${offer.chainId}` };
10517
- if (!allowedAssets.includes(offer.loanToken.toLowerCase())) return { message: "Loan token is not allowed" };
10518
- if (offer.collaterals.some((collateral) => !allowedAssets.includes(collateral.asset.toLowerCase()))) return { message: "Collateral is not allowed" };
11058
+ const collateralToken = ({ collateralAssetsByChainId }) => single("collateral_token", "Validates that offer collateral tokens are in the allowed token list for the offer chain", (offer) => {
11059
+ const allowedCollateralTokens = collateralAssetsByChainId[offer.chainId]?.map((asset) => asset.toLowerCase()) ?? [];
11060
+ if (allowedCollateralTokens.length === 0) return { message: `No allowed collateral tokens for chain ${offer.chainId}` };
11061
+ if (offer.collaterals.length === 0) return { message: "At least one collateral token is required" };
11062
+ if (offer.collaterals.some((collateral) => !allowedCollateralTokens.includes(collateral.asset.toLowerCase()))) return { message: "Collateral token is not allowed" };
10519
11063
  });
10520
11064
  /**
10521
11065
  * A validation rule that checks if the offer's oracle addresses are allowed for its chain.
@@ -10558,21 +11102,24 @@ const amountMutualExclusivity = () => single("amount_mutual_exclusivity", "Valid
10558
11102
  //#region src/gatekeeper/morphoRules.ts
10559
11103
  const morphoRules = (chains$3) => {
10560
11104
  const assetsByChainId = {};
11105
+ const collateralAssetsByChainId = {};
10561
11106
  const oraclesByChainId = {};
10562
11107
  for (const chain of chains$3) {
10563
11108
  assetsByChainId[chain.id] = assets[chain.id.toString()] ?? [];
11109
+ collateralAssetsByChainId[chain.id] = collateralAssets[chain.id.toString()] ?? [];
10564
11110
  oraclesByChainId[chain.id] = oracles[chain.id.toString()] ?? [];
10565
11111
  }
10566
11112
  return [
10567
11113
  sameMaker(),
10568
11114
  amountMutualExclusivity(),
10569
11115
  chains({ chains: chains$3 }),
10570
- maturity({ maturities: [MaturityType.EndOfMonth, MaturityType.EndOfNextMonth] }),
11116
+ maturity({ maturities: [MaturityType.EndOfWeek, MaturityType.EndOfNextWeek] }),
10571
11117
  callback({
10572
11118
  callbacks: [Type$1.BuyWithEmptyCallback, Type$1.SellWithEmptyCallback],
10573
11119
  allowedAddresses: []
10574
11120
  }),
10575
- token({ assetsByChainId }),
11121
+ loanToken({ assetsByChainId }),
11122
+ collateralToken({ collateralAssetsByChainId }),
10576
11123
  oracle({ oraclesByChainId })
10577
11124
  ];
10578
11125
  };
@@ -10755,5 +11302,5 @@ var mempool_exports = /* @__PURE__ */ __exportAll({
10755
11302
  });
10756
11303
 
10757
11304
  //#endregion
10758
- export { Abi_exports as Abi, BookResponse_exports as BookResponse, BooksController, BrandTypeId, Callback_exports as Callback, Chain_exports as Chain, ChainHealth, ChainRegistry_exports as ChainRegistry, ChainsHealthResponse, Collateral_exports as Collateral, CollectorHealth, CollectorsHealthResponse, ConfigContractsController, ConfigRulesController, Database_exports as Database, ERC4626_exports as ERC4626, Errors_exports as Errors, Format_exports as Format, Gatekeeper_exports as Gatekeeper, Client_exports as GatekeeperClient, Health_exports as Health, HealthController, Indexer_exports as Indexer, LLTV_exports as LLTV, Liquidity_exports as Liquidity, Logger_exports as Logger, Maturity_exports as Maturity, mempool_exports as Mempool, Obligation_exports as Obligation, ObligationResponse_exports as ObligationResponse, ObligationsController, Offer_exports as Offer, OfferResponse_exports as OfferResponse, OffersController, drizzle_exports as OffersSchema, OpenApi, Oracle_exports as Oracle, Position_exports as Position, PositionResponse_exports as PositionResponse, Quote_exports as Quote, RouterApi_exports as RouterApi, Client_exports$1 as RouterClient, RouterStatusResponse, Rules_exports as Rules, time_exports as Time, TradingFee_exports as TradingFee, Transfer_exports as Transfer, Tree_exports as Tree, UsersController, utils_exports as Utils, ValidateController, Gate_exports as Validation, morphoRules, parse, safeParse };
11305
+ export { Abi_exports as Abi, BookResponse_exports as BookResponse, BooksController, BrandTypeId, Callback_exports as Callback, Chain_exports as Chain, ChainHealth, ChainRegistry_exports as ChainRegistry, ChainsHealthResponse, Collateral_exports as Collateral, CollectorHealth, CollectorsHealthResponse, ConfigContractsController, ConfigRulesController, Database_exports as Database, ERC4626_exports as ERC4626, Errors_exports as Errors, Format_exports as Format, Gatekeeper_exports as Gatekeeper, Client_exports as GatekeeperClient, Health_exports as Health, HealthController, Indexer_exports as Indexer, LLTV_exports as LLTV, Liquidity_exports as Liquidity, Logger_exports as Logger, Maturity_exports as Maturity, mempool_exports as Mempool, Obligation_exports as Obligation, ObligationResponse_exports as ObligationResponse, ObligationsController, Offer_exports as Offer, OfferResponse_exports as OfferResponse, OffersController, drizzle_exports as OffersSchema, OpenApi, Oracle_exports as Oracle, Position_exports as Position, PositionResponse_exports as PositionResponse, Quote_exports as Quote, RouterApi_exports as RouterApi, Client_exports$1 as RouterClient, RouterStatusResponse, Rules_exports as Rules, Tick_exports as Tick, time_exports as Time, TradingFee_exports as TradingFee, Transfer_exports as Transfer, Tree_exports as Tree, UsersController, utils_exports as Utils, ValidateController, Gate_exports as Validation, morphoRules, parse, safeParse };
10759
11306
  //# sourceMappingURL=index.node.mjs.map