@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
package/dist/cli.js CHANGED
@@ -39,7 +39,7 @@ import { migrate } from "drizzle-orm/node-postgres/migrator";
39
39
  import { drizzle as drizzle$1 } from "drizzle-orm/pglite";
40
40
  import { migrate as migrate$1 } from "drizzle-orm/pglite/migrator";
41
41
  import { Pool } from "pg";
42
- import { and, asc, eq, gt, gte, inArray, lte, ne, sql } from "drizzle-orm";
42
+ import { and, asc, desc, eq, gt, gte, inArray, lte, ne, sql } from "drizzle-orm";
43
43
  import { bigint, boolean, foreignKey, index, integer, numeric, pgSchema, primaryKey, serial, text, timestamp, uniqueIndex, varchar } from "drizzle-orm/pg-core";
44
44
  import { parse } from "smol-toml";
45
45
  import { cors } from "hono/cors";
@@ -152,7 +152,7 @@ function startActiveSpan(tracer, name, fn) {
152
152
  //#endregion
153
153
  //#region package.json
154
154
  var name = "@morpho-dev/router";
155
- var version = "0.7.2";
155
+ var version = "0.9.0";
156
156
  var description = "Router package for Morpho protocol";
157
157
 
158
158
  //#endregion
@@ -331,8 +331,8 @@ const chains$2 = {
331
331
  name: "ethereum-virtual-testnet",
332
332
  custom: {
333
333
  morpho: {
334
- address: "0x634b095371e4e45feed94c1a45c37798e173ea50",
335
- blockCreated: 23226700
334
+ address: "0xc9f3c65996fc46b9500608b2c9a9152c01c540f7",
335
+ blockCreated: 23226871
336
336
  },
337
337
  morphoBlue: {
338
338
  address: "0xBBBBBbbBBb9cC5e90e3b3Af64bdAF62C37EEFFCb",
@@ -1469,7 +1469,7 @@ async function run(parameters) {
1469
1469
  * @param parameters - Gatekeeper parameters. {@link GatekeeperParameters}
1470
1470
  * @returns Gatekeeper instance. {@link Gatekeeper}
1471
1471
  */
1472
- function create$20(parameters) {
1472
+ function create$21(parameters) {
1473
1473
  const { rules } = parameters;
1474
1474
  return { isAllowed: async (offers) => {
1475
1475
  return await run({
@@ -1640,6 +1640,33 @@ const assets = {
1640
1640
  "0x2260FAC5E5542a773Aa44fBCfeDf7C193bc2C599"
1641
1641
  ]
1642
1642
  };
1643
+ const collateralAssets = {
1644
+ [ChainId.ETHEREUM.toString()]: [
1645
+ "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
1646
+ "0x1aBaEA1f7C830bD89Acc67eC4af516284b1bC33c",
1647
+ "0x2260FAC5E5542a773Aa44fBCfeDf7C193bc2C599",
1648
+ "0x7f39C581F595B53c5cb19bD0b3f8dA6c935E2Ca0"
1649
+ ],
1650
+ [ChainId.BASE.toString()]: [
1651
+ "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
1652
+ "0x4200000000000000000000000000000000000006",
1653
+ "0xcbB7C0000aB88B473b1f5aFd9ef808440eed33Bf",
1654
+ "0xc1CBa3fCea344f92D9239c08C0568f6F2F0ee452",
1655
+ "0x60a3E35Cc302bFA44Cb288Bc5a4F316Fdb1adb42"
1656
+ ],
1657
+ [ChainId["ETHEREUM-VIRTUAL-TESTNET"].toString()]: [
1658
+ "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
1659
+ "0x1aBaEA1f7C830bD89Acc67eC4af516284b1bC33c",
1660
+ "0x2260FAC5E5542a773Aa44fBCfeDf7C193bc2C599",
1661
+ "0x7f39C581F595B53c5cb19bD0b3f8dA6c935E2Ca0"
1662
+ ],
1663
+ [ChainId.ANVIL.toString()]: [
1664
+ "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
1665
+ "0x1aBaEA1f7C830bD89Acc67eC4af516284b1bC33c",
1666
+ "0x2260FAC5E5542a773Aa44fBCfeDf7C193bc2C599",
1667
+ "0x7f39C581F595B53c5cb19bD0b3f8dA6c935E2Ca0"
1668
+ ]
1669
+ };
1643
1670
  const oracles$1 = {
1644
1671
  [ChainId.ETHEREUM.toString()]: [
1645
1672
  "0xDddd770BADd886dF3864029e4B377B5F6a2B6b83",
@@ -1680,19 +1707,19 @@ const oracles$1 = {
1680
1707
  const configs = {
1681
1708
  ethereum: {
1682
1709
  callbacks: [{ type: Type$1.BuyWithEmptyCallback }, { type: Type$1.SellWithEmptyCallback }],
1683
- maturities: [MaturityType.EndOfMonth, MaturityType.EndOfNextMonth]
1710
+ maturities: [MaturityType.EndOfWeek, MaturityType.EndOfNextWeek]
1684
1711
  },
1685
1712
  base: {
1686
1713
  callbacks: [{ type: Type$1.BuyWithEmptyCallback }, { type: Type$1.SellWithEmptyCallback }],
1687
- maturities: [MaturityType.EndOfMonth, MaturityType.EndOfNextMonth]
1714
+ maturities: [MaturityType.EndOfWeek, MaturityType.EndOfNextWeek]
1688
1715
  },
1689
1716
  "ethereum-virtual-testnet": {
1690
1717
  callbacks: [{ type: Type$1.BuyWithEmptyCallback }, { type: Type$1.SellWithEmptyCallback }],
1691
- maturities: [MaturityType.EndOfMonth, MaturityType.EndOfNextMonth]
1718
+ maturities: [MaturityType.EndOfWeek, MaturityType.EndOfNextWeek]
1692
1719
  },
1693
1720
  anvil: {
1694
1721
  callbacks: [{ type: Type$1.BuyWithEmptyCallback }, { type: Type$1.SellWithEmptyCallback }],
1695
- maturities: [MaturityType.EndOfMonth, MaturityType.EndOfNextMonth]
1722
+ maturities: [MaturityType.EndOfWeek, MaturityType.EndOfNextWeek]
1696
1723
  }
1697
1724
  };
1698
1725
 
@@ -1738,18 +1765,18 @@ const MorphoV2 = parseAbi([
1738
1765
  "function setFeeSetter(address newFeeSetter)",
1739
1766
  "function setObligationTradingFee(bytes32 id, uint256 index, uint256 newTradingFee)",
1740
1767
  "function setOwner(address newOwner)",
1741
- "function setTradingFeeRecipient(address recipient)",
1768
+ "function setTradingFeeRecipient(address feeRecipient)",
1742
1769
  "function sharesOf(bytes32 id, address user) view returns (uint256)",
1743
1770
  "function shuffleSession()",
1744
1771
  "function supplyCollateral((address loanToken, (address token, uint256 lltv, address oracle)[] collaterals, uint256 maturity) obligation, address collateral, uint256 assets, address onBehalf)",
1745
- "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)",
1772
+ "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)",
1746
1773
  "function totalShares(bytes32 id) view returns (uint256)",
1747
1774
  "function totalUnits(bytes32 id) view returns (uint256)",
1748
1775
  "function touchObligation((address loanToken, (address token, uint256 lltv, address oracle)[] collaterals, uint256 maturity) obligation) returns (bytes32)",
1749
1776
  "function tradingFee(bytes32 id, uint256 timeToMaturity) view returns (uint256)",
1750
1777
  "function tradingFeeRecipient() view returns (address)",
1751
- "function withdraw((address loanToken, (address token, uint256 lltv, address oracle)[] collaterals, uint256 maturity) obligation, uint256 obligationUnits, uint256 shares, address onBehalf) returns (uint256, uint256)",
1752
- "function withdrawCollateral((address loanToken, (address token, uint256 lltv, address oracle)[] collaterals, uint256 maturity) obligation, address collateral, uint256 assets, address onBehalf)",
1778
+ "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)",
1779
+ "function withdrawCollateral((address loanToken, (address token, uint256 lltv, address oracle)[] collaterals, uint256 maturity) obligation, address collateral, uint256 assets, address onBehalf, address receiver)",
1753
1780
  "function withdrawable(bytes32 id) view returns (uint256)",
1754
1781
  "event Constructor(address indexed owner)",
1755
1782
  "event Consume(address indexed user, bytes32 indexed group, uint256 amount)",
@@ -1761,12 +1788,12 @@ const MorphoV2 = parseAbi([
1761
1788
  "event SetFeeSetter(address indexed feeSetter)",
1762
1789
  "event SetObligationTradingFee(bytes32 indexed id, uint256 indexed index, uint256 newTradingFee)",
1763
1790
  "event SetOwner(address indexed owner)",
1764
- "event SetTradingFeeRecipient(address indexed recipient)",
1791
+ "event SetTradingFeeRecipient(address indexed feeRecipient)",
1765
1792
  "event ShuffleSession(address indexed user, bytes32 session)",
1766
1793
  "event SupplyCollateral(address caller, bytes32 indexed id, address indexed collateral, uint256 assets, address indexed onBehalf)",
1767
- "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)",
1768
- "event Withdraw(address indexed caller, bytes32 indexed id, uint256 obligationUnits, uint256 shares, address indexed onBehalf)",
1769
- "event WithdrawCollateral(address caller, bytes32 indexed id, address indexed collateral, uint256 assets, address indexed onBehalf)"
1794
+ "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)",
1795
+ "event Withdraw(address caller, bytes32 indexed id, uint256 obligationUnits, uint256 shares, address indexed onBehalf, address indexed receiver)",
1796
+ "event WithdrawCollateral(address caller, bytes32 indexed id, address indexed collateral, uint256 assets, address indexed onBehalf, address receiver)"
1770
1797
  ]);
1771
1798
 
1772
1799
  //#endregion
@@ -1789,7 +1816,7 @@ const Oracle = [{
1789
1816
  * @param chains - Array of chain objects to register.
1790
1817
  * @returns A registry for looking up chains by ID. {@link ChainRegistry}
1791
1818
  */
1792
- function create$19(chains) {
1819
+ function create$20(chains) {
1793
1820
  const byId = /* @__PURE__ */ new Map();
1794
1821
  for (const chain of chains) byId.set(chain.id, chain);
1795
1822
  return {
@@ -2126,7 +2153,7 @@ const OfferSchema = () => {
2126
2153
  assets: z$2.bigint({ coerce: true }).min(0n).max(maxUint256),
2127
2154
  obligationUnits: z$2.bigint({ coerce: true }).min(0n).max(maxUint256).optional().default(0n),
2128
2155
  obligationShares: z$2.bigint({ coerce: true }).min(0n).max(maxUint256).optional().default(0n),
2129
- price: z$2.bigint({ coerce: true }).min(0n).max(maxUint256),
2156
+ tick: z$2.coerce.number().int().min(0).max(990),
2130
2157
  maturity: MaturitySchema,
2131
2158
  expiry: z$2.number().int().max(Number.MAX_SAFE_INTEGER),
2132
2159
  start: z$2.number().int().max(Number.MAX_SAFE_INTEGER),
@@ -2147,7 +2174,8 @@ const OfferSchema = () => {
2147
2174
  callback: z$2.object({
2148
2175
  address: z$2.string().transform(transformAddress),
2149
2176
  data: z$2.string().transform(transformHex)
2150
- })
2177
+ }),
2178
+ receiverIfMakerIsSeller: z$2.string().transform(transformAddress)
2151
2179
  }).refine((data) => data.start < data.expiry, {
2152
2180
  message: "start must be before expiry",
2153
2181
  path: ["start"]
@@ -2163,8 +2191,12 @@ const OfferSchema = () => {
2163
2191
  * @returns The created offer.
2164
2192
  */
2165
2193
  function from$12(input) {
2194
+ const normalizedInput = {
2195
+ ...input,
2196
+ receiverIfMakerIsSeller: input.receiverIfMakerIsSeller ?? input.maker
2197
+ };
2166
2198
  try {
2167
- return OfferSchema().parse(input);
2199
+ return OfferSchema().parse(normalizedInput);
2168
2200
  } catch (error) {
2169
2201
  throw new InvalidOfferError(error);
2170
2202
  }
@@ -2198,7 +2230,7 @@ const serialize = (offer) => ({
2198
2230
  assets: offer.assets.toString(),
2199
2231
  obligationUnits: offer.obligationUnits.toString(),
2200
2232
  obligationShares: offer.obligationShares.toString(),
2201
- price: offer.price.toString(),
2233
+ tick: offer.tick,
2202
2234
  maturity: Number(offer.maturity),
2203
2235
  expiry: Number(offer.expiry),
2204
2236
  start: Number(offer.start),
@@ -2216,6 +2248,7 @@ const serialize = (offer) => ({
2216
2248
  address: offer.callback.address,
2217
2249
  data: offer.callback.data
2218
2250
  },
2251
+ receiverIfMakerIsSeller: offer.receiverIfMakerIsSeller,
2219
2252
  hash: hash(offer)
2220
2253
  });
2221
2254
  /**
@@ -2243,14 +2276,13 @@ function random(config) {
2243
2276
  [.98, 2]
2244
2277
  ]));
2245
2278
  const buy = config?.buy !== void 0 ? config.buy : bool();
2246
- const ONE = 1000000000000000000n;
2247
- const qMin = buy ? 16 : 4;
2248
- const len = (buy ? 32 : 16) - qMin + 1;
2249
- const pricePairs = Array.from({ length: len }, (_, idx) => {
2250
- const q = qMin + idx;
2251
- return [BigInt(q) * (ONE / 4n), buy ? 1 + idx : 1 + (len - 1 - idx)];
2279
+ const tickMin = buy ? 0 : 495;
2280
+ const len = (buy ? 495 : 990) - tickMin + 1;
2281
+ const tickPairs = Array.from({ length: len }, (_, idx) => {
2282
+ const weight = buy ? 1 + idx : 1 + (len - 1 - idx);
2283
+ return [tickMin + idx, weight];
2252
2284
  });
2253
- const price = config?.price ?? weightedChoice(pricePairs);
2285
+ const tick = config?.tick ?? weightedChoice(tickPairs);
2254
2286
  const loanTokenDecimals = config?.assetsDecimals?.[loanToken] ?? 18;
2255
2287
  const unit = BigInt(10) ** BigInt(loanTokenDecimals);
2256
2288
  const amountBase = BigInt(100 + int(999901));
@@ -2259,12 +2291,13 @@ function random(config) {
2259
2291
  address: zeroAddress,
2260
2292
  data: "0x"
2261
2293
  };
2294
+ const maker = config?.maker ?? address();
2262
2295
  return from$12({
2263
- maker: config?.maker ?? address(),
2296
+ maker,
2264
2297
  assets: assetsScaled,
2265
2298
  obligationUnits: config?.obligationUnits ?? 0n,
2266
2299
  obligationShares: config?.obligationShares ?? 0n,
2267
- price,
2300
+ tick,
2268
2301
  maturity,
2269
2302
  expiry: config?.expiry ?? maturity - 1,
2270
2303
  start: config?.start ?? maturity - 10,
@@ -2277,7 +2310,8 @@ function random(config) {
2277
2310
  ...random$1(),
2278
2311
  lltv
2279
2312
  })).sort((a, b) => a.asset.localeCompare(b.asset)),
2280
- callback: config?.callback ?? emptyCallback
2313
+ callback: config?.callback ?? emptyCallback,
2314
+ receiverIfMakerIsSeller: config?.receiverIfMakerIsSeller ?? maker
2281
2315
  });
2282
2316
  }
2283
2317
  const weightedChoice = (pairs) => {
@@ -2329,7 +2363,7 @@ const types = {
2329
2363
  type: "uint256"
2330
2364
  },
2331
2365
  {
2332
- name: "price",
2366
+ name: "tick",
2333
2367
  type: "uint256"
2334
2368
  },
2335
2369
  {
@@ -2363,6 +2397,10 @@ const types = {
2363
2397
  {
2364
2398
  name: "callback",
2365
2399
  type: "Callback"
2400
+ },
2401
+ {
2402
+ name: "receiverIfMakerIsSeller",
2403
+ type: "address"
2366
2404
  }
2367
2405
  ],
2368
2406
  Collateral: [
@@ -2397,7 +2435,7 @@ function hash(offer) {
2397
2435
  assets: offer.assets,
2398
2436
  obligationUnits: offer.obligationUnits,
2399
2437
  obligationShares: offer.obligationShares,
2400
- price: offer.price,
2438
+ tick: BigInt(offer.tick),
2401
2439
  maturity: BigInt(offer.maturity),
2402
2440
  expiry: BigInt(offer.expiry),
2403
2441
  group: offer.group,
@@ -2408,7 +2446,8 @@ function hash(offer) {
2408
2446
  callback: {
2409
2447
  address: offer.callback.address.toLowerCase(),
2410
2448
  data: offer.callback.data
2411
- }
2449
+ },
2450
+ receiverIfMakerIsSeller: offer.receiverIfMakerIsSeller.toLowerCase()
2412
2451
  },
2413
2452
  primaryType: "Offer",
2414
2453
  types
@@ -2503,6 +2542,12 @@ const takeEvent = {
2503
2542
  indexed: false,
2504
2543
  internalType: "bool"
2505
2544
  },
2545
+ {
2546
+ name: "sellerReceiver",
2547
+ type: "address",
2548
+ indexed: false,
2549
+ internalType: "address"
2550
+ },
2506
2551
  {
2507
2552
  name: "group",
2508
2553
  type: "bytes32",
@@ -2624,13 +2669,57 @@ function from$10(parameters) {
2624
2669
  };
2625
2670
  }
2626
2671
 
2672
+ //#endregion
2673
+ //#region src/core/Tick.ts
2674
+ /** ln(1 + 0.025), scaled by 1e18. Matches TickLib onchain constant. */
2675
+ const LN_ONE_PLUS_DELTA = 24692612590371501n;
2676
+ /** ln(2), scaled by 1e18. Matches TickLib onchain constant. */
2677
+ const LN2 = 693147180559945309n;
2678
+ const WAD$1 = 10n ** 18n;
2679
+ const WAD_SQUARED = 10n ** 36n;
2680
+ const PRICE_STEP = 10n ** 13n;
2681
+ const HALF_TICK_RANGE = 495n;
2682
+ /** Tick domain supported by Morpho V2. */
2683
+ const TICK_RANGE = 990;
2684
+ /**
2685
+ * Converts a tick to a wad price using the same approximation and rounding as TickLib.
2686
+ * @param tick - Tick value in the inclusive range [0, 990].
2687
+ * @returns The price in wad units.
2688
+ * @throws {@link InvalidTickError} If tick is not an integer in range [0, 990].
2689
+ */
2690
+ function tickToPrice(tick) {
2691
+ assertTick(tick);
2692
+ return divHalfDownUnchecked(divHalfDownUnchecked(WAD_SQUARED, WAD$1 + wExp(LN_ONE_PLUS_DELTA * (HALF_TICK_RANGE - BigInt(tick)))), PRICE_STEP) * PRICE_STEP;
2693
+ }
2694
+ function divHalfDownUnchecked(x, d) {
2695
+ return (x + (d - 1n) / 2n) / d;
2696
+ }
2697
+ function wExp(x) {
2698
+ if (x < 0n) return WAD_SQUARED / wExp(-x);
2699
+ const q = (x + LN2 / 2n) / LN2;
2700
+ const r = x - q * LN2;
2701
+ const secondTerm = r * r / (2n * WAD$1);
2702
+ const thirdTerm = secondTerm * r / (3n * WAD$1);
2703
+ return WAD$1 + r + secondTerm + thirdTerm << q;
2704
+ }
2705
+ function assertTick(tick) {
2706
+ if (!Number.isInteger(tick) || tick < 0 || tick > TICK_RANGE) throw new InvalidTickError(tick);
2707
+ }
2708
+ var InvalidTickError = class extends BaseError {
2709
+ name = "Tick.InvalidTickError";
2710
+ constructor(tick) {
2711
+ super(`Invalid tick: ${tick}. Tick must be an integer between 0 and ${TICK_RANGE}.`);
2712
+ }
2713
+ };
2714
+
2627
2715
  //#endregion
2628
2716
  //#region src/core/Quote.ts
2629
- const QuoteSchema = z$2.object({
2717
+ const SideInputSchema = z$2.object({ tick: z$2.number().int().min(0).max(TICK_RANGE).nullable() }).strict();
2718
+ const QuoteInputSchema = z$2.object({
2630
2719
  obligationId: z$2.string().transform(transformHex),
2631
- ask: z$2.object({ price: z$2.bigint({ coerce: true }).min(0n).max(maxUint256) }),
2632
- bid: z$2.object({ price: z$2.bigint({ coerce: true }).min(0n).max(maxUint256) })
2633
- });
2720
+ ask: SideInputSchema,
2721
+ bid: SideInputSchema
2722
+ }).strict();
2634
2723
  /**
2635
2724
  * Creates a quote for a given obligation.
2636
2725
  * @constructor
@@ -2640,16 +2729,16 @@ const QuoteSchema = z$2.object({
2640
2729
  *
2641
2730
  * @example
2642
2731
  * ```ts
2643
- * const quote = Quote.from({ obligationId: "0x123", ask: { price: 100n }, bid: { price: 100n } });
2732
+ * const quote = Quote.from({ obligationId: "0x123", ask: { tick: 500 }, bid: { tick: 510 } });
2644
2733
  * ```
2645
2734
  */
2646
2735
  function from$9(parameters) {
2647
2736
  try {
2648
- const parsedQuote = QuoteSchema.parse(parameters);
2737
+ const parsedQuote = QuoteInputSchema.parse(parameters);
2649
2738
  return {
2650
2739
  obligationId: parsedQuote.obligationId,
2651
- ask: parsedQuote.ask,
2652
- bid: parsedQuote.bid
2740
+ ask: sideFromTick(parsedQuote.ask),
2741
+ bid: sideFromTick(parsedQuote.bid)
2653
2742
  };
2654
2743
  } catch (error) {
2655
2744
  throw new InvalidQuoteError(error);
@@ -2661,6 +2750,12 @@ var InvalidQuoteError = class extends BaseError {
2661
2750
  super("Invalid quote.", { cause: error });
2662
2751
  }
2663
2752
  };
2753
+ function sideFromTick(side) {
2754
+ return {
2755
+ tick: side.tick,
2756
+ price: side.tick === null ? 0n : tickToPrice(side.tick)
2757
+ };
2758
+ }
2664
2759
 
2665
2760
  //#endregion
2666
2761
  //#region src/core/TradingFee.ts
@@ -2942,7 +3037,7 @@ const chains$1 = ({ chains }) => single("chain_ids", `Validates that offer chain
2942
3037
  });
2943
3038
  const maturity = ({ maturities }) => single("maturity", `Validates that offer maturity is one of: [${maturities.join(", ")}]`, (offer) => {
2944
3039
  const allowedMaturities = maturities.map((m) => from$16(m));
2945
- 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}` };
3040
+ if (!allowedMaturities.includes(offer.maturity)) return { message: `Maturity must be one of (${allowedMaturities.join(", ")}). Got: ${offer.maturity}` };
2946
3041
  });
2947
3042
  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) => {
2948
3043
  if (!isEmptyCallback(offer)) return { message: "Non-empty callbacks are not supported." };
@@ -2950,15 +3045,25 @@ const callback = ({ callbacks }) => single("callback", `Validates callbacks: buy
2950
3045
  if (isEmptyCallback(offer) && !offer.buy && !callbacks.includes(Type$1.SellWithEmptyCallback)) return { message: "Sell offers with empty callback not allowed." };
2951
3046
  });
2952
3047
  /**
2953
- * A validation rule that checks if the offer's tokens are allowed for its chain.
2954
- * @param assetsByChainId - Allowed assets indexed by chain id.
3048
+ * A validation rule that checks if the offer's loan token is allowed for its chain.
3049
+ * @param assetsByChainId - Allowed loan tokens indexed by chain id.
3050
+ * @returns The issue that was found. If the offer is valid, this will be undefined.
3051
+ */
3052
+ const loanToken = ({ assetsByChainId }) => single("loan_token", "Validates that offer loan token is in the allowed token list for the offer chain", (offer) => {
3053
+ const allowedLoanTokens = assetsByChainId[offer.chainId]?.map((asset) => asset.toLowerCase());
3054
+ if (!allowedLoanTokens || allowedLoanTokens.length === 0) return { message: `No allowed loan tokens for chain ${offer.chainId}` };
3055
+ if (!allowedLoanTokens.includes(offer.loanToken.toLowerCase())) return { message: "Loan token is not allowed" };
3056
+ });
3057
+ /**
3058
+ * A validation rule that checks if the offer's collateral tokens are allowed for its chain.
3059
+ * @param collateralAssetsByChainId - Allowed collateral tokens indexed by chain id.
2955
3060
  * @returns The issue that was found. If the offer is valid, this will be undefined.
2956
3061
  */
2957
- const token = ({ assetsByChainId }) => single("token", "Validates that offer loan token and collateral tokens are in the allowed assets list for the offer chain", (offer) => {
2958
- const allowedAssets = assetsByChainId[offer.chainId]?.map((asset) => asset.toLowerCase());
2959
- if (!allowedAssets || allowedAssets.length === 0) return { message: `No allowed assets for chain ${offer.chainId}` };
2960
- if (!allowedAssets.includes(offer.loanToken.toLowerCase())) return { message: "Loan token is not allowed" };
2961
- if (offer.collaterals.some((collateral) => !allowedAssets.includes(collateral.asset.toLowerCase()))) return { message: "Collateral is not allowed" };
3062
+ const collateralToken = ({ collateralAssetsByChainId }) => single("collateral_token", "Validates that offer collateral tokens are in the allowed token list for the offer chain", (offer) => {
3063
+ const allowedCollateralTokens = collateralAssetsByChainId[offer.chainId]?.map((asset) => asset.toLowerCase()) ?? [];
3064
+ if (allowedCollateralTokens.length === 0) return { message: `No allowed collateral tokens for chain ${offer.chainId}` };
3065
+ if (offer.collaterals.length === 0) return { message: "At least one collateral token is required" };
3066
+ if (offer.collaterals.some((collateral) => !allowedCollateralTokens.includes(collateral.asset.toLowerCase()))) return { message: "Collateral token is not allowed" };
2962
3067
  });
2963
3068
  /**
2964
3069
  * A validation rule that checks if the offer's oracle addresses are allowed for its chain.
@@ -3001,21 +3106,24 @@ const amountMutualExclusivity = () => single("amount_mutual_exclusivity", "Valid
3001
3106
  //#region src/gatekeeper/morphoRules.ts
3002
3107
  const morphoRules = (chains) => {
3003
3108
  const assetsByChainId = {};
3109
+ const collateralAssetsByChainId = {};
3004
3110
  const oraclesByChainId = {};
3005
3111
  for (const chain of chains) {
3006
3112
  assetsByChainId[chain.id] = assets[chain.id.toString()] ?? [];
3113
+ collateralAssetsByChainId[chain.id] = collateralAssets[chain.id.toString()] ?? [];
3007
3114
  oraclesByChainId[chain.id] = oracles$1[chain.id.toString()] ?? [];
3008
3115
  }
3009
3116
  return [
3010
3117
  sameMaker(),
3011
3118
  amountMutualExclusivity(),
3012
3119
  chains$1({ chains }),
3013
- maturity({ maturities: [MaturityType.EndOfMonth, MaturityType.EndOfNextMonth] }),
3120
+ maturity({ maturities: [MaturityType.EndOfWeek, MaturityType.EndOfNextWeek] }),
3014
3121
  callback({
3015
3122
  callbacks: [Type$1.BuyWithEmptyCallback, Type$1.SellWithEmptyCallback],
3016
3123
  allowedAddresses: []
3017
3124
  }),
3018
- token({ assetsByChainId }),
3125
+ loanToken({ assetsByChainId }),
3126
+ collateralToken({ collateralAssetsByChainId }),
3019
3127
  oracle({ oraclesByChainId })
3020
3128
  ];
3021
3129
  };
@@ -3023,7 +3131,7 @@ const morphoRules = (chains) => {
3023
3131
  //#endregion
3024
3132
  //#region src/gatekeeper/ConfigRules.ts
3025
3133
  /**
3026
- * Build the configured rules (maturities + callback addresses + loan tokens + oracles) for the provided chains.
3134
+ * Build the configured rules (maturities + callback addresses + loan tokens + collateral tokens + oracles) for the provided chains.
3027
3135
  * @param chains - Chains to include in the configured rules.
3028
3136
  * @returns Sorted list of config rules.
3029
3137
  */
@@ -3043,6 +3151,12 @@ function buildConfigRules(chains) {
3043
3151
  chain_id: chain.id,
3044
3152
  address: normalizeAddress(address)
3045
3153
  });
3154
+ const collateralTokens = collateralAssets[chain.id.toString()] ?? [];
3155
+ for (const address of collateralTokens) rules.push({
3156
+ type: "collateral_token",
3157
+ chain_id: chain.id,
3158
+ address: normalizeAddress(address)
3159
+ });
3046
3160
  const oracles = oracles$1[chain.id.toString()] ?? [];
3047
3161
  for (const address of oracles) rules.push({
3048
3162
  type: "oracle",
@@ -3070,6 +3184,10 @@ function buildConfigRulesChecksum(rules) {
3070
3184
  hash.update(`callback:${rule.chain_id}:${rule.callback_type}:${rule.address}\n`);
3071
3185
  continue;
3072
3186
  }
3187
+ if (rule.type === "collateral_token") {
3188
+ hash.update(`collateral_token:${rule.chain_id}:${rule.address}\n`);
3189
+ continue;
3190
+ }
3073
3191
  if (rule.type === "oracle") {
3074
3192
  hash.update(`oracle:${rule.chain_id}:${rule.address}\n`);
3075
3193
  continue;
@@ -3090,6 +3208,7 @@ function compareConfigRules(left, right) {
3090
3208
  return left.address.localeCompare(right.address);
3091
3209
  }
3092
3210
  if (left.type === "loan_token" && right.type === "loan_token") return left.address.localeCompare(right.address);
3211
+ if (left.type === "collateral_token" && right.type === "collateral_token") return left.address.localeCompare(right.address);
3093
3212
  if (left.type === "oracle" && right.type === "oracle") return left.address.localeCompare(right.address);
3094
3213
  return 0;
3095
3214
  }
@@ -3097,8 +3216,10 @@ function compareConfigRules(left, right) {
3097
3216
  //#endregion
3098
3217
  //#region src/api/Schema/BookResponse.ts
3099
3218
  function from$6(level) {
3219
+ const price = tickToPrice(level.tick);
3100
3220
  return {
3101
- price: level.price.toString(),
3221
+ tick: level.tick,
3222
+ price: price.toString(),
3102
3223
  assets: level.assets.toString(),
3103
3224
  count: level.count
3104
3225
  };
@@ -3144,6 +3265,7 @@ const RouterStatusResponse = z$1.object({
3144
3265
  * Creates an `ObligationResponse` from a `Obligation`.
3145
3266
  * @constructor
3146
3267
  * @param obligation - {@link Obligation}
3268
+ * @param quote - {@link Quote}
3147
3269
  * @returns The created `ObligationResponse`. {@link ObligationResponse}
3148
3270
  */
3149
3271
  function from$5(obligation, quote) {
@@ -3157,8 +3279,14 @@ function from$5(obligation, quote) {
3157
3279
  oracle: c.oracle
3158
3280
  })),
3159
3281
  maturity: obligation.maturity,
3160
- ask: { price: quote.ask.price.toString() },
3161
- bid: { price: quote.bid.price.toString() }
3282
+ ask: {
3283
+ tick: quote.ask.tick,
3284
+ price: quote.ask.price.toString()
3285
+ },
3286
+ bid: {
3287
+ tick: quote.bid.tick,
3288
+ price: quote.bid.price.toString()
3289
+ }
3162
3290
  };
3163
3291
  }
3164
3292
 
@@ -3201,11 +3329,12 @@ function from$4(input) {
3201
3329
  obligation_shares: input.obligationShares.toString(),
3202
3330
  start: input.start,
3203
3331
  expiry: input.expiry,
3204
- price: input.price.toString(),
3332
+ tick: input.tick,
3205
3333
  group: input.group,
3206
3334
  session: input.session,
3207
3335
  callback: input.callback.address,
3208
- callback_data: input.callback.data
3336
+ callback_data: input.callback.data,
3337
+ receiver_if_maker_is_seller: input.receiverIfMakerIsSeller
3209
3338
  },
3210
3339
  offer_hash: input.hash,
3211
3340
  obligation_id: id({
@@ -3272,7 +3401,7 @@ var InternalServerError = class extends APIError {
3272
3401
  super(STATUS_CODE.INTERNAL_SERVER_ERROR, message, "INTERNAL_SERVER_ERROR");
3273
3402
  }
3274
3403
  };
3275
- var BadRequestError = class extends APIError {
3404
+ var BadRequestError$1 = class extends APIError {
3276
3405
  constructor(message = "Invalid JSON format", details) {
3277
3406
  super(STATUS_CODE.BAD_REQUEST, message, "BAD_REQUEST", details);
3278
3407
  }
@@ -3294,7 +3423,7 @@ function success(args) {
3294
3423
  */
3295
3424
  function failure(err) {
3296
3425
  if (err instanceof APIError) return handleAPIError(err);
3297
- if (err instanceof SyntaxError) return handleAPIError(new BadRequestError(err.message));
3426
+ if (err instanceof SyntaxError) return handleAPIError(new BadRequestError$1(err.message));
3298
3427
  if (err instanceof z$2.ZodError) return handleAPIError(handleZodError(err));
3299
3428
  return handleAPIError(new InternalServerError());
3300
3429
  }
@@ -3344,7 +3473,7 @@ function __decorate(decorators, target, key, desc) {
3344
3473
  //#region src/api/Schema/openapi.ts
3345
3474
  const timestampExample = "2024-01-01T12:00:00.000Z";
3346
3475
  const offerCursorExample = "eyJvZmZzZXQiOjEwMH0";
3347
- const obligationCursorExample = "0x25690ae1aee324a005be565f3bcdd16dbf8daf7969b26c181c8b8f467dad9abc";
3476
+ const obligationCursorExample = "eyJzb3J0IjpbImlkIl0sImlkIjoiMHgxMjM0NTY3ODkwMTIzNDU2Nzg5MDEyMzQ1Njc4OTAxMjM0NTY3ODkwMTIzNDU2Nzg5MDEyMzQ1Njc4OTAiLCJhc2siOiIwIiwiYmlkIjoiMCIsIm1hdHVyaXR5IjoxNzYxOTIyODAwfQ";
3348
3477
  const offerExample = {
3349
3478
  offer: {
3350
3479
  obligation: {
@@ -3363,11 +3492,12 @@ const offerExample = {
3363
3492
  obligation_shares: "0",
3364
3493
  start: 1761922790,
3365
3494
  expiry: 1761922799,
3366
- price: "2750000000000000000",
3495
+ tick: 495,
3367
3496
  group: "0x000000000000000000000000000000000000000000000000000000000008b8f4",
3368
3497
  session: "0x0000000000000000000000000000000000000000000000000000000000000000",
3369
3498
  callback: "0x0000000000000000000000000000000000000000",
3370
- callback_data: "0x"
3499
+ callback_data: "0x",
3500
+ receiver_if_maker_is_seller: "0x7b093658BE7f90B63D7c359e8f408e503c2D9401"
3371
3501
  },
3372
3502
  offer_hash: "0xac4bd8318ec914f89f8af913f162230575b0ac0696a19256bc12138c5cfe1427",
3373
3503
  obligation_id: "0x25690ae1aee324a005be565f3bcdd16dbf8daf7969b26c181c8b8f467dad9abc",
@@ -3404,7 +3534,7 @@ const validateOfferExample = {
3404
3534
  assets: "369216000000000000000000",
3405
3535
  obligation_units: "0",
3406
3536
  obligation_shares: "0",
3407
- price: "2750000000000000000",
3537
+ tick: 495,
3408
3538
  maturity: 1761922799,
3409
3539
  expiry: 1761922799,
3410
3540
  start: 1761922790,
@@ -3421,7 +3551,8 @@ const validateOfferExample = {
3421
3551
  callback: {
3422
3552
  address: "0x0000000000000000000000000000000000000000",
3423
3553
  data: "0x"
3424
- }
3554
+ },
3555
+ receiver_if_maker_is_seller: "0x7b093658BE7f90B63D7c359e8f408e503c2D9401"
3425
3556
  };
3426
3557
  const routerStatusExample = {
3427
3558
  status: "live",
@@ -3492,11 +3623,23 @@ __decorate([ApiProperty({
3492
3623
  example: validateOfferExample.callback.data
3493
3624
  })], ValidateCallbackRequest.prototype, "data", void 0);
3494
3625
  var AskResponse = class {};
3626
+ __decorate([ApiProperty({
3627
+ type: "number",
3628
+ nullable: true,
3629
+ example: 500,
3630
+ description: "Best ask tick. Null when there is no active ask quote."
3631
+ })], AskResponse.prototype, "tick", void 0);
3495
3632
  __decorate([ApiProperty({
3496
3633
  type: "string",
3497
3634
  example: "1000000000000000000"
3498
3635
  })], AskResponse.prototype, "price", void 0);
3499
3636
  var BidResponse = class {};
3637
+ __decorate([ApiProperty({
3638
+ type: "number",
3639
+ nullable: true,
3640
+ example: 500,
3641
+ description: "Best bid tick. Null when there is no active bid quote."
3642
+ })], BidResponse.prototype, "tick", void 0);
3500
3643
  __decorate([ApiProperty({
3501
3644
  type: "string",
3502
3645
  example: "1000000000000000000"
@@ -3548,9 +3691,11 @@ __decorate([ApiProperty({
3548
3691
  example: offerExample.offer.expiry
3549
3692
  })], OfferDataResponse.prototype, "expiry", void 0);
3550
3693
  __decorate([ApiProperty({
3551
- type: "string",
3552
- example: offerExample.offer.price
3553
- })], OfferDataResponse.prototype, "price", void 0);
3694
+ type: "number",
3695
+ example: offerExample.offer.tick,
3696
+ minimum: 0,
3697
+ maximum: 990
3698
+ })], OfferDataResponse.prototype, "tick", void 0);
3554
3699
  __decorate([ApiProperty({
3555
3700
  type: "string",
3556
3701
  example: offerExample.offer.group
@@ -3567,6 +3712,10 @@ __decorate([ApiProperty({
3567
3712
  type: "string",
3568
3713
  example: offerExample.offer.callback_data
3569
3714
  })], OfferDataResponse.prototype, "callback_data", void 0);
3715
+ __decorate([ApiProperty({
3716
+ type: "string",
3717
+ example: offerExample.offer.receiver_if_maker_is_seller
3718
+ })], OfferDataResponse.prototype, "receiver_if_maker_is_seller", void 0);
3570
3719
  var OfferListItemResponse = class {};
3571
3720
  __decorate([ApiProperty({
3572
3721
  type: () => OfferDataResponse,
@@ -3791,9 +3940,11 @@ __decorate([ApiProperty({
3791
3940
  required: false
3792
3941
  })], ValidateOfferRequest.prototype, "obligation_shares", void 0);
3793
3942
  __decorate([ApiProperty({
3794
- type: "string",
3795
- example: validateOfferExample.price
3796
- })], ValidateOfferRequest.prototype, "price", void 0);
3943
+ type: "number",
3944
+ example: validateOfferExample.tick,
3945
+ minimum: 0,
3946
+ maximum: 990
3947
+ })], ValidateOfferRequest.prototype, "tick", void 0);
3797
3948
  __decorate([ApiProperty({
3798
3949
  type: "number",
3799
3950
  example: validateOfferExample.maturity
@@ -3834,6 +3985,10 @@ __decorate([ApiProperty({
3834
3985
  type: () => ValidateCallbackRequest,
3835
3986
  example: validateOfferExample.callback
3836
3987
  })], ValidateOfferRequest.prototype, "callback", void 0);
3988
+ __decorate([ApiProperty({
3989
+ type: "string",
3990
+ example: validateOfferExample.receiver_if_maker_is_seller
3991
+ })], ValidateOfferRequest.prototype, "receiver_if_maker_is_seller", void 0);
3837
3992
  var ValidateOffersRequest = class {};
3838
3993
  __decorate([ApiProperty({
3839
3994
  type: () => [ValidateOfferRequest],
@@ -3893,9 +4048,16 @@ __decorate([ApiProperty({
3893
4048
  description: "List of validation issues. Returned when any offer fails validation."
3894
4049
  })], ValidationFailureResponse.prototype, "data", void 0);
3895
4050
  var BookLevelResponse = class {};
4051
+ __decorate([ApiProperty({
4052
+ type: "number",
4053
+ example: 495,
4054
+ minimum: 0,
4055
+ maximum: 990
4056
+ })], BookLevelResponse.prototype, "tick", void 0);
3896
4057
  __decorate([ApiProperty({
3897
4058
  type: "string",
3898
- example: "2750000000000000000"
4059
+ example: "500000000000000000",
4060
+ description: "Price derived from tick, scaled by 1e18."
3899
4061
  })], BookLevelResponse.prototype, "price", void 0);
3900
4062
  __decorate([ApiProperty({
3901
4063
  type: "string",
@@ -3909,6 +4071,7 @@ const positionExample = {
3909
4071
  chain_id: 1,
3910
4072
  contract: "0xC9A9C45C0eB717f8b5F193Af6bAa05A1c0Ac5078",
3911
4073
  user: "0x7b093658BE7f90B63D7c359e8f408e503c2D9401",
4074
+ obligation_id: "0x12590ae1aee324a005be565f3bcdd16dbf8daf7969b26c181c8b8f467dad9f67",
3912
4075
  reserved: "200000000000000000000",
3913
4076
  block_number: 21345678
3914
4077
  };
@@ -3925,6 +4088,12 @@ __decorate([ApiProperty({
3925
4088
  type: "string",
3926
4089
  example: positionExample.user
3927
4090
  })], PositionListItemResponse.prototype, "user", void 0);
4091
+ __decorate([ApiProperty({
4092
+ type: "string",
4093
+ nullable: true,
4094
+ example: positionExample.obligation_id,
4095
+ description: "Obligation id this reserved amount belongs to, or null if no lots exist."
4096
+ })], PositionListItemResponse.prototype, "obligation_id", void 0);
3928
4097
  __decorate([ApiProperty({
3929
4098
  type: "string",
3930
4099
  example: positionExample.reserved
@@ -3952,7 +4121,7 @@ __decorate([ApiProperty({
3952
4121
  })], BookListResponse.prototype, "cursor", void 0);
3953
4122
  __decorate([ApiProperty({
3954
4123
  type: () => [BookLevelResponse],
3955
- description: "Aggregated book levels grouped by computed price."
4124
+ description: "Aggregated book levels grouped by offer tick."
3956
4125
  })], BookListResponse.prototype, "data", void 0);
3957
4126
  let BooksController = class BooksController {
3958
4127
  async getBook() {}
@@ -3962,7 +4131,7 @@ __decorate([
3962
4131
  methods: ["get"],
3963
4132
  path: "/v1/books/{obligationId}/{side}",
3964
4133
  summary: "Get aggregated book",
3965
- 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)."
4134
+ 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)."
3966
4135
  }),
3967
4136
  ApiParam({
3968
4137
  name: "obligationId",
@@ -3987,7 +4156,7 @@ __decorate([
3987
4156
  name: "limit",
3988
4157
  type: "number",
3989
4158
  example: 10,
3990
- description: "Maximum number of price levels to return."
4159
+ description: "Maximum number of tick levels to return."
3991
4160
  }),
3992
4161
  ApiResponse({
3993
4162
  status: 200,
@@ -4181,6 +4350,11 @@ const configRulesLoanTokenExample = {
4181
4350
  chain_id: 1,
4182
4351
  address: "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48"
4183
4352
  };
4353
+ const configRulesCollateralTokenExample = {
4354
+ type: "collateral_token",
4355
+ chain_id: 1,
4356
+ address: "0x7f39C581F595B53c5cb19bD0b3f8dA6c935E2Ca0"
4357
+ };
4184
4358
  const configRulesOracleExample = {
4185
4359
  type: "oracle",
4186
4360
  chain_id: 1,
@@ -4190,6 +4364,7 @@ const configRulesChecksumExample = "f1d2d2f924e986ac86fdf7b36c94bcdf";
4190
4364
  const configRulesPayloadExample = [
4191
4365
  configRulesMaturityExample,
4192
4366
  configRulesLoanTokenExample,
4367
+ configRulesCollateralTokenExample,
4193
4368
  configRulesOracleExample
4194
4369
  ];
4195
4370
  const configContractNames = [
@@ -4316,7 +4491,7 @@ __decorate([
4316
4491
  methods: ["get"],
4317
4492
  path: "/v1/config/rules",
4318
4493
  summary: "Get config rules",
4319
- description: "Returns configured rules (maturities, loan tokens, oracles) for supported chains."
4494
+ description: "Returns configured rules (maturities, loan tokens, collateral tokens, oracles) for supported chains."
4320
4495
  }),
4321
4496
  ApiQuery({
4322
4497
  name: "cursor",
@@ -4336,7 +4511,7 @@ __decorate([
4336
4511
  name: "types",
4337
4512
  type: ["string"],
4338
4513
  required: false,
4339
- example: "maturity,loan_token,oracle",
4514
+ example: "maturity,loan_token,collateral_token,oracle",
4340
4515
  description: "Filter by rule types (comma-separated).",
4341
4516
  style: "form",
4342
4517
  explode: false
@@ -4366,13 +4541,13 @@ __decorate([
4366
4541
  methods: ["get"],
4367
4542
  path: "/v1/obligations",
4368
4543
  summary: "List all obligations",
4369
- description: "Returns a list of obligations with their current best ask and bid. Obligations are sorted by their id in ascending order by default."
4544
+ 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."
4370
4545
  }),
4371
4546
  ApiQuery({
4372
4547
  name: "cursor",
4373
4548
  type: "string",
4374
4549
  example: obligationCursorExample,
4375
- description: "Obligation id cursor for pagination."
4550
+ description: "Pagination cursor in base64url-encoded format."
4376
4551
  }),
4377
4552
  ApiQuery({
4378
4553
  name: "limit",
@@ -4416,6 +4591,15 @@ __decorate([
4416
4591
  style: "form",
4417
4592
  explode: false
4418
4593
  }),
4594
+ ApiQuery({
4595
+ name: "sort",
4596
+ type: "string",
4597
+ required: false,
4598
+ example: "-ask,bid,maturity",
4599
+ description: "Sort order as comma-separated fields (`id`, `ask`, `bid`, `maturity`). Prefix with `-` for descending order. Max 3 fields.",
4600
+ style: "form",
4601
+ explode: false
4602
+ }),
4419
4603
  ApiResponse({
4420
4604
  status: 200,
4421
4605
  description: "Success",
@@ -4454,7 +4638,7 @@ __decorate([
4454
4638
  methods: ["get"],
4455
4639
  path: "/v1/users/{userAddress}/positions",
4456
4640
  summary: "Get user positions",
4457
- description: "Returns positions for a user with reserved balance. The reserved balance is the amount locked by active offers (max lot upper - offset - consumed)."
4641
+ 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."
4458
4642
  }),
4459
4643
  ApiParam({
4460
4644
  name: "userAddress",
@@ -4541,6 +4725,7 @@ function from$3(position) {
4541
4725
  chain_id: position.chainId,
4542
4726
  contract: position.contract,
4543
4727
  user: position.user,
4728
+ obligation_id: position.obligationId,
4544
4729
  reserved: position.reserved.toString(),
4545
4730
  block_number: position.blockNumber
4546
4731
  };
@@ -4549,11 +4734,13 @@ function from$3(position) {
4549
4734
  //#endregion
4550
4735
  //#region src/api/Schema/requests.ts
4551
4736
  const MAX_LIMIT = 100;
4552
- const DEFAULT_LIMIT$4 = 20;
4737
+ const DEFAULT_LIMIT$5 = 20;
4738
+ const MAX_OBLIGATION_SORT_FIELDS = 3;
4553
4739
  const CONFIG_RULES_MAX_LIMIT = 1e3;
4554
4740
  const CONFIG_RULES_DEFAULT_LIMIT = 100;
4555
4741
  const CONFIG_CONTRACTS_MAX_LIMIT = 1e3;
4556
4742
  const CONFIG_CONTRACTS_DEFAULT_LIMIT = 1e3;
4743
+ const OBLIGATION_SORT_ENTRY_REGEX = /^-?(id|ask|bid|maturity)$/;
4557
4744
  /** Validate cursor is a valid base64url-encoded JSON object.
4558
4745
  * Domain layer handles semantic validation of cursor fields. */
4559
4746
  function isValidBase64urlJson(val) {
@@ -4585,8 +4772,8 @@ const PaginationQueryParams = z$2.object({
4585
4772
  description: "Pagination cursor in base64url-encoded format",
4586
4773
  example: "eyJzaWRlIjoic2VsbCIsImN1cnJlbnRQcmljZSI6IjEwMDAwMDAwMDAwMDAwMDAwMDAiLCJibG9ja051bWJlciI6MSwiYXNzZXRzIjoiMTAwMDAwMDAwMDAwMDAwMDAwMCIsImhhc2giOiIweGRmZDY4NTllM2UwODJkMTkzODlhMWFlYzFiZGFkN2U4ZDkyZDk2YjFhYTc5NDBkYTkxYTMxMjVkMzFlM2JlNWIiLCJ0b3RhbFJldHVybmVkIjoxMCwibm93IjoxNjAwMDAwMDAwfQ"
4587
4774
  }),
4588
- limit: z$2.string().regex(/^[1-9]\d*$/, { message: "Limit must be a positive integer" }).transform((val) => Number.parseInt(val, 10)).pipe(z$2.number().max(MAX_LIMIT, { message: `Limit cannot exceed ${MAX_LIMIT}` })).optional().default(DEFAULT_LIMIT$4).meta({
4589
- description: `Limit maximum: ${MAX_LIMIT}. Default: ${DEFAULT_LIMIT$4}`,
4775
+ limit: z$2.string().regex(/^[1-9]\d*$/, { message: "Limit must be a positive integer" }).transform((val) => Number.parseInt(val, 10)).pipe(z$2.number().max(MAX_LIMIT, { message: `Limit cannot exceed ${MAX_LIMIT}` })).optional().default(DEFAULT_LIMIT$5).meta({
4776
+ description: `Limit maximum: ${MAX_LIMIT}. Default: ${DEFAULT_LIMIT$5}`,
4590
4777
  example: 10
4591
4778
  })
4592
4779
  });
@@ -4594,10 +4781,11 @@ const ConfigRuleTypes = z$2.enum([
4594
4781
  "maturity",
4595
4782
  "callback",
4596
4783
  "loan_token",
4784
+ "collateral_token",
4597
4785
  "oracle"
4598
4786
  ]);
4599
4787
  const GetConfigRulesQueryParams = z$2.object({
4600
- cursor: z$2.string().regex(/^(maturity|callback|loan_token|oracle):[1-9]\d*:.+$/, { message: "Cursor must be in the format type:chain_id:<value>" }).optional().meta({
4788
+ cursor: z$2.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({
4601
4789
  description: "Pagination cursor in type:chain_id:<value> format",
4602
4790
  example: "maturity:1:1730415600:end_of_next_month"
4603
4791
  }),
@@ -4607,7 +4795,7 @@ const GetConfigRulesQueryParams = z$2.object({
4607
4795
  }),
4608
4796
  types: csvArray(ConfigRuleTypes).meta({
4609
4797
  description: "Filter by rule types (comma-separated).",
4610
- example: "maturity,loan_token,oracle"
4798
+ example: "maturity,loan_token,collateral_token,oracle"
4611
4799
  }),
4612
4800
  chains: csvArray(z$2.string().regex(/^[1-9]\d*$/, { message: "Chain must be a positive integer" }).transform((val) => Number.parseInt(val, 10))).meta({
4613
4801
  description: "Filter by chain IDs (comma-separated).",
@@ -4682,9 +4870,12 @@ const GetOffersQueryParams = PaginationQueryParams.omit({ cursor: true }).extend
4682
4870
  });
4683
4871
  const GetObligationsQueryParams = z$2.object({
4684
4872
  ...PaginationQueryParams.shape,
4685
- cursor: z$2.string().optional().meta({
4686
- description: "Obligation id cursor",
4687
- example: "0x1234567890123456789012345678901234567890123456789012345678901234"
4873
+ cursor: z$2.string().optional().refine((val) => {
4874
+ if (!val) return true;
4875
+ return isValidBase64urlJson(val);
4876
+ }, { message: "Invalid cursor format. Must be a valid base64url-encoded cursor object" }).meta({
4877
+ description: "Pagination cursor in base64url-encoded format.",
4878
+ example: "eyJzb3J0IjpbImlkIl0sImlkIjoiMHgxMjM0NTY3ODkwMTIzNDU2Nzg5MDEyMzQ1Njc4OTAxMjM0NTY3ODkwMTIzNDU2Nzg5MDEyMzQ1Njc4OTAiLCJhc2siOiIwIiwiYmlkIjoiMCIsIm1hdHVyaXR5IjoxNzYxOTIyODAwfQ"
4688
4879
  }),
4689
4880
  chains: csvArray(z$2.string().regex(/^[1-9]\d*$/, { message: "Chain must be a positive integer" }).transform((val) => Number.parseInt(val, 10))).meta({
4690
4881
  description: "Filter by chain IDs (comma-separated).",
@@ -4701,18 +4892,35 @@ const GetObligationsQueryParams = z$2.object({
4701
4892
  maturities: csvArray(z$2.string().regex(/^[1-9]\d*$/, { message: "Maturity must be a positive integer" }).transform((val) => Number.parseInt(val, 10))).meta({
4702
4893
  description: "Filter by exact maturity timestamps (comma-separated, unix seconds).",
4703
4894
  example: "1761922800,1764524800"
4895
+ }),
4896
+ sort: csvArray(z$2.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) => {
4897
+ if (!entries) return;
4898
+ const seen = /* @__PURE__ */ new Set();
4899
+ for (const entry of entries) {
4900
+ const field = entry.startsWith("-") ? entry.slice(1) : entry;
4901
+ if (seen.has(field)) {
4902
+ ctx.addIssue({
4903
+ code: "custom",
4904
+ message: `Duplicate sort field: ${field}`
4905
+ });
4906
+ return;
4907
+ }
4908
+ seen.add(field);
4909
+ }
4910
+ }).meta({
4911
+ description: "Sort order as comma-separated fields. Prefix a field with '-' for descending order. Max 3 fields.",
4912
+ example: "-ask,bid,maturity"
4704
4913
  })
4705
4914
  });
4706
4915
  const GetObligationParams = z$2.object({ obligation_id: z$2.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({
4707
4916
  description: "Obligation id",
4708
4917
  example: "0x1234567890123456789012345678901234567890123456789012345678901234"
4709
4918
  }) });
4710
- /** Validate a book cursor format: {side, lastPrice, offersCursor} */
4919
+ /** Validate a book cursor format: {side, lastTick, offersCursor} */
4711
4920
  function isValidBookCursor(cursorString) {
4712
- const isNumericString = (value) => typeof value === "string" && /^-?\d+$/.test(value);
4713
4921
  try {
4714
4922
  const v = JSON.parse(Buffer.from(cursorString, "base64url").toString("utf8"));
4715
- return (v?.side === "buy" || v?.side === "sell") && isNumericString(v?.lastPrice) && (v?.offersCursor === null || typeof v?.offersCursor === "string");
4923
+ return (v?.side === "buy" || v?.side === "sell") && typeof v?.lastTick === "number" && Number.isInteger(v.lastTick) && (v?.offersCursor === null || typeof v?.offersCursor === "string");
4716
4924
  } catch {
4717
4925
  return false;
4718
4926
  }
@@ -4725,8 +4933,8 @@ const BookPaginationQueryParams = z$2.object({
4725
4933
  description: "Pagination cursor in base64url-encoded format for book levels",
4726
4934
  example: "eyJzaWRlIjoiYnV5IiwibGFzdFJhdGUiOiIxMDAwMDAwMDAwMDAwMDAwMDAwIiwib2ZmZXJzQ3Vyc29yIjpudWxsfQ"
4727
4935
  }),
4728
- limit: z$2.string().regex(/^[1-9]\d*$/, { message: "Limit must be a positive integer" }).transform((val) => Number.parseInt(val, 10)).pipe(z$2.number().max(MAX_LIMIT, { message: `Limit cannot exceed ${MAX_LIMIT}` })).optional().default(DEFAULT_LIMIT$4).meta({
4729
- description: `Limit maximum: ${MAX_LIMIT}. Default: ${DEFAULT_LIMIT$4}`,
4936
+ limit: z$2.string().regex(/^[1-9]\d*$/, { message: "Limit must be a positive integer" }).transform((val) => Number.parseInt(val, 10)).pipe(z$2.number().max(MAX_LIMIT, { message: `Limit cannot exceed ${MAX_LIMIT}` })).optional().default(DEFAULT_LIMIT$5).meta({
4937
+ description: `Limit maximum: ${MAX_LIMIT}. Default: ${DEFAULT_LIMIT$5}`,
4730
4938
  example: 10
4731
4939
  })
4732
4940
  });
@@ -4801,8 +5009,8 @@ async function getConfigRules(query, chains) {
4801
5009
  } catch (err) {
4802
5010
  return failure(err);
4803
5011
  }
4804
- if (cursorRule && typeFilter && !typeFilter.has(cursorRule.type)) return failure(new BadRequestError("Cursor type must match requested rule types"));
4805
- if (cursorRule && chainFilter && !chainFilter.has(cursorRule.chain_id)) return failure(new BadRequestError("Cursor chain_id must match requested chains"));
5012
+ if (cursorRule && typeFilter && !typeFilter.has(cursorRule.type)) return failure(new BadRequestError$1("Cursor type must match requested rule types"));
5013
+ if (cursorRule && chainFilter && !chainFilter.has(cursorRule.chain_id)) return failure(new BadRequestError$1("Cursor chain_id must match requested chains"));
4806
5014
  const startIndex = cursorRule ? findStartIndex$1(filteredRules, cursorRule) : 0;
4807
5015
  const page = filteredRules.slice(startIndex, startIndex + limit);
4808
5016
  const nextCursor = startIndex + limit < filteredRules.length && page.length > 0 ? formatCursor$1(page.at(-1)) : null;
@@ -4817,19 +5025,20 @@ function formatCursor$1(rule) {
4817
5025
  if (rule.type === "maturity") return `maturity:${rule.chain_id}:${rule.timestamp}:${rule.name}`;
4818
5026
  if (rule.type === "callback") return `callback:${rule.chain_id}:${rule.callback_type}:${rule.address.toLowerCase()}`;
4819
5027
  if (rule.type === "oracle") return `oracle:${rule.chain_id}:${rule.address.toLowerCase()}`;
5028
+ if (rule.type === "collateral_token") return `collateral_token:${rule.chain_id}:${rule.address.toLowerCase()}`;
4820
5029
  return `loan_token:${rule.chain_id}:${rule.address.toLowerCase()}`;
4821
5030
  }
4822
5031
  function parseCursor$1(cursor) {
4823
5032
  const [type, chain, ...rest] = cursor.split(":");
4824
- if (!type || !chain || rest.length === 0) throw new BadRequestError("Cursor must be in the format type:chain_id:<value>");
4825
- if (!isConfigRuleType(type)) throw new BadRequestError("Cursor has an invalid rule type");
5033
+ if (!type || !chain || rest.length === 0) throw new BadRequestError$1("Cursor must be in the format type:chain_id:<value>");
5034
+ if (!isConfigRuleType(type)) throw new BadRequestError$1("Cursor has an invalid rule type");
4826
5035
  const chain_id = Number.parseInt(chain, 10);
4827
- if (!Number.isFinite(chain_id)) throw new BadRequestError("Cursor has an invalid chain_id");
5036
+ if (!Number.isFinite(chain_id)) throw new BadRequestError$1("Cursor has an invalid chain_id");
4828
5037
  if (type === "maturity") {
4829
5038
  const timestampValue = Number.parseInt(rest[0] ?? "", 10);
4830
5039
  const nameValue = rest.slice(1).join(":");
4831
- if (!Number.isFinite(timestampValue) || nameValue.length === 0) throw new BadRequestError("Cursor must be in the format maturity:chain_id:timestamp:name");
4832
- if (!isMaturityType(nameValue)) throw new BadRequestError("Cursor has an invalid maturity name");
5040
+ if (!Number.isFinite(timestampValue) || nameValue.length === 0) throw new BadRequestError$1("Cursor must be in the format maturity:chain_id:timestamp:name");
5041
+ if (!isMaturityType(nameValue)) throw new BadRequestError$1("Cursor has an invalid maturity name");
4833
5042
  return {
4834
5043
  type,
4835
5044
  chain_id,
@@ -4840,8 +5049,8 @@ function parseCursor$1(cursor) {
4840
5049
  if (type === "callback") {
4841
5050
  const callbackTypeValue = rest[0] ?? "";
4842
5051
  const addressValue = rest.slice(1).join(":");
4843
- if (!callbackTypeValue || !addressValue) throw new BadRequestError("Cursor must be in the format callback:chain_id:callback_type:address");
4844
- if (!isCallbackType(callbackTypeValue)) throw new BadRequestError("Cursor has an invalid callback type");
5052
+ if (!callbackTypeValue || !addressValue) throw new BadRequestError$1("Cursor must be in the format callback:chain_id:callback_type:address");
5053
+ if (!isCallbackType(callbackTypeValue)) throw new BadRequestError$1("Cursor has an invalid callback type");
4845
5054
  return {
4846
5055
  type,
4847
5056
  chain_id,
@@ -4849,16 +5058,16 @@ function parseCursor$1(cursor) {
4849
5058
  address: parseAddress(addressValue, "Cursor address")
4850
5059
  };
4851
5060
  }
4852
- if (type === "loan_token" || type === "oracle") {
5061
+ if (type === "loan_token" || type === "collateral_token" || type === "oracle") {
4853
5062
  const addressValue = rest.join(":");
4854
- if (!addressValue) throw new BadRequestError(`Cursor must be in the format ${type}:chain_id:address`);
5063
+ if (!addressValue) throw new BadRequestError$1(`Cursor must be in the format ${type}:chain_id:address`);
4855
5064
  return {
4856
5065
  type,
4857
5066
  chain_id,
4858
5067
  address: parseAddress(addressValue, "Cursor address")
4859
5068
  };
4860
5069
  }
4861
- throw new BadRequestError("Cursor has an invalid rule type");
5070
+ throw new BadRequestError$1("Cursor has an invalid rule type");
4862
5071
  }
4863
5072
  function findStartIndex$1(rules, cursor) {
4864
5073
  let low = 0;
@@ -4872,11 +5081,11 @@ function findStartIndex$1(rules, cursor) {
4872
5081
  return low;
4873
5082
  }
4874
5083
  function parseAddress(address, label) {
4875
- if (!/^0x[a-fA-F0-9]{40}$/.test(address)) throw new BadRequestError(`${label} must be a valid 20-byte address`);
5084
+ if (!/^0x[a-fA-F0-9]{40}$/.test(address)) throw new BadRequestError$1(`${label} must be a valid 20-byte address`);
4876
5085
  return address.toLowerCase();
4877
5086
  }
4878
5087
  function isConfigRuleType(value) {
4879
- return value === "maturity" || value === "callback" || value === "loan_token" || value === "oracle";
5088
+ return value === "maturity" || value === "callback" || value === "loan_token" || value === "collateral_token" || value === "oracle";
4880
5089
  }
4881
5090
  function isMaturityType(value) {
4882
5091
  return Object.values(MaturityType).includes(value);
@@ -4885,7 +5094,7 @@ function parseMaturity(value) {
4885
5094
  try {
4886
5095
  return from$16(value);
4887
5096
  } catch (err) {
4888
- throw new BadRequestError(err instanceof Error ? err.message : "Invalid maturity timestamp");
5097
+ throw new BadRequestError$1(err instanceof Error ? err.message : "Invalid maturity timestamp");
4889
5098
  }
4890
5099
  }
4891
5100
  function isCallbackType(value) {
@@ -4968,7 +5177,7 @@ function formatValue(value) {
4968
5177
  async function validateOffers(body, gatekeeper) {
4969
5178
  const logger = getLogger();
4970
5179
  const result = safeParse("validate_offers", body, (issue) => issue.message);
4971
- if (!result.success) return failure(new BadRequestError(result.error.issues[0]?.message ?? "Invalid request body"));
5180
+ if (!result.success) return failure(new BadRequestError$1(result.error.issues[0]?.message ?? "Invalid request body"));
4972
5181
  const { offers: rawOffers } = result.data;
4973
5182
  const parsedOffers = [];
4974
5183
  const offerIndexByHash = /* @__PURE__ */ new Map();
@@ -4984,7 +5193,7 @@ async function validateOffers(body, gatekeeper) {
4984
5193
  } catch (err) {
4985
5194
  let message = err instanceof Error ? err.message : String(err);
4986
5195
  if (err instanceof InvalidOfferError) message = err.formattedMessage;
4987
- return failure(new BadRequestError(`Offer at index ${i} failed to parse: ${message}`));
5196
+ return failure(new BadRequestError$1(`Offer at index ${i} failed to parse: ${message}`));
4988
5197
  }
4989
5198
  }
4990
5199
  try {
@@ -5043,7 +5252,7 @@ function createApp(parameters) {
5043
5252
  return c.json(failure$4.body, failure$4.statusCode);
5044
5253
  }
5045
5254
  if (body === null || typeof body !== "object") {
5046
- const failure$3 = failure(new BadRequestError("Request body must be a JSON object"));
5255
+ const failure$3 = failure(new BadRequestError$1("Request body must be a JSON object"));
5047
5256
  return c.json(failure$3.body, failure$3.statusCode);
5048
5257
  }
5049
5258
  const { statusCode, body: payload } = await validateOffers(body, gatekeeper);
@@ -5149,7 +5358,7 @@ function now() {
5149
5358
 
5150
5359
  //#endregion
5151
5360
  //#region src/database/drizzle/VERSION.ts
5152
- const VERSION = "router_v1.6";
5361
+ const VERSION = "router_v1.8";
5153
5362
 
5154
5363
  //#endregion
5155
5364
  //#region src/database/drizzle/schema.ts
@@ -5301,10 +5510,7 @@ const offers = s.table(EnumTableName.OFFERS, {
5301
5510
  precision: 78,
5302
5511
  scale: 0
5303
5512
  }).notNull().default("0"),
5304
- price: numeric("price", {
5305
- precision: 78,
5306
- scale: 0
5307
- }).notNull(),
5513
+ tick: integer("tick").notNull(),
5308
5514
  maturity: integer("maturity").notNull(),
5309
5515
  expiry: integer("expiry").notNull(),
5310
5516
  start: integer("start").notNull(),
@@ -5315,6 +5521,7 @@ const offers = s.table(EnumTableName.OFFERS, {
5315
5521
  buy: boolean("buy").notNull(),
5316
5522
  callbackAddress: varchar("callback_address", { length: 42 }).notNull(),
5317
5523
  callbackData: text("callback_data").notNull(),
5524
+ receiverIfMakerIsSeller: varchar("receiver_if_maker_is_seller", { length: 42 }),
5318
5525
  blockNumber: bigint("block_number", { mode: "number" }).notNull(),
5319
5526
  updatedAt: timestamp("updated_at").defaultNow().notNull()
5320
5527
  }, (table) => [
@@ -5369,6 +5576,7 @@ const lots = s.table(EnumTableName.LOTS, {
5369
5576
  user: varchar("user", { length: 42 }).notNull(),
5370
5577
  contract: varchar("contract", { length: 42 }).notNull(),
5371
5578
  group: varchar("group", { length: 66 }).notNull(),
5579
+ obligationId: varchar("obligation_id", { length: 66 }).notNull(),
5372
5580
  lower: numeric("lower", {
5373
5581
  precision: 78,
5374
5582
  scale: 0
@@ -5383,7 +5591,8 @@ const lots = s.table(EnumTableName.LOTS, {
5383
5591
  table.chainId,
5384
5592
  table.user,
5385
5593
  table.contract,
5386
- table.group
5594
+ table.group,
5595
+ table.obligationId
5387
5596
  ],
5388
5597
  name: "lots_pk"
5389
5598
  }),
@@ -5419,6 +5628,7 @@ const offsets = s.table(EnumTableName.OFFSETS, {
5419
5628
  user: varchar("user", { length: 42 }).notNull(),
5420
5629
  contract: varchar("contract", { length: 42 }).notNull(),
5421
5630
  group: varchar("group", { length: 66 }).notNull(),
5631
+ obligationId: varchar("obligation_id", { length: 66 }).notNull(),
5422
5632
  value: numeric("value", {
5423
5633
  precision: 78,
5424
5634
  scale: 0
@@ -5428,7 +5638,8 @@ const offsets = s.table(EnumTableName.OFFSETS, {
5428
5638
  table.chainId,
5429
5639
  table.user,
5430
5640
  table.contract,
5431
- table.group
5641
+ table.group,
5642
+ table.obligationId
5432
5643
  ],
5433
5644
  name: "offsets_pk"
5434
5645
  }), foreignKey({
@@ -6019,6 +6230,7 @@ function decodeCallbacks(parameters) {
6019
6230
  positionContract: loanToken,
6020
6231
  positionUser: offer.maker,
6021
6232
  group: offer.group,
6233
+ obligationId: obligationId(offer),
6022
6234
  size: offer.assets
6023
6235
  });
6024
6236
  callbacks.push({
@@ -6691,7 +6903,7 @@ async function* collectPrices(parameters) {
6691
6903
  //#region src/indexer/collectors/CollectorBuilder.ts
6692
6904
  function createBuilder(parameters) {
6693
6905
  const { client, db, gatekeeper, options: { maxBlockNumber, blockWindow, interval } = {} } = parameters;
6694
- const createCollector = (name, collect) => create$16({
6906
+ const createCollector = (name, collect) => create$17({
6695
6907
  name,
6696
6908
  collect,
6697
6909
  client,
@@ -6795,7 +7007,7 @@ function from$1(config) {
6795
7007
  retryAttempts,
6796
7008
  retryDelayMs
6797
7009
  });
6798
- return create$18({
7010
+ return create$19({
6799
7011
  client,
6800
7012
  collectors: [
6801
7013
  offersCollector,
@@ -6805,7 +7017,7 @@ function from$1(config) {
6805
7017
  ]
6806
7018
  });
6807
7019
  }
6808
- function create$18(params) {
7020
+ function create$19(params) {
6809
7021
  const { collectors, client } = params;
6810
7022
  const indexerId = `${client.chain.id.toString()}.indexer`;
6811
7023
  const tracer = getTracer(`router.${indexerId}`);
@@ -6834,7 +7046,7 @@ function create$18(params) {
6834
7046
 
6835
7047
  //#endregion
6836
7048
  //#region src/indexer/collectors/Admin.ts
6837
- function create$17(parameters) {
7049
+ function create$18(parameters) {
6838
7050
  const collector = "admin";
6839
7051
  const { client, db, options: { maxBatchSize = 25, maxBlockNumber } = {} } = parameters;
6840
7052
  const maxBlockNumberBI = maxBlockNumber !== void 0 ? BigInt(maxBlockNumber) : void 0;
@@ -7074,8 +7286,8 @@ const names = [
7074
7286
  "positions",
7075
7287
  "prices"
7076
7288
  ];
7077
- function create$16({ name, collect, client, db, options }) {
7078
- const admin = create$17({
7289
+ function create$17({ name, collect, client, db, options }) {
7290
+ const admin = create$18({
7079
7291
  client,
7080
7292
  db,
7081
7293
  options
@@ -7202,7 +7414,7 @@ function start(collector) {
7202
7414
  //#endregion
7203
7415
  //#region src/database/domains/Blocks.ts
7204
7416
  /** Postgres implementation. */
7205
- const create$15 = (config) => {
7417
+ const create$16 = (config) => {
7206
7418
  const { db, chainRegistry } = config;
7207
7419
  const getChain = async (chainId) => {
7208
7420
  const rows = await db.select({
@@ -7381,14 +7593,14 @@ const create$15 = (config) => {
7381
7593
 
7382
7594
  //#endregion
7383
7595
  //#region src/database/domains/Book.ts
7384
- const DEFAULT_LIMIT$3 = 100;
7596
+ const DEFAULT_LIMIT$4 = 100;
7385
7597
  const MAX_TOTAL_OFFERS = 500;
7386
- function create$14(config) {
7598
+ function create$15(config) {
7387
7599
  const db = config.db;
7388
7600
  const logger = getLogger();
7389
7601
  const getOffers = async (parameters) => {
7390
7602
  const { side, obligationId, cursor: cursorString } = parameters;
7391
- const requestedLimit = parameters.limit ?? DEFAULT_LIMIT$3;
7603
+ const requestedLimit = parameters.limit ?? DEFAULT_LIMIT$4;
7392
7604
  const priceSortDirection = side === "sell" ? "asc" : "desc";
7393
7605
  const inputCursor = Cursor.decode(cursorString, logger);
7394
7606
  if (cursorString != null && inputCursor === null) return {
@@ -7420,7 +7632,8 @@ function create$14(config) {
7420
7632
  };
7421
7633
  return {
7422
7634
  get: async (parameters) => {
7423
- const { side, obligationId, cursor: cursorString, limit = DEFAULT_LIMIT$3 } = parameters;
7635
+ const { side, obligationId, cursor: cursorString, limit = DEFAULT_LIMIT$4 } = parameters;
7636
+ const tickSortDirection = side === "sell" ? "asc" : "desc";
7424
7637
  const inputCursor = LevelCursor.decode(cursorString, logger);
7425
7638
  if (cursorString != null && inputCursor === null) return {
7426
7639
  levels: [],
@@ -7435,23 +7648,23 @@ function create$14(config) {
7435
7648
  cursor: inputCursor?.offersCursor ?? void 0,
7436
7649
  limit: fetchLimit
7437
7650
  });
7438
- const priceMap = /* @__PURE__ */ new Map();
7651
+ const tickMap = /* @__PURE__ */ new Map();
7439
7652
  for (const row of rows) {
7440
- const priceKey = row.price.toString();
7441
- const existing = priceMap.get(priceKey);
7653
+ const existing = tickMap.get(row.tick);
7442
7654
  if (existing) {
7443
7655
  existing.assets += row.takeable;
7444
7656
  existing.count += 1;
7445
- } else priceMap.set(priceKey, {
7657
+ } else tickMap.set(row.tick, {
7446
7658
  assets: row.takeable,
7447
7659
  count: 1
7448
7660
  });
7449
7661
  }
7450
- const levels = Array.from(priceMap.entries()).map(([price, data]) => ({
7451
- price: BigInt(price),
7452
- assets: data.assets,
7453
- count: data.count
7662
+ const levels = Array.from(tickMap.entries()).map(([tick, level]) => ({
7663
+ tick,
7664
+ assets: level.assets,
7665
+ count: level.count
7454
7666
  }));
7667
+ levels.sort((a, b) => tickSortDirection === "asc" ? a.tick - b.tick : b.tick - a.tick);
7455
7668
  const paginatedLevels = levels.slice(0, limit);
7456
7669
  const hasMore = levels.length > limit || offersNextCursor !== null;
7457
7670
  const lastLevel = paginatedLevels[paginatedLevels.length - 1];
@@ -7497,14 +7710,14 @@ async function _getOffers(db, params) {
7497
7710
  AND (s.code IS NULL OR s.code = ${Status.VALID})
7498
7711
  ORDER BY
7499
7712
  o.group_chain_id, o.group_maker, o."group_group",
7500
- o.price::numeric ${priceSortDirection === "asc" ? sql`ASC` : sql`DESC`}, o.block_number ASC, o.assets DESC, o.hash ASC
7713
+ o.tick ${priceSortDirection === "asc" ? sql`ASC` : sql`DESC`}, o.block_number ASC, o.assets DESC, o.hash ASC
7501
7714
  ),
7502
7715
  enriched AS (
7503
7716
  SELECT
7504
7717
  w.*,
7505
7718
  g.consumed, g.chain_id, obl.loan_token,
7506
7719
  CASE WHEN ${priceSortDirection === "asc" ? sql`TRUE` : sql`FALSE`}
7507
- THEN w.price::numeric ELSE -w.price::numeric END AS price_norm,
7720
+ THEN w.tick ELSE -w.tick END AS tick_norm,
7508
7721
  w.block_number AS block_norm,
7509
7722
  -w.assets AS assets_norm,
7510
7723
  w.hash AS hash_norm
@@ -7521,33 +7734,35 @@ async function _getOffers(db, params) {
7521
7734
  FROM enriched e
7522
7735
  ${cursor != null ? sql`
7523
7736
  WHERE
7524
- (e.price_norm, e.block_norm, e.assets_norm, e.hash_norm)
7737
+ (e.tick_norm, e.block_norm, e.assets_norm, e.hash_norm)
7525
7738
  > (
7526
7739
  CASE WHEN ${priceSortDirection === "asc" ? sql`TRUE` : sql`FALSE`}
7527
- THEN ${cursor.price}::numeric ELSE -${cursor.price}::numeric END,
7740
+ THEN ${cursor.tick}::integer ELSE -${cursor.tick}::integer END,
7528
7741
  ${cursor.blockNumber},
7529
7742
  -${cursor.assets}::numeric,
7530
7743
  ${cursor.hash}
7531
7744
  )` : sql``}
7532
- ORDER BY e.price::numeric ${priceSortDirection === "asc" ? sql`ASC` : sql`DESC`}, e.block_number ASC, e.assets DESC, e.hash ASC
7745
+ ORDER BY e.tick ${priceSortDirection === "asc" ? sql`ASC` : sql`DESC`}, e.block_number ASC, e.assets DESC, e.hash ASC
7533
7746
  LIMIT ${limit}
7534
7747
  ),
7535
- -- Compute sum of offsets per position
7748
+ -- Compute sum of offsets per position and obligation
7536
7749
  position_offsets AS (
7537
7750
  SELECT
7538
7751
  chain_id,
7539
7752
  "user",
7540
7753
  contract,
7754
+ obligation_id,
7541
7755
  SUM(value::numeric) AS total_offset
7542
7756
  FROM ${offsets}
7543
- GROUP BY chain_id, "user", contract
7757
+ GROUP BY chain_id, "user", contract, obligation_id
7544
7758
  ),
7545
- -- Compute position_consumed: sum of consumed from all groups with lots on each position (converted to lot terms)
7759
+ -- Compute position_consumed: sum of consumed from all groups with lots on each position+obligation (converted to lot terms)
7546
7760
  position_consumed AS (
7547
7761
  SELECT
7548
7762
  l.chain_id,
7549
7763
  l.contract,
7550
7764
  l."user",
7765
+ l.obligation_id,
7551
7766
  SUM(
7552
7767
  CASE
7553
7768
  WHEN wo.assets::numeric > 0
@@ -7564,7 +7779,7 @@ async function _getOffers(db, params) {
7564
7779
  ON wo.group_chain_id = g.chain_id
7565
7780
  AND LOWER(wo.group_maker) = LOWER(g.maker)
7566
7781
  AND wo.group_group = g."group"
7567
- GROUP BY l.chain_id, l.contract, l."user"
7782
+ GROUP BY l.chain_id, l.contract, l."user", l.obligation_id
7568
7783
  ),
7569
7784
  -- Compute callback contributions with lot balance
7570
7785
  callback_contributions AS (
@@ -7572,7 +7787,7 @@ async function _getOffers(db, params) {
7572
7787
  p.hash,
7573
7788
  p.obligation_id,
7574
7789
  p.assets,
7575
- p.price,
7790
+ p.tick,
7576
7791
  p.obligation_units,
7577
7792
  p.obligation_shares,
7578
7793
  p.maturity,
@@ -7582,6 +7797,7 @@ async function _getOffers(db, params) {
7582
7797
  p.buy,
7583
7798
  p.callback_address,
7584
7799
  p.callback_data,
7800
+ p.receiver_if_maker_is_seller,
7585
7801
  p.block_number,
7586
7802
  p.group_chain_id,
7587
7803
  p.group_maker,
@@ -7617,6 +7833,7 @@ async function _getOffers(db, params) {
7617
7833
  AND LOWER(l.contract) = LOWER(c.position_contract)
7618
7834
  AND LOWER(l."user") = LOWER(c.position_user)
7619
7835
  AND l."group" = p.group_group
7836
+ AND l.obligation_id = p.obligation_id
7620
7837
  LEFT JOIN ${positions} pos
7621
7838
  ON pos.chain_id = c.position_chain_id
7622
7839
  AND LOWER(pos.contract) = LOWER(c.position_contract)
@@ -7625,10 +7842,12 @@ async function _getOffers(db, params) {
7625
7842
  ON pos_offsets.chain_id = c.position_chain_id
7626
7843
  AND LOWER(pos_offsets.contract) = LOWER(c.position_contract)
7627
7844
  AND LOWER(pos_offsets."user") = LOWER(c.position_user)
7845
+ AND pos_offsets.obligation_id = p.obligation_id
7628
7846
  LEFT JOIN position_consumed pc
7629
7847
  ON pc.chain_id = c.position_chain_id
7630
7848
  AND LOWER(pc.contract) = LOWER(c.position_contract)
7631
7849
  AND LOWER(pc."user") = LOWER(c.position_user)
7850
+ AND pc.obligation_id = p.obligation_id
7632
7851
  ),
7633
7852
  -- Compute contribution per callback in loan terms (loan token only — collateral positions are not indexed)
7634
7853
  callback_loan_contribution AS (
@@ -7646,7 +7865,7 @@ async function _getOffers(db, params) {
7646
7865
  hash,
7647
7866
  obligation_id,
7648
7867
  assets,
7649
- price,
7868
+ tick,
7650
7869
  obligation_units,
7651
7870
  obligation_shares,
7652
7871
  maturity,
@@ -7656,6 +7875,7 @@ async function _getOffers(db, params) {
7656
7875
  buy,
7657
7876
  callback_address,
7658
7877
  callback_data,
7878
+ receiver_if_maker_is_seller,
7659
7879
  block_number,
7660
7880
  group_chain_id,
7661
7881
  group_maker,
@@ -7672,16 +7892,17 @@ async function _getOffers(db, params) {
7672
7892
  WHERE clc.callback_id IS NOT NULL
7673
7893
  ORDER BY clc.hash, clc.position_chain_id, clc.position_contract, clc.position_user, clc.contribution_in_loan DESC
7674
7894
  ) deduped
7675
- GROUP BY hash, obligation_id, assets, price, obligation_units, obligation_shares, maturity, expiry, start, group_group, buy,
7676
- callback_address, callback_data, block_number, group_chain_id, group_maker,
7677
- consumed, chain_id, loan_token, session
7895
+ GROUP BY hash, obligation_id, assets, tick, obligation_units, obligation_shares, maturity, expiry, start, group_group, buy,
7896
+ callback_address, callback_data, block_number, group_chain_id, group_maker,
7897
+ consumed, chain_id, loan_token, session, receiver_if_maker_is_seller
7678
7898
  UNION ALL
7679
7899
  -- Sell offers without callbacks: collateral positions not indexed, takeable = assets - consumed
7680
7900
  SELECT
7681
- p.hash, p.obligation_id, p.assets, p.price,
7901
+ p.hash, p.obligation_id, p.assets, p.tick,
7682
7902
  p.obligation_units, p.obligation_shares,
7683
7903
  p.maturity, p.expiry, p.start, p.group_group,
7684
7904
  p.buy, p.callback_address, p.callback_data,
7905
+ p.receiver_if_maker_is_seller,
7685
7906
  p.block_number, p.group_chain_id, p.group_maker,
7686
7907
  p.consumed, p.chain_id, p.loan_token, p.session,
7687
7908
  0 AS total_available
@@ -7700,7 +7921,7 @@ async function _getOffers(db, params) {
7700
7921
  oc.obligation_units,
7701
7922
  oc.obligation_shares,
7702
7923
  oc.consumed,
7703
- oc.price,
7924
+ oc.tick,
7704
7925
  oc.maturity,
7705
7926
  oc.expiry,
7706
7927
  oc.start,
@@ -7710,6 +7931,7 @@ async function _getOffers(db, params) {
7710
7931
  oc.loan_token,
7711
7932
  oc.callback_address,
7712
7933
  oc.callback_data,
7934
+ oc.receiver_if_maker_is_seller,
7713
7935
  oc.block_number,
7714
7936
  oc.session,
7715
7937
  COALESCE(oc.total_available, 0) AS available,
@@ -7732,20 +7954,21 @@ async function _getOffers(db, params) {
7732
7954
  ))
7733
7955
  END > 0
7734
7956
  ORDER BY
7735
- oc.price::numeric ${priceSortDirection === "asc" ? sql`ASC` : sql`DESC`},
7957
+ oc.tick ${priceSortDirection === "asc" ? sql`ASC` : sql`DESC`},
7736
7958
  oc.block_number ASC,
7737
7959
  oc.assets DESC,
7738
7960
  oc.hash ASC;
7739
7961
  `);
7740
7962
  return {
7741
7963
  rows: raw.rows.map((row) => {
7964
+ const receiverIfMakerIsSeller = (row.receiver_if_maker_is_seller ?? row.group_maker).toLowerCase();
7742
7965
  return {
7743
7966
  hash: row.hash,
7744
7967
  maker: row.group_maker,
7745
7968
  assets: BigInt(row.assets),
7746
7969
  obligationUnits: BigInt(row.obligation_units ?? 0),
7747
7970
  obligationShares: BigInt(row.obligation_shares ?? 0),
7748
- price: BigInt(row.price),
7971
+ tick: row.tick,
7749
7972
  maturity: row.maturity,
7750
7973
  expiry: row.expiry,
7751
7974
  start: row.start,
@@ -7763,6 +7986,7 @@ async function _getOffers(db, params) {
7763
7986
  address: row.callback_address,
7764
7987
  data: row.callback_data
7765
7988
  },
7989
+ receiverIfMakerIsSeller,
7766
7990
  blockNumber: row.block_number,
7767
7991
  consumed: BigInt(row.consumed ?? 0),
7768
7992
  available: BigInt(String(row.available ?? "0").split(".")[0] ?? "0"),
@@ -7777,7 +8001,7 @@ let Cursor;
7777
8001
  function encode(row, totalReturned, now, side) {
7778
8002
  return Buffer.from(JSON.stringify({
7779
8003
  side,
7780
- price: row.price.toString(),
8004
+ tick: row.tick,
7781
8005
  blockNumber: row.blockNumber,
7782
8006
  assets: row.assets.toString(),
7783
8007
  hash: row.hash,
@@ -7788,10 +8012,9 @@ let Cursor;
7788
8012
  _Cursor.encode = encode;
7789
8013
  function decode(cursorString, logger) {
7790
8014
  if (cursorString == null) return null;
7791
- const isNumericString = (value) => typeof value === "string" && /^-?\d+$/.test(value);
7792
8015
  try {
7793
8016
  const v = JSON.parse(Buffer.from(cursorString, "base64url").toString("utf8"));
7794
- 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;
8017
+ 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;
7795
8018
  throw new Error("Invalid cursor");
7796
8019
  } catch {
7797
8020
  logger.error({
@@ -7809,7 +8032,7 @@ let LevelCursor;
7809
8032
  function encode(lastLevel, offersCursor, side, now) {
7810
8033
  return Buffer.from(JSON.stringify({
7811
8034
  side,
7812
- lastPrice: lastLevel.price.toString(),
8035
+ lastTick: lastLevel.tick,
7813
8036
  now,
7814
8037
  offersCursor
7815
8038
  })).toString("base64url");
@@ -7817,10 +8040,9 @@ let LevelCursor;
7817
8040
  _LevelCursor.encode = encode;
7818
8041
  function decode(cursorString, logger) {
7819
8042
  if (cursorString == null) return null;
7820
- const isNumericString = (value) => typeof value === "string" && /^-?\d+$/.test(value);
7821
8043
  try {
7822
8044
  const v = JSON.parse(Buffer.from(cursorString, "base64url").toString("utf8"));
7823
- 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;
8045
+ 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;
7824
8046
  throw new Error("Invalid book cursor");
7825
8047
  } catch {
7826
8048
  logger.error({
@@ -7856,7 +8078,7 @@ const DEFAULT_BATCH_SIZE = 4e3;
7856
8078
  * @param db - Database core instance.
7857
8079
  * @returns Callbacks domain. {@link CallbacksDomain}
7858
8080
  */
7859
- function create$13(db) {
8081
+ function create$14(db) {
7860
8082
  return {
7861
8083
  upsert: async (inputs) => {
7862
8084
  if (inputs.length === 0) return;
@@ -7911,7 +8133,7 @@ function create$13(db) {
7911
8133
 
7912
8134
  //#endregion
7913
8135
  //#region src/database/domains/Consumed.ts
7914
- function create$12(db) {
8136
+ function create$13(db) {
7915
8137
  return {
7916
8138
  create: async (events) => {
7917
8139
  if (events.length === 0) return;
@@ -7959,7 +8181,7 @@ function create$12(db) {
7959
8181
  * @param db - Database core instance.
7960
8182
  * @returns Groups domain. {@link GroupsDomain}
7961
8183
  */
7962
- function create$11(db) {
8184
+ function create$12(db) {
7963
8185
  return { create: async (groups$1) => {
7964
8186
  if (groups$1.length === 0) return;
7965
8187
  const rows = groups$1.map((group) => ({
@@ -7975,34 +8197,36 @@ function create$11(db) {
7975
8197
 
7976
8198
  //#endregion
7977
8199
  //#region src/database/domains/Lots.ts
7978
- function create$10(db) {
8200
+ function create$11(db) {
7979
8201
  return {
7980
8202
  get: async (parameters) => {
7981
- const { chainId, user, contract, group } = parameters ?? {};
8203
+ const { chainId, user, contract, group, obligationId } = parameters ?? {};
7982
8204
  const conditions = [];
7983
8205
  if (chainId !== void 0) conditions.push(eq(lots.chainId, chainId));
7984
8206
  if (user !== void 0) conditions.push(eq(lots.user, user.toLowerCase()));
7985
8207
  if (contract !== void 0) conditions.push(eq(lots.contract, contract.toLowerCase()));
7986
8208
  if (group !== void 0) conditions.push(eq(lots.group, group));
8209
+ if (obligationId !== void 0) conditions.push(eq(lots.obligationId, obligationId));
7987
8210
  return (await db.select().from(lots).where(conditions.length > 0 ? and(...conditions) : void 0)).map((row) => ({
7988
8211
  chainId: row.chainId,
7989
8212
  user: row.user,
7990
8213
  contract: row.contract,
7991
8214
  group: row.group,
8215
+ obligationId: row.obligationId,
7992
8216
  lower: BigInt(row.lower),
7993
8217
  upper: BigInt(row.upper)
7994
8218
  }));
7995
8219
  },
7996
8220
  create: async (parameters) => {
7997
8221
  if (parameters.length === 0) return;
7998
- const lotsByPositionGroup = /* @__PURE__ */ new Map();
8222
+ const lotsByKey = /* @__PURE__ */ new Map();
7999
8223
  for (const offer of parameters) {
8000
- const key = `${offer.positionChainId}-${offer.positionContract}-${offer.positionUser}-${offer.group}`.toLowerCase();
8001
- const existing = lotsByPositionGroup.get(key);
8002
- if (!existing || offer.size > existing.size) lotsByPositionGroup.set(key, offer);
8224
+ const key = `${offer.positionChainId}-${offer.positionContract}-${offer.positionUser}-${offer.group}-${offer.obligationId}`.toLowerCase();
8225
+ const existing = lotsByKey.get(key);
8226
+ if (!existing || offer.size > existing.size) lotsByKey.set(key, offer);
8003
8227
  }
8004
- 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) {
8005
- 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())));
8228
+ 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) {
8229
+ 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())));
8006
8230
  const newLower = BigInt(maxUpperResult[0]?.maxUpper ?? "0");
8007
8231
  const newUpper = newLower + offer.size;
8008
8232
  await db.insert(lots).values({
@@ -8010,6 +8234,7 @@ function create$10(db) {
8010
8234
  user: offer.positionUser.toLowerCase(),
8011
8235
  contract: offer.positionContract.toLowerCase(),
8012
8236
  group: offer.group.toLowerCase(),
8237
+ obligationId: offer.obligationId.toLowerCase(),
8013
8238
  lower: newLower.toString(),
8014
8239
  upper: newUpper.toString()
8015
8240
  });
@@ -8025,45 +8250,67 @@ function create$10(db) {
8025
8250
  * @param db - Database core instance.
8026
8251
  * @returns Obligations domain. {@link ObligationsDomain}
8027
8252
  */
8028
- function create$9(db) {
8029
- return { create: async (obligations$1) => {
8030
- if (obligations$1.length === 0) return;
8031
- const obligationsById = /* @__PURE__ */ new Map();
8032
- for (const obligation of obligations$1) {
8033
- const id$1 = id(obligation).toLowerCase();
8034
- if (!obligationsById.get(id$1)) obligationsById.set(id$1, obligation);
8035
- }
8036
- try {
8037
- await db.transaction(async (dbTx) => {
8038
- const obligationRows = obligations$1.map((obligation) => ({
8039
- obligationId: id(obligation),
8040
- chainId: obligation.chainId,
8041
- loanToken: obligation.loanToken.toLowerCase(),
8042
- maturity: obligation.maturity
8043
- }));
8044
- for (const batch of batch$1(obligationRows, DEFAULT_BATCH_SIZE)) await dbTx.insert(obligations).values(batch).onConflictDoNothing();
8045
- const collateralRows = obligations$1.flatMap((obligation) => {
8046
- return obligation.collaterals.map((collateral) => ({
8253
+ function create$10(db) {
8254
+ return {
8255
+ get: async (parameters) => {
8256
+ const chainIds = parameters?.chainId;
8257
+ const now$3 = now();
8258
+ return (await db.select({
8259
+ chainId: obligations.chainId,
8260
+ loanToken: obligations.loanToken,
8261
+ collaterals: sql`ARRAY_AGG(jsonb_build_object('asset', ${obligationCollateralsV2.asset}, 'oracle', ${oracles.address}, 'lltv', ${obligationCollateralsV2.lltv}))`.as("collaterals"),
8262
+ maturity: obligations.maturity
8263
+ }).from(obligations).innerJoin(obligationCollateralsV2, eq(obligations.obligationId, obligationCollateralsV2.obligationId)).innerJoin(oracles, sql`${obligationCollateralsV2.oracleChainId} = ${oracles.chainId}
8264
+ AND ${obligationCollateralsV2.oracleAddress} = ${oracles.address}`).groupBy(obligations.obligationId).where(and(chainIds !== void 0 && chainIds.length > 0 ? inArray(obligations.chainId, chainIds) : void 0, gte(obligations.maturity, now$3))).orderBy(asc(obligations.obligationId))).map((row) => from$13({
8265
+ chainId: row.chainId,
8266
+ loanToken: row.loanToken,
8267
+ collaterals: row.collaterals.sort((left, right) => left.asset.localeCompare(right.asset)).map((collateral) => from$14({
8268
+ asset: collateral.asset,
8269
+ oracle: collateral.oracle,
8270
+ lltv: from$15(BigInt(collateral.lltv))
8271
+ })),
8272
+ maturity: row.maturity
8273
+ }));
8274
+ },
8275
+ create: async (obligations$1) => {
8276
+ if (obligations$1.length === 0) return;
8277
+ const obligationsById = /* @__PURE__ */ new Map();
8278
+ for (const obligation of obligations$1) {
8279
+ const id$1 = id(obligation).toLowerCase();
8280
+ if (!obligationsById.get(id$1)) obligationsById.set(id$1, obligation);
8281
+ }
8282
+ try {
8283
+ await db.transaction(async (dbTx) => {
8284
+ const obligationRows = obligations$1.map((obligation) => ({
8047
8285
  obligationId: id(obligation),
8048
- asset: collateral.asset.toLowerCase(),
8049
- oracleChainId: obligation.chainId,
8050
- oracleAddress: collateral.oracle.toLowerCase(),
8051
- lltv: collateral.lltv
8286
+ chainId: obligation.chainId,
8287
+ loanToken: obligation.loanToken.toLowerCase(),
8288
+ maturity: obligation.maturity
8052
8289
  }));
8290
+ for (const batch of batch$1(obligationRows, DEFAULT_BATCH_SIZE)) await dbTx.insert(obligations).values(batch).onConflictDoNothing();
8291
+ const collateralRows = obligations$1.flatMap((obligation) => {
8292
+ return obligation.collaterals.map((collateral) => ({
8293
+ obligationId: id(obligation),
8294
+ asset: collateral.asset.toLowerCase(),
8295
+ oracleChainId: obligation.chainId,
8296
+ oracleAddress: collateral.oracle.toLowerCase(),
8297
+ lltv: collateral.lltv
8298
+ }));
8299
+ });
8300
+ for (const batch of batch$1(collateralRows, DEFAULT_BATCH_SIZE)) await dbTx.insert(obligationCollateralsV2).values(batch).onConflictDoNothing();
8053
8301
  });
8054
- for (const batch of batch$1(collateralRows, DEFAULT_BATCH_SIZE)) await dbTx.insert(obligationCollateralsV2).values(batch).onConflictDoNothing();
8055
- });
8056
- } catch (err) {
8057
- const error = err instanceof Error ? err : new Error(String(err));
8058
- throw new Error("Obligations.create failed. Ensure oracles exist before inserting obligations.", { cause: error });
8302
+ } catch (err) {
8303
+ const error = err instanceof Error ? err : new Error(String(err));
8304
+ throw new Error("Obligations.create failed. Ensure oracles exist before inserting obligations.", { cause: error });
8305
+ }
8059
8306
  }
8060
- } };
8307
+ };
8061
8308
  }
8062
8309
 
8063
8310
  //#endregion
8064
8311
  //#region src/database/domains/Offers.ts
8065
- const DEFAULT_LIMIT$2 = 100;
8066
- function create$8(config) {
8312
+ const DEFAULT_LIMIT$3 = 100;
8313
+ function create$9(config) {
8067
8314
  const { db } = config;
8068
8315
  return {
8069
8316
  create: async (batches) => {
@@ -8075,6 +8322,7 @@ function create$8(config) {
8075
8322
  groupMaker: offer.maker.toLowerCase(),
8076
8323
  callbackAddress: offer.callback.address.toLowerCase(),
8077
8324
  callbackData: offer.callback.data,
8325
+ receiverIfMakerIsSeller: offer.receiverIfMakerIsSeller.toLowerCase(),
8078
8326
  blockNumber
8079
8327
  })));
8080
8328
  if (offersRows.length === 0) return [];
@@ -8103,7 +8351,7 @@ function create$8(config) {
8103
8351
  }
8104
8352
  },
8105
8353
  get: async (parameters) => {
8106
- const limit = parameters?.limit ?? DEFAULT_LIMIT$2;
8354
+ const limit = parameters?.limit ?? DEFAULT_LIMIT$3;
8107
8355
  const cursor = parameters?.cursor;
8108
8356
  const maker = parameters?.maker;
8109
8357
  if (cursor !== null && cursor !== void 0) {
@@ -8126,7 +8374,7 @@ function create$8(config) {
8126
8374
  assets: offers.assets,
8127
8375
  obligationUnits: offers.obligationUnits,
8128
8376
  obligationShares: offers.obligationShares,
8129
- price: offers.price,
8377
+ tick: offers.tick,
8130
8378
  maturity: offers.maturity,
8131
8379
  expiry: offers.expiry,
8132
8380
  start: offers.start,
@@ -8137,16 +8385,18 @@ function create$8(config) {
8137
8385
  loanToken: obligations.loanToken,
8138
8386
  callbackAddress: offers.callbackAddress,
8139
8387
  callbackData: offers.callbackData,
8388
+ receiverIfMakerIsSeller: offers.receiverIfMakerIsSeller,
8140
8389
  collaterals: collateralsLateral.collaterals,
8141
8390
  blockNumber: offers.blockNumber
8142
8391
  }).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) => {
8392
+ const receiverIfMakerIsSeller = (row.receiverIfMakerIsSeller ?? row.maker).toLowerCase();
8143
8393
  return {
8144
8394
  hash: row.hash,
8145
8395
  maker: row.maker,
8146
8396
  assets: BigInt(row.assets),
8147
8397
  obligationUnits: BigInt(row.obligationUnits),
8148
8398
  obligationShares: BigInt(row.obligationShares),
8149
- price: BigInt(row.price),
8399
+ tick: row.tick,
8150
8400
  maturity: from$16(row.maturity),
8151
8401
  expiry: row.expiry,
8152
8402
  start: row.start,
@@ -8164,6 +8414,7 @@ function create$8(config) {
8164
8414
  address: row.callbackAddress,
8165
8415
  data: row.callbackData
8166
8416
  },
8417
+ receiverIfMakerIsSeller,
8167
8418
  consumed: 0n,
8168
8419
  available: 0n,
8169
8420
  takeable: 0n,
@@ -8188,64 +8439,30 @@ function create$8(config) {
8188
8439
  }
8189
8440
  throw new Error("Invalid parameters");
8190
8441
  },
8191
- getObligations: async (parameters) => {
8192
- const { ids, chainId: chainIds, loanToken: loanTokens, collateralToken: collateralTokens, maturity: maturities, cursor, limit = DEFAULT_LIMIT$2 } = parameters ?? {};
8193
- const now$1 = now();
8194
- const loanTokenFilter = loanTokens !== void 0 && loanTokens.length > 0 ? sql`(${sql.join(loanTokens.map((token) => sql`LOWER(${obligations.loanToken}) = ${token.toLowerCase()}`), sql` OR `)})` : void 0;
8195
- const collateralFilter = collateralTokens !== void 0 && collateralTokens.length > 0 ? sql`EXISTS (
8196
- SELECT 1 FROM ${obligationCollateralsV2} oc
8197
- WHERE oc.obligation_id = ${obligations.obligationId}
8198
- AND (${sql.join(collateralTokens.map((token) => sql`LOWER(oc.asset) = ${token.toLowerCase()}`), sql` OR `)})
8199
- )` : void 0;
8200
- const result = await db.select({
8201
- obligationId: obligations.obligationId,
8202
- chainId: obligations.chainId,
8203
- loanToken: obligations.loanToken,
8204
- collaterals: sql`ARRAY_AGG(jsonb_build_object('asset', ${obligationCollateralsV2.asset}, 'oracle', ${oracles.address}, 'lltv', ${obligationCollateralsV2.lltv}))`.as("collaterals"),
8205
- maturity: obligations.maturity
8206
- }).from(obligations).innerJoin(obligationCollateralsV2, eq(obligations.obligationId, obligationCollateralsV2.obligationId)).innerJoin(oracles, sql`${obligationCollateralsV2.oracleChainId} = ${oracles.chainId}
8207
- AND ${obligationCollateralsV2.oracleAddress} = ${oracles.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);
8208
- const items = [];
8209
- for (const row of result) items.push(from$13({
8210
- chainId: row.chainId,
8211
- loanToken: row.loanToken,
8212
- collaterals: row.collaterals.sort((a, b) => a.asset.localeCompare(b.asset)).map((c) => from$14({
8213
- asset: c.asset,
8214
- oracle: c.oracle,
8215
- lltv: from$15(BigInt(c.lltv))
8216
- })),
8217
- maturity: row.maturity
8218
- }));
8219
- const returnedItems = Array.from(items.values());
8220
- return {
8221
- obligations: returnedItems,
8222
- nextCursor: returnedItems.length === limit && returnedItems.length > 0 ? result[result.length - 1].obligationId : null
8223
- };
8224
- },
8225
8442
  getQuotes: async (parameters) => {
8226
8443
  const { obligationIds } = parameters;
8227
8444
  if (obligationIds.length === 0) return [];
8228
8445
  const now$2 = now();
8229
8446
  const query = ({ side }) => db.selectDistinctOn([offers.obligationId], {
8230
8447
  obligationId: offers.obligationId,
8231
- price: offers.price
8232
- }).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`);
8448
+ tick: offers.tick
8449
+ }).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`);
8233
8450
  const [bestBuys, bestSells] = await Promise.all([query({ side: "buy" }), query({ side: "sell" })]);
8234
8451
  const quotes = /* @__PURE__ */ new Map();
8235
8452
  for (const row of bestSells) quotes.set(row.obligationId, {
8236
- ask: { price: BigInt(row.price) },
8237
- bid: { price: 0n }
8453
+ ask: { tick: row.tick },
8454
+ bid: { tick: null }
8238
8455
  });
8239
8456
  for (const row of bestBuys) {
8240
8457
  const quote = quotes.get(row.obligationId);
8241
8458
  if (!quote) {
8242
8459
  quotes.set(row.obligationId, {
8243
- ask: { price: 0n },
8244
- bid: { price: BigInt(row.price) }
8460
+ ask: { tick: null },
8461
+ bid: { tick: row.tick }
8245
8462
  });
8246
8463
  continue;
8247
8464
  }
8248
- quote.bid = { price: BigInt(row.price) };
8465
+ quote.bid = { tick: row.tick };
8249
8466
  }
8250
8467
  return Array.from(quotes.entries()).map(([id, quote]) => {
8251
8468
  return from$9({
@@ -8262,19 +8479,21 @@ function create$8(config) {
8262
8479
 
8263
8480
  //#endregion
8264
8481
  //#region src/database/domains/Offsets.ts
8265
- function create$7(db) {
8482
+ function create$8(db) {
8266
8483
  return { get: async (parameters) => {
8267
- const { chainId, user, contract, group } = parameters ?? {};
8484
+ const { chainId, user, contract, group, obligationId } = parameters ?? {};
8268
8485
  const conditions = [];
8269
8486
  if (chainId !== void 0) conditions.push(eq(offsets.chainId, chainId));
8270
8487
  if (user !== void 0) conditions.push(eq(offsets.user, user.toLowerCase()));
8271
8488
  if (contract !== void 0) conditions.push(eq(offsets.contract, contract.toLowerCase()));
8272
8489
  if (group !== void 0) conditions.push(eq(offsets.group, group));
8490
+ if (obligationId !== void 0) conditions.push(eq(offsets.obligationId, obligationId));
8273
8491
  return (await db.select().from(offsets).where(conditions.length > 0 ? and(...conditions) : void 0)).map((row) => ({
8274
8492
  chainId: row.chainId,
8275
8493
  user: row.user,
8276
8494
  contract: row.contract,
8277
8495
  group: row.group,
8496
+ obligationId: row.obligationId,
8278
8497
  value: BigInt(row.value)
8279
8498
  }));
8280
8499
  } };
@@ -8282,7 +8501,7 @@ function create$7(db) {
8282
8501
 
8283
8502
  //#endregion
8284
8503
  //#region src/database/domains/Oracles.ts
8285
- function create$6(db) {
8504
+ function create$7(db) {
8286
8505
  return {
8287
8506
  get: async ({ chainId }) => {
8288
8507
  return (await db.select({
@@ -8324,8 +8543,8 @@ function create$6(db) {
8324
8543
 
8325
8544
  //#endregion
8326
8545
  //#region src/database/domains/Positions.ts
8327
- const DEFAULT_LIMIT$1 = 100;
8328
- const create$5 = (db) => {
8546
+ const DEFAULT_LIMIT$2 = 100;
8547
+ const create$6 = (db) => {
8329
8548
  return {
8330
8549
  upsert: async (positions$1) => {
8331
8550
  const positionsMap = /* @__PURE__ */ new Map();
@@ -8380,7 +8599,7 @@ const create$5 = (db) => {
8380
8599
  return totalUpdated;
8381
8600
  },
8382
8601
  get: async (parameters) => {
8383
- const { limit = DEFAULT_LIMIT$1, cursor: encodedCursor, chainId, type, filled } = parameters ?? {};
8602
+ const { limit = DEFAULT_LIMIT$2, cursor: encodedCursor, chainId, type, filled } = parameters ?? {};
8384
8603
  let cursor = null;
8385
8604
  if (encodedCursor !== null && encodedCursor !== void 0) {
8386
8605
  const parsed = JSON.parse(Buffer.from(encodedCursor, "base64url").toString("utf8"));
@@ -8417,14 +8636,15 @@ const create$5 = (db) => {
8417
8636
  };
8418
8637
  },
8419
8638
  getByUser: async (parameters) => {
8420
- const { user, limit = DEFAULT_LIMIT$1, cursor: encodedCursor } = parameters;
8639
+ const { user, limit = DEFAULT_LIMIT$2, cursor: encodedCursor } = parameters;
8421
8640
  let cursor = null;
8422
8641
  if (encodedCursor !== null && encodedCursor !== void 0) {
8423
8642
  const parsed = JSON.parse(Buffer.from(encodedCursor, "base64url").toString("utf8"));
8424
8643
  if (!parsed.chainId || !parsed.contract) throw new Error("Invalid cursor format");
8425
8644
  cursor = {
8426
8645
  chainId: parsed.chainId,
8427
- contract: parsed.contract
8646
+ contract: parsed.contract,
8647
+ obligationId: parsed.obligationId ?? null
8428
8648
  };
8429
8649
  }
8430
8650
  const raw = await db.execute(sql`
@@ -8433,16 +8653,18 @@ const create$5 = (db) => {
8433
8653
  chain_id,
8434
8654
  "user",
8435
8655
  contract,
8656
+ obligation_id,
8436
8657
  SUM(value::numeric) AS total_offset
8437
8658
  FROM ${offsets}
8438
8659
  WHERE LOWER("user") = LOWER(${user})
8439
- GROUP BY chain_id, "user", contract
8660
+ GROUP BY chain_id, "user", contract, obligation_id
8440
8661
  ),
8441
8662
  position_consumed AS (
8442
8663
  SELECT
8443
8664
  l.chain_id,
8444
8665
  l.contract,
8445
8666
  l."user",
8667
+ l.obligation_id,
8446
8668
  SUM(
8447
8669
  CASE
8448
8670
  WHEN offer_agg.assets > 0
@@ -8468,50 +8690,64 @@ const create$5 = (db) => {
8468
8690
  AND LOWER(offer_agg.group_maker) = LOWER(g.maker)
8469
8691
  AND offer_agg."group_group" = g."group"
8470
8692
  WHERE LOWER(l."user") = LOWER(${user})
8471
- GROUP BY l.chain_id, l.contract, l."user"
8693
+ GROUP BY l.chain_id, l.contract, l."user", l.obligation_id
8472
8694
  ),
8473
8695
  position_max_lot AS (
8474
8696
  SELECT
8475
8697
  chain_id,
8476
8698
  contract,
8477
8699
  "user",
8700
+ obligation_id,
8478
8701
  MAX(upper::numeric) AS max_upper
8479
8702
  FROM ${lots}
8480
8703
  WHERE LOWER("user") = LOWER(${user})
8481
- GROUP BY chain_id, contract, "user"
8704
+ GROUP BY chain_id, contract, "user", obligation_id
8705
+ ),
8706
+ per_obligation AS (
8707
+ SELECT
8708
+ pml.chain_id,
8709
+ pml.contract,
8710
+ pml."user",
8711
+ pml.obligation_id,
8712
+ GREATEST(0,
8713
+ COALESCE(pml.max_upper, 0)
8714
+ - COALESCE(po.total_offset, 0)
8715
+ - COALESCE(pc.consumed, 0)
8716
+ )::text AS reserved_balance
8717
+ FROM position_max_lot pml
8718
+ LEFT JOIN position_offsets po
8719
+ ON po.chain_id = pml.chain_id
8720
+ AND LOWER(po.contract) = LOWER(pml.contract)
8721
+ AND LOWER(po."user") = LOWER(pml."user")
8722
+ AND po.obligation_id = pml.obligation_id
8723
+ LEFT JOIN position_consumed pc
8724
+ ON pc.chain_id = pml.chain_id
8725
+ AND LOWER(pc.contract) = LOWER(pml.contract)
8726
+ AND LOWER(pc."user") = LOWER(pml."user")
8727
+ AND pc.obligation_id = pml.obligation_id
8482
8728
  )
8483
8729
  SELECT
8484
8730
  p.chain_id,
8485
8731
  p.contract,
8486
8732
  p."user",
8487
8733
  p.block_number,
8488
- GREATEST(0,
8489
- COALESCE(pml.max_upper, 0)
8490
- - COALESCE(po.total_offset, 0)
8491
- - COALESCE(pc.consumed, 0)
8492
- )::text AS reserved_balance
8734
+ po.obligation_id,
8735
+ COALESCE(po.reserved_balance, '0') AS reserved_balance
8493
8736
  FROM ${positions} p
8494
- LEFT JOIN position_offsets po
8737
+ LEFT JOIN per_obligation po
8495
8738
  ON po.chain_id = p.chain_id
8496
8739
  AND LOWER(po.contract) = LOWER(p.contract)
8497
8740
  AND LOWER(po."user") = LOWER(p."user")
8498
- LEFT JOIN position_consumed pc
8499
- ON pc.chain_id = p.chain_id
8500
- AND LOWER(pc.contract) = LOWER(p.contract)
8501
- AND LOWER(pc."user") = LOWER(p."user")
8502
- LEFT JOIN position_max_lot pml
8503
- ON pml.chain_id = p.chain_id
8504
- AND LOWER(pml.contract) = LOWER(p.contract)
8505
- AND LOWER(pml."user") = LOWER(p."user")
8506
8741
  WHERE LOWER(p."user") = LOWER(${user})
8507
8742
  AND p."user" != ${zeroAddress}
8508
- ${cursor !== null ? sql`AND (p.chain_id, p.contract) > (${cursor.chainId}, ${cursor.contract})` : sql``}
8509
- ORDER BY p.chain_id ASC, p.contract ASC
8743
+ ${cursor !== null ? sql`AND (p.chain_id, p.contract, COALESCE(po.obligation_id, '')) > (${cursor.chainId}, ${cursor.contract}, ${cursor.obligationId ?? ""})` : sql``}
8744
+ ORDER BY p.chain_id ASC, p.contract ASC, po.obligation_id ASC NULLS FIRST
8510
8745
  LIMIT ${limit}
8511
8746
  `);
8512
8747
  const nextCursor = raw.rows.length === limit ? Buffer.from(JSON.stringify({
8513
8748
  chainId: raw.rows[raw.rows.length - 1].chain_id.toString(),
8514
- contract: raw.rows[raw.rows.length - 1].contract
8749
+ contract: raw.rows[raw.rows.length - 1].contract,
8750
+ obligationId: raw.rows[raw.rows.length - 1].obligation_id
8515
8751
  })).toString("base64url") : null;
8516
8752
  return {
8517
8753
  positions: raw.rows.map((row) => ({
@@ -8519,6 +8755,7 @@ const create$5 = (db) => {
8519
8755
  contract: row.contract,
8520
8756
  user: row.user,
8521
8757
  blockNumber: row.block_number,
8758
+ obligationId: row.obligation_id,
8522
8759
  reserved: BigInt(row.reserved_balance.split(".")[0] ?? "0")
8523
8760
  })),
8524
8761
  nextCursor
@@ -8549,7 +8786,7 @@ const create$5 = (db) => {
8549
8786
 
8550
8787
  //#endregion
8551
8788
  //#region src/database/domains/Transfers.ts
8552
- const create$4 = (db) => ({ create: async (transfers$1) => {
8789
+ const create$5 = (db) => ({ create: async (transfers$1) => {
8553
8790
  if (transfers$1.length === 0) return 0;
8554
8791
  return await db.transaction(async (dbTx) => {
8555
8792
  let totalInserted = 0;
@@ -8654,7 +8891,7 @@ const create$4 = (db) => ({ create: async (transfers$1) => {
8654
8891
  * @param config - Configuration with database instance
8655
8892
  * @returns TreesDomain instance
8656
8893
  */
8657
- function create$3(config) {
8894
+ function create$4(config) {
8658
8895
  const db = config.db;
8659
8896
  return {
8660
8897
  create: async (trees$1) => {
@@ -8737,11 +8974,11 @@ function splitProofs(concatenated) {
8737
8974
 
8738
8975
  //#endregion
8739
8976
  //#region src/database/domains/Validations.ts
8740
- const DEFAULT_LIMIT = 100;
8741
- function create$2(db) {
8977
+ const DEFAULT_LIMIT$1 = 100;
8978
+ function create$3(db) {
8742
8979
  return {
8743
8980
  get: async (params) => {
8744
- const { status: status$2, cursor, limit = DEFAULT_LIMIT } = params ?? {};
8981
+ const { status: status$2, cursor, limit = DEFAULT_LIMIT$1 } = params ?? {};
8745
8982
  if (cursor !== null && cursor !== void 0) {
8746
8983
  if (!cursor.startsWith("0x") || cursor.length !== 66) throw new Error("Invalid cursor format");
8747
8984
  }
@@ -8795,29 +9032,260 @@ function create$2(db) {
8795
9032
  };
8796
9033
  }
8797
9034
 
9035
+ //#endregion
9036
+ //#region src/database/readers/ObligationsListing.ts
9037
+ const SORT_FIELDS = [
9038
+ "id",
9039
+ "ask",
9040
+ "bid",
9041
+ "maturity"
9042
+ ];
9043
+ const CURSOR_ID_REGEX = /^0x[a-f0-9]{64}$/i;
9044
+ const INT32_MIN = -2147483648;
9045
+ const INT32_MAX = 2147483647;
9046
+ const INT32_MAX_BIGINT = BigInt(INT32_MAX);
9047
+ const MAX_CURSOR_SORT_FIELDS = 4;
9048
+ const DEFAULT_LIMIT = 20;
9049
+ var BadRequestError = class extends Error {
9050
+ constructor(message) {
9051
+ super(message);
9052
+ this.name = "ObligationsListingBadRequestError";
9053
+ }
9054
+ };
9055
+ /**
9056
+ * Creates the obligations listing reader facade.
9057
+ * @param parameters - Reader dependencies.
9058
+ * @returns Obligations listing reader.
9059
+ */
9060
+ function create$2(parameters) {
9061
+ const { db } = parameters;
9062
+ return { list: async (queryParameters) => {
9063
+ const { ids, chainId: chainIds, loanToken: loanTokens, collateralToken: collateralTokens, maturity: maturities, sort: sortTokens, cursor: encodedCursor, limit: requestedLimit } = queryParameters ?? {};
9064
+ const limit = requestedLimit ?? DEFAULT_LIMIT;
9065
+ if (!Number.isInteger(limit) || limit <= 0) throw new BadRequestError("Limit must be a positive integer");
9066
+ const cursorPayload = encodedCursor ? decodeCursorPayload(encodedCursor) : void 0;
9067
+ const requestedSort = normalizeSort(sortTokens);
9068
+ const cursorSort = cursorPayload ? normalizeSort(cursorPayload.sort) : void 0;
9069
+ if (cursorSort !== void 0 && sortTokens !== void 0 && !hasSameSort(requestedSort, cursorSort)) throw new BadRequestError("Cursor sort does not match requested sort");
9070
+ const sort = sortTokens !== void 0 ? requestedSort : cursorSort ?? requestedSort;
9071
+ const cursorValues = cursorPayload ? cursorValuesFromPayload(cursorPayload) : void 0;
9072
+ const now$1 = now();
9073
+ const loanTokenFilter = loanTokens !== void 0 && loanTokens.length > 0 ? sql`(${sql.join(loanTokens.map((token) => sql`LOWER(${obligations.loanToken}) = ${token.toLowerCase()}`), sql` OR `)})` : void 0;
9074
+ const collateralFilter = collateralTokens !== void 0 && collateralTokens.length > 0 ? sql`EXISTS (
9075
+ SELECT 1 FROM ${obligationCollateralsV2} oc
9076
+ WHERE oc.obligation_id = ${obligations.obligationId}
9077
+ AND (${sql.join(collateralTokens.map((token) => sql`LOWER(oc.asset) = ${token.toLowerCase()}`), sql` OR `)})
9078
+ )` : void 0;
9079
+ 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$1), gte(offers.maturity, now$1), lte(offers.start, now$1), sql`(${status.code} IS NULL OR ${status.code} = ${Status.VALID})`)).orderBy(desc(offers.tick)).limit(1).as("best_ask_tick");
9080
+ 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$1), gte(offers.maturity, now$1), lte(offers.start, now$1), sql`(${status.code} IS NULL OR ${status.code} = ${Status.VALID})`)).orderBy(asc(offers.tick)).limit(1).as("best_bid_tick");
9081
+ const obligationsWithQuotes = db.select({
9082
+ obligationId: obligations.obligationId,
9083
+ chainId: obligations.chainId,
9084
+ loanToken: obligations.loanToken,
9085
+ collaterals: sql`ARRAY_AGG(jsonb_build_object('asset', ${obligationCollateralsV2.asset}, 'oracle', ${oracles.address}, 'lltv', ${obligationCollateralsV2.lltv}))`.as("collaterals"),
9086
+ maturity: obligations.maturity,
9087
+ askTick: sql`MAX(${bestAskTick.askTick})`.as("ask_tick"),
9088
+ bidTick: sql`MAX(${bestBidTick.bidTick})`.as("bid_tick"),
9089
+ ask: sql`COALESCE(MAX(${bestAskTick.askTick}) + 1, 0)`.as("ask"),
9090
+ bid: sql`COALESCE(MAX(${bestBidTick.bidTick}) + 1, 0)`.as("bid")
9091
+ }).from(obligations).innerJoin(obligationCollateralsV2, eq(obligations.obligationId, obligationCollateralsV2.obligationId)).innerJoin(oracles, sql`${obligationCollateralsV2.oracleChainId} = ${oracles.chainId}
9092
+ AND ${obligationCollateralsV2.oracleAddress} = ${oracles.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$1), collateralFilter)).as("obligations_with_quotes");
9093
+ const sortColumns = {
9094
+ id: obligationsWithQuotes.obligationId,
9095
+ ask: obligationsWithQuotes.ask,
9096
+ bid: obligationsWithQuotes.bid,
9097
+ maturity: obligationsWithQuotes.maturity
9098
+ };
9099
+ const rows = await db.select({
9100
+ obligationId: obligationsWithQuotes.obligationId,
9101
+ chainId: obligationsWithQuotes.chainId,
9102
+ loanToken: obligationsWithQuotes.loanToken,
9103
+ collaterals: obligationsWithQuotes.collaterals,
9104
+ maturity: obligationsWithQuotes.maturity,
9105
+ askTick: obligationsWithQuotes.askTick,
9106
+ bidTick: obligationsWithQuotes.bidTick,
9107
+ ask: obligationsWithQuotes.ask,
9108
+ bid: obligationsWithQuotes.bid
9109
+ }).from(obligationsWithQuotes).where(buildCursorFilter(sortColumns, sort, cursorValues)).orderBy(...buildOrderBy(sortColumns, sort)).limit(limit + 1);
9110
+ const hasMore = rows.length > limit;
9111
+ const listedRows = (hasMore ? rows.slice(0, limit) : rows).map((row) => {
9112
+ return {
9113
+ obligation: from$13({
9114
+ chainId: row.chainId,
9115
+ loanToken: row.loanToken,
9116
+ collaterals: row.collaterals.sort((left, right) => left.asset.localeCompare(right.asset)).map((collateral) => from$14({
9117
+ asset: collateral.asset,
9118
+ oracle: collateral.oracle,
9119
+ lltv: from$15(BigInt(collateral.lltv))
9120
+ })),
9121
+ maturity: row.maturity
9122
+ }),
9123
+ quote: from$9({
9124
+ obligationId: row.obligationId,
9125
+ ask: { tick: row.askTick },
9126
+ bid: { tick: row.bidTick }
9127
+ }),
9128
+ cursorValues: {
9129
+ id: row.obligationId,
9130
+ ask: toBigInt(row.ask),
9131
+ bid: toBigInt(row.bid),
9132
+ maturity: row.maturity
9133
+ }
9134
+ };
9135
+ });
9136
+ const nextCursor = hasMore && listedRows.length > 0 ? encodeCursorPayload({
9137
+ sort: sortToTokens(sort),
9138
+ id: listedRows[listedRows.length - 1].cursorValues.id,
9139
+ ask: listedRows[listedRows.length - 1].cursorValues.ask.toString(),
9140
+ bid: listedRows[listedRows.length - 1].cursorValues.bid.toString(),
9141
+ maturity: listedRows[listedRows.length - 1].cursorValues.maturity
9142
+ }) : null;
9143
+ return {
9144
+ obligations: listedRows.map((row) => ({
9145
+ obligation: row.obligation,
9146
+ quote: row.quote
9147
+ })),
9148
+ nextCursor
9149
+ };
9150
+ } };
9151
+ }
9152
+ function isSortField(value) {
9153
+ return SORT_FIELDS.includes(value);
9154
+ }
9155
+ function parseSortToken(token) {
9156
+ const direction = token.startsWith("-") ? "desc" : "asc";
9157
+ const rawField = token.startsWith("-") ? token.slice(1) : token;
9158
+ if (!isSortField(rawField)) throw new BadRequestError(`Invalid sort field: ${rawField}`);
9159
+ return {
9160
+ field: rawField,
9161
+ direction
9162
+ };
9163
+ }
9164
+ function normalizeSort(sortTokens) {
9165
+ const parsed = sortTokens?.length ? sortTokens.map(parseSortToken) : [{
9166
+ field: "id",
9167
+ direction: "asc"
9168
+ }];
9169
+ return parsed.some((entry) => entry.field === "id") ? parsed : [...parsed, {
9170
+ field: "id",
9171
+ direction: "asc"
9172
+ }];
9173
+ }
9174
+ function sortToTokens(sortEntries) {
9175
+ return sortEntries.map((entry) => entry.direction === "desc" ? `-${entry.field}` : entry.field);
9176
+ }
9177
+ function hasSameSort(left, right) {
9178
+ if (left.length !== right.length) return false;
9179
+ return left.every((sortEntry, index) => sortEntry.field === right[index]?.field && sortEntry.direction === right[index]?.direction);
9180
+ }
9181
+ function decodeCursorPayload(cursor) {
9182
+ let decoded;
9183
+ try {
9184
+ decoded = JSON.parse(Buffer.from(cursor, "base64url").toString("utf8"));
9185
+ } catch {
9186
+ throw new BadRequestError("Invalid cursor format");
9187
+ }
9188
+ if (decoded === null || typeof decoded !== "object") throw new BadRequestError("Invalid cursor payload");
9189
+ const payload = decoded;
9190
+ const sortTokens = parseCursorSortTokens(payload.sort);
9191
+ if (typeof payload.id !== "string" || !CURSOR_ID_REGEX.test(payload.id)) throw new BadRequestError("Invalid cursor obligation id");
9192
+ const ask = parseCursorNonNegativeInt32(payload.ask, "ask");
9193
+ const bid = parseCursorNonNegativeInt32(payload.bid, "bid");
9194
+ if (!isInt32(payload.maturity)) throw new BadRequestError("Invalid cursor maturity value");
9195
+ return {
9196
+ sort: sortTokens,
9197
+ id: payload.id,
9198
+ ask,
9199
+ bid,
9200
+ maturity: payload.maturity
9201
+ };
9202
+ }
9203
+ function parseCursorSortTokens(value) {
9204
+ if (!Array.isArray(value) || value.length === 0 || value.length > MAX_CURSOR_SORT_FIELDS) throw new BadRequestError("Invalid cursor sort");
9205
+ const sortEntries = value.map((token) => {
9206
+ if (typeof token !== "string") throw new BadRequestError("Invalid cursor sort");
9207
+ try {
9208
+ return parseSortToken(token);
9209
+ } catch {
9210
+ throw new BadRequestError("Invalid cursor sort");
9211
+ }
9212
+ });
9213
+ if (new Set(sortEntries.map((entry) => entry.field)).size !== sortEntries.length) throw new BadRequestError("Invalid cursor sort");
9214
+ return sortToTokens(sortEntries);
9215
+ }
9216
+ function parseCursorNonNegativeInt32(value, field) {
9217
+ if (typeof value !== "string" || !/^\d+$/.test(value)) throw new BadRequestError(`Invalid cursor ${field} value`);
9218
+ if (BigInt(value) > INT32_MAX_BIGINT) throw new BadRequestError(`Invalid cursor ${field} value`);
9219
+ return value;
9220
+ }
9221
+ function isInt32(value) {
9222
+ return typeof value === "number" && Number.isSafeInteger(value) && value >= INT32_MIN && value <= INT32_MAX;
9223
+ }
9224
+ function encodeCursorPayload(payload) {
9225
+ return Buffer.from(JSON.stringify(payload), "utf8").toString("base64url");
9226
+ }
9227
+ function cursorValuesFromPayload(payload) {
9228
+ return {
9229
+ id: payload.id,
9230
+ ask: BigInt(payload.ask),
9231
+ bid: BigInt(payload.bid),
9232
+ maturity: payload.maturity
9233
+ };
9234
+ }
9235
+ function cursorComparisonValue(cursorValues, field) {
9236
+ switch (field) {
9237
+ case "id": return cursorValues.id;
9238
+ case "maturity": return cursorValues.maturity;
9239
+ case "ask": return cursorValues.ask.toString();
9240
+ case "bid": return cursorValues.bid.toString();
9241
+ }
9242
+ }
9243
+ function buildCursorFilter(columns, sortEntries, cursorValues) {
9244
+ if (cursorValues === void 0) return void 0;
9245
+ const comparisons = sortEntries.map((sortEntry, index) => {
9246
+ const equals = sortEntries.slice(0, index).map((previous) => {
9247
+ return sql`${columns[previous.field]} = ${cursorComparisonValue(cursorValues, previous.field)}`;
9248
+ });
9249
+ const comparison = sortEntry.direction === "asc" ? sql`${columns[sortEntry.field]} > ${cursorComparisonValue(cursorValues, sortEntry.field)}` : sql`${columns[sortEntry.field]} < ${cursorComparisonValue(cursorValues, sortEntry.field)}`;
9250
+ return equals.length > 0 ? sql`(${sql.join([...equals, comparison], sql` AND `)})` : sql`(${comparison})`;
9251
+ });
9252
+ return comparisons.length > 0 ? sql`(${sql.join(comparisons, sql` OR `)})` : void 0;
9253
+ }
9254
+ function buildOrderBy(columns, sortEntries) {
9255
+ return sortEntries.map((sortEntry) => sortEntry.direction === "asc" ? asc(columns[sortEntry.field]) : desc(columns[sortEntry.field]));
9256
+ }
9257
+ function toBigInt(value) {
9258
+ if (typeof value === "bigint") return value;
9259
+ if (typeof value === "number") return BigInt(value);
9260
+ return BigInt(value.split(".")[0] ?? "0");
9261
+ }
9262
+
8798
9263
  //#endregion
8799
9264
  //#region src/database/Database.ts
8800
9265
  function createDomains(core, chainRegistry) {
8801
9266
  return {
8802
- book: create$14({ db: core }),
8803
- blocks: create$15({
9267
+ book: create$15({ db: core }),
9268
+ blocks: create$16({
8804
9269
  db: core,
8805
9270
  chainRegistry
8806
9271
  }),
8807
- callbacks: create$13(core),
8808
- offers: create$8({ db: core }),
8809
- consumed: create$12(core),
8810
- groups: create$11(core),
8811
- lots: create$10(core),
8812
- obligations: create$9(core),
8813
- offsets: create$7(core),
8814
- oracles: create$6(core),
8815
- trees: create$3({ db: core }),
8816
- validations: create$2(core),
8817
- positions: create$5(core),
8818
- transfers: create$4(core)
9272
+ callbacks: create$14(core),
9273
+ offers: create$9({ db: core }),
9274
+ consumed: create$13(core),
9275
+ groups: create$12(core),
9276
+ lots: create$11(core),
9277
+ obligations: create$10(core),
9278
+ offsets: create$8(core),
9279
+ oracles: create$7(core),
9280
+ trees: create$4({ db: core }),
9281
+ validations: create$3(core),
9282
+ positions: create$6(core),
9283
+ transfers: create$5(core)
8819
9284
  };
8820
9285
  }
9286
+ function createReaders(core) {
9287
+ return { obligations: create$2({ db: core }) };
9288
+ }
8821
9289
  const AUGMENT_CACHE = /* @__PURE__ */ new WeakMap();
8822
9290
  function augmentWithDomains(base, chainRegistry) {
8823
9291
  const cached = AUGMENT_CACHE.get(base)?.get(chainRegistry);
@@ -8829,6 +9297,7 @@ function augmentWithDomains(base, chainRegistry) {
8829
9297
  });
8830
9298
  };
8831
9299
  const dms = createDomains(wrapped, chainRegistry);
9300
+ const readers = createReaders(wrapped);
8832
9301
  Object.defineProperties(wrapped, {
8833
9302
  book: {
8834
9303
  value: dms.book,
@@ -8885,6 +9354,10 @@ function augmentWithDomains(base, chainRegistry) {
8885
9354
  transfers: {
8886
9355
  value: dms.transfers,
8887
9356
  enumerable: true
9357
+ },
9358
+ readers: {
9359
+ value: readers,
9360
+ enumerable: true
8888
9361
  }
8889
9362
  });
8890
9363
  const chainRegistryMap = AUGMENT_CACHE.get(base);
@@ -8893,6 +9366,7 @@ function augmentWithDomains(base, chainRegistry) {
8893
9366
  return wrapped;
8894
9367
  }
8895
9368
  const InMemoryDbMap = /* @__PURE__ */ new Map();
9369
+ const LEGACY_SCHEMA_START_MINOR = 7;
8896
9370
  /**
8897
9371
  * Connect to the database.
8898
9372
  * @notice If no connection string is provided, an in-process PGLite database is created.
@@ -8947,9 +9421,26 @@ function applyMigrations(kind, driver) {
8947
9421
  async function preMigrate(driver) {
8948
9422
  const tracer = getTracer("db.preMigrate");
8949
9423
  await startActiveSpan(tracer, "db.preMigrate", async () => {
8950
- await driver.execute(`create schema if not exists "${VERSION}"`);
9424
+ const schemaNames = getSchemaNamesForMigration(VERSION);
9425
+ for (const schemaName of schemaNames) await driver.execute(`create schema if not exists "${schemaName}"`);
8951
9426
  });
8952
9427
  }
9428
+ /**
9429
+ * Build the list of router schemas that should exist before running migrations.
9430
+ * @param version - Current schema version (e.g. `router_v1.8`).
9431
+ * @returns Ordered schema names from `router_v1.7` to current, or just current if parsing fails.
9432
+ */
9433
+ function getSchemaNamesForMigration(version) {
9434
+ const parsed = /^router_v(?<major>\d+)\.(?<minor>\d+)$/.exec(version);
9435
+ if (!parsed?.groups?.major || !parsed.groups.minor) return [version];
9436
+ const major = Number.parseInt(parsed.groups.major, 10);
9437
+ const currentMinor = Number.parseInt(parsed.groups.minor, 10);
9438
+ if (!Number.isInteger(major) || !Number.isInteger(currentMinor)) return [version];
9439
+ if (currentMinor < LEGACY_SCHEMA_START_MINOR) return [version];
9440
+ const schemaNames = [];
9441
+ for (let minor = LEGACY_SCHEMA_START_MINOR; minor <= currentMinor; minor += 1) schemaNames.push(`router_v${major}.${minor}`);
9442
+ return schemaNames;
9443
+ }
8953
9444
  async function postMigrate(driver) {
8954
9445
  const tracer = getTracer("db.postMigrate");
8955
9446
  await startActiveSpan(tracer, "db.postMigrate", async () => {
@@ -9219,15 +9710,16 @@ async function postMigrate(driver) {
9219
9710
  RETURNS trigger
9220
9711
  LANGUAGE plpgsql AS $$
9221
9712
  BEGIN
9222
- INSERT INTO "${VERSION}"."offsets" (chain_id, "user", contract, "group", value)
9713
+ INSERT INTO "${VERSION}"."offsets" (chain_id, "user", contract, "group", obligation_id, value)
9223
9714
  VALUES (
9224
9715
  OLD.chain_id,
9225
9716
  OLD."user",
9226
9717
  OLD.contract,
9227
9718
  OLD."group",
9719
+ OLD.obligation_id,
9228
9720
  OLD.upper::numeric - OLD.lower::numeric
9229
9721
  )
9230
- ON CONFLICT (chain_id, "user", contract, "group") DO NOTHING;
9722
+ ON CONFLICT (chain_id, "user", contract, "group", obligation_id) DO NOTHING;
9231
9723
  RETURN OLD;
9232
9724
  END;
9233
9725
  $$;
@@ -9527,7 +10019,7 @@ var RouterCmd = class RouterCmd extends Command {
9527
10019
  const configPath = resolveConfigPath(options.configFile);
9528
10020
  const config = configPath !== null ? loadRouterConfig(configPath) : createDefaultConfig();
9529
10021
  const logger = defaultLogger(config.logging.level, config.logging.pretty);
9530
- const chainRegistry = create$19(Object.values(config.chains).map((entry) => entry.chain));
10022
+ const chainRegistry = create$20(Object.values(config.chains).map((entry) => entry.chain));
9531
10023
  const clients = (config.indexer?.chains ?? []).map((name) => {
9532
10024
  const chainConfig = config.chains[name];
9533
10025
  if (!chainConfig) throw new Error(`Indexer chain ${name} is not defined under [chains].`);
@@ -9575,7 +10067,7 @@ const gatekeeperCmd = new RouterCmd("gatekeeper");
9575
10067
  gatekeeperCmd.description("Start Gatekeeper validation service.").action(async (opts) => {
9576
10068
  const { gatekeeper: gatekeeperConfig, chainRegistry, logger } = opts;
9577
10069
  await runWithLogger(logger, async () => {
9578
- const gatekeeperCore = create$20({ rules: morphoRules(chainRegistry.list()) });
10070
+ const gatekeeperCore = create$21({ rules: morphoRules(chainRegistry.list()) });
9579
10071
  const handle = await start$1({
9580
10072
  gatekeeper: gatekeeperCore,
9581
10073
  chainRegistry,
@@ -9633,7 +10125,7 @@ async function getBook(params, db) {
9633
10125
  side: query.side,
9634
10126
  levels_count: levels.length,
9635
10127
  has_next_cursor: nextCursor != null,
9636
- first_level_price: firstLevel?.price.toString() ?? null,
10128
+ first_level_tick: firstLevel?.tick ?? null,
9637
10129
  first_level_assets: firstLevel?.assets.toString() ?? null,
9638
10130
  first_level_count: firstLevel?.count ?? null
9639
10131
  });
@@ -9721,7 +10213,7 @@ async function getConfigContracts(query, chainRegistry) {
9721
10213
  }
9722
10214
  function parseCursor(cursor) {
9723
10215
  const [chain, address] = cursor.split(":", 2);
9724
- if (!chain || !address) throw new BadRequestError("Cursor must be in the format chain_id:0x...");
10216
+ if (!chain || !address) throw new BadRequestError$1("Cursor must be in the format chain_id:0x...");
9725
10217
  return {
9726
10218
  chain_id: Number.parseInt(chain, 10),
9727
10219
  address: address.toLowerCase()
@@ -10042,37 +10534,44 @@ async function getHealthCollectors(query, db, chainRegistry) {
10042
10534
 
10043
10535
  //#endregion
10044
10536
  //#region src/api/Controllers/getObligation.ts
10537
+ function toPayloadError$1(err) {
10538
+ if (err instanceof BadRequestError) return new BadRequestError$1(err.message);
10539
+ return err;
10540
+ }
10045
10541
  async function getObligation(params, db) {
10046
10542
  const logger = getLogger();
10047
10543
  const result = safeParse("get_obligation", params, (issue) => issue.message);
10048
10544
  if (!result.success) return failure(result.error);
10049
10545
  const query = result.data;
10050
10546
  try {
10051
- const { obligations } = await db.offers.getObligations({ ids: [query.obligation_id] });
10052
- if (obligations.length === 0) return failure(new NotFoundError("Obligation not found"));
10053
- const obligation = obligations[0];
10054
- const [quote] = await db.offers.getQuotes({ obligationIds: [id(obligation)] });
10547
+ const listing = await db.readers.obligations.list({
10548
+ ids: [query.obligation_id],
10549
+ limit: 1
10550
+ });
10551
+ if (listing.obligations.length === 0) return failure(new NotFoundError("Obligation not found"));
10552
+ const obligation = listing.obligations[0];
10055
10553
  return success({
10056
- data: from$5(obligation, quote ?? {
10057
- obligationId: id(obligation),
10058
- ask: { price: 0n },
10059
- bid: { price: 0n }
10060
- }),
10554
+ data: from$5(obligation.obligation, obligation.quote),
10061
10555
  cursor: null
10062
10556
  });
10063
10557
  } catch (err) {
10558
+ const payloadError = toPayloadError$1(err);
10064
10559
  logger.error({
10065
- err,
10560
+ err: payloadError,
10066
10561
  msg: "Error get obligation",
10067
- errorMessage: err instanceof Error ? err.message : String(err),
10068
- errorStack: err instanceof Error ? err.stack : void 0
10562
+ errorMessage: payloadError instanceof Error ? payloadError.message : String(payloadError),
10563
+ errorStack: payloadError instanceof Error ? payloadError.stack : void 0
10069
10564
  });
10070
- return failure(err);
10565
+ return failure(payloadError);
10071
10566
  }
10072
10567
  }
10073
10568
 
10074
10569
  //#endregion
10075
10570
  //#region src/api/Controllers/getObligations.ts
10571
+ function toPayloadError(err) {
10572
+ if (err instanceof BadRequestError) return new BadRequestError$1(err.message);
10573
+ return err;
10574
+ }
10076
10575
  async function getObligations(queryParameters, db) {
10077
10576
  const logger = getLogger();
10078
10577
  const result = safeParse("get_obligations", queryParameters, (issue) => issue.message);
@@ -10083,31 +10582,28 @@ async function getObligations(queryParameters, db) {
10083
10582
  const loanTokens = query.loan_tokens?.length ? query.loan_tokens : void 0;
10084
10583
  const collateralTokens = query.collateral_tokens?.length ? query.collateral_tokens : void 0;
10085
10584
  const maturities = query.maturities?.length ? query.maturities : void 0;
10086
- const { obligations, nextCursor } = await db.offers.getObligations({
10087
- cursor: query.cursor,
10088
- limit: query.limit,
10585
+ const listing = await db.readers.obligations.list({
10089
10586
  chainId: chainIds,
10090
10587
  loanToken: loanTokens,
10091
10588
  collateralToken: collateralTokens,
10092
- maturity: maturities
10589
+ maturity: maturities,
10590
+ sort: query.sort,
10591
+ cursor: query.cursor,
10592
+ limit: query.limit
10093
10593
  });
10094
- const quotes = await db.offers.getQuotes({ obligationIds: obligations.map((o) => id(o)) });
10095
10594
  return success({
10096
- data: obligations.map((o) => from$5(o, quotes.find((q) => q.obligationId === id(o)) ?? {
10097
- obligationId: id(o),
10098
- ask: { price: 0n },
10099
- bid: { price: 0n }
10100
- })),
10101
- cursor: nextCursor ?? null
10595
+ data: listing.obligations.map((item) => from$5(item.obligation, item.quote)),
10596
+ cursor: listing.nextCursor
10102
10597
  });
10103
10598
  } catch (err) {
10599
+ const payloadError = toPayloadError(err);
10104
10600
  logger.error({
10105
- err,
10601
+ err: payloadError,
10106
10602
  msg: "Error get obligations",
10107
- errorMessage: err instanceof Error ? err.message : String(err),
10108
- errorStack: err instanceof Error ? err.stack : void 0
10603
+ errorMessage: payloadError instanceof Error ? payloadError.message : String(payloadError),
10604
+ errorStack: payloadError instanceof Error ? payloadError.stack : void 0
10109
10605
  });
10110
- return failure(err);
10606
+ return failure(payloadError);
10111
10607
  }
10112
10608
  }
10113
10609
 
@@ -10120,7 +10616,7 @@ async function getObligations(queryParameters, db) {
10120
10616
  * @returns The offers with pagination cursor.
10121
10617
  */
10122
10618
  async function getOffersQuery(db, parameters) {
10123
- const limit = parameters?.limit ?? DEFAULT_LIMIT$2;
10619
+ const limit = parameters?.limit ?? DEFAULT_LIMIT$3;
10124
10620
  const cursor = parameters?.cursor;
10125
10621
  const maker = parameters?.maker;
10126
10622
  if (cursor !== null && cursor !== void 0) {
@@ -10196,7 +10692,7 @@ async function getOffersQuery(db, parameters) {
10196
10692
  obligationUnits: offers.obligationUnits,
10197
10693
  obligationShares: offers.obligationShares,
10198
10694
  consumed: groups.consumed,
10199
- price: offers.price,
10695
+ tick: offers.tick,
10200
10696
  maturity: offers.maturity,
10201
10697
  expiry: offers.expiry,
10202
10698
  start: offers.start,
@@ -10207,6 +10703,7 @@ async function getOffersQuery(db, parameters) {
10207
10703
  loanToken: obligations.loanToken,
10208
10704
  callbackAddress: offers.callbackAddress,
10209
10705
  callbackData: offers.callbackData,
10706
+ receiverIfMakerIsSeller: offers.receiverIfMakerIsSeller,
10210
10707
  collaterals: collateralsLateral.collaterals,
10211
10708
  blockNumber: offers.blockNumber,
10212
10709
  available: sql`${availableExpr}::numeric`.as("available"),
@@ -10228,13 +10725,14 @@ async function getOffersQuery(db, parameters) {
10228
10725
  )
10229
10726
  END
10230
10727
  ) > 0` : void 0)).orderBy(asc(offers.hash)).limit(limit)).map((row) => {
10728
+ const receiverIfMakerIsSeller = (row.receiverIfMakerIsSeller ?? row.maker).toLowerCase();
10231
10729
  return {
10232
10730
  hash: row.hash,
10233
10731
  maker: row.maker,
10234
10732
  assets: BigInt(row.assets),
10235
10733
  obligationUnits: BigInt(row.obligationUnits),
10236
10734
  obligationShares: BigInt(row.obligationShares),
10237
- price: BigInt(row.price),
10735
+ tick: row.tick,
10238
10736
  maturity: from$16(row.maturity),
10239
10737
  expiry: row.expiry,
10240
10738
  start: row.start,
@@ -10252,6 +10750,7 @@ async function getOffersQuery(db, parameters) {
10252
10750
  address: row.callbackAddress,
10253
10751
  data: row.callbackData
10254
10752
  },
10753
+ receiverIfMakerIsSeller,
10255
10754
  consumed: BigInt(row.consumed),
10256
10755
  available: BigInt(String(row.available ?? "0").split(".")[0] ?? "0"),
10257
10756
  takeable: BigInt(String(row.takeable ?? "0").split(".")[0] ?? "0"),
@@ -10565,7 +11064,7 @@ startCmd.description("Start Router services.").addOption(new Option("--mock <n>"
10565
11064
  let gatekeeperUrl = gatekeeperConfig?.url;
10566
11065
  let gatekeeperHandle = null;
10567
11066
  if (!gatekeeperUrl) {
10568
- const gatekeeperCore = create$20({ rules: morphoRules(chainRegistry.list()) });
11067
+ const gatekeeperCore = create$21({ rules: morphoRules(chainRegistry.list()) });
10569
11068
  gatekeeperHandle = await start$1({
10570
11069
  gatekeeper: gatekeeperCore,
10571
11070
  chainRegistry,
@@ -10891,6 +11390,7 @@ function buildOfferAssociationsFromOffers(parameters) {
10891
11390
  positionContract: loanToken,
10892
11391
  positionUser: offer.maker,
10893
11392
  group: offer.group,
11393
+ obligationId: obligationId(offer),
10894
11394
  size: offer.assets
10895
11395
  });
10896
11396
  }