@morpho-dev/router 0.8.0 → 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.
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.8.0";
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({
@@ -1707,19 +1707,19 @@ const oracles$1 = {
1707
1707
  const configs = {
1708
1708
  ethereum: {
1709
1709
  callbacks: [{ type: Type$1.BuyWithEmptyCallback }, { type: Type$1.SellWithEmptyCallback }],
1710
- maturities: [MaturityType.EndOfMonth, MaturityType.EndOfNextMonth]
1710
+ maturities: [MaturityType.EndOfWeek, MaturityType.EndOfNextWeek]
1711
1711
  },
1712
1712
  base: {
1713
1713
  callbacks: [{ type: Type$1.BuyWithEmptyCallback }, { type: Type$1.SellWithEmptyCallback }],
1714
- maturities: [MaturityType.EndOfMonth, MaturityType.EndOfNextMonth]
1714
+ maturities: [MaturityType.EndOfWeek, MaturityType.EndOfNextWeek]
1715
1715
  },
1716
1716
  "ethereum-virtual-testnet": {
1717
1717
  callbacks: [{ type: Type$1.BuyWithEmptyCallback }, { type: Type$1.SellWithEmptyCallback }],
1718
- maturities: [MaturityType.EndOfMonth, MaturityType.EndOfNextMonth]
1718
+ maturities: [MaturityType.EndOfWeek, MaturityType.EndOfNextWeek]
1719
1719
  },
1720
1720
  anvil: {
1721
1721
  callbacks: [{ type: Type$1.BuyWithEmptyCallback }, { type: Type$1.SellWithEmptyCallback }],
1722
- maturities: [MaturityType.EndOfMonth, MaturityType.EndOfNextMonth]
1722
+ maturities: [MaturityType.EndOfWeek, MaturityType.EndOfNextWeek]
1723
1723
  }
1724
1724
  };
1725
1725
 
@@ -1765,18 +1765,18 @@ const MorphoV2 = parseAbi([
1765
1765
  "function setFeeSetter(address newFeeSetter)",
1766
1766
  "function setObligationTradingFee(bytes32 id, uint256 index, uint256 newTradingFee)",
1767
1767
  "function setOwner(address newOwner)",
1768
- "function setTradingFeeRecipient(address recipient)",
1768
+ "function setTradingFeeRecipient(address feeRecipient)",
1769
1769
  "function sharesOf(bytes32 id, address user) view returns (uint256)",
1770
1770
  "function shuffleSession()",
1771
1771
  "function supplyCollateral((address loanToken, (address token, uint256 lltv, address oracle)[] collaterals, uint256 maturity) obligation, address collateral, uint256 assets, address onBehalf)",
1772
- "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)",
1773
1773
  "function totalShares(bytes32 id) view returns (uint256)",
1774
1774
  "function totalUnits(bytes32 id) view returns (uint256)",
1775
1775
  "function touchObligation((address loanToken, (address token, uint256 lltv, address oracle)[] collaterals, uint256 maturity) obligation) returns (bytes32)",
1776
1776
  "function tradingFee(bytes32 id, uint256 timeToMaturity) view returns (uint256)",
1777
1777
  "function tradingFeeRecipient() view returns (address)",
1778
- "function withdraw((address loanToken, (address token, uint256 lltv, address oracle)[] collaterals, uint256 maturity) obligation, uint256 obligationUnits, uint256 shares, address onBehalf) returns (uint256, uint256)",
1779
- "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)",
1780
1780
  "function withdrawable(bytes32 id) view returns (uint256)",
1781
1781
  "event Constructor(address indexed owner)",
1782
1782
  "event Consume(address indexed user, bytes32 indexed group, uint256 amount)",
@@ -1788,12 +1788,12 @@ const MorphoV2 = parseAbi([
1788
1788
  "event SetFeeSetter(address indexed feeSetter)",
1789
1789
  "event SetObligationTradingFee(bytes32 indexed id, uint256 indexed index, uint256 newTradingFee)",
1790
1790
  "event SetOwner(address indexed owner)",
1791
- "event SetTradingFeeRecipient(address indexed recipient)",
1791
+ "event SetTradingFeeRecipient(address indexed feeRecipient)",
1792
1792
  "event ShuffleSession(address indexed user, bytes32 session)",
1793
1793
  "event SupplyCollateral(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, bytes32 group, uint256 consumed)",
1795
- "event Withdraw(address indexed caller, bytes32 indexed id, uint256 obligationUnits, uint256 shares, address indexed onBehalf)",
1796
- "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)"
1797
1797
  ]);
1798
1798
 
1799
1799
  //#endregion
@@ -1816,7 +1816,7 @@ const Oracle = [{
1816
1816
  * @param chains - Array of chain objects to register.
1817
1817
  * @returns A registry for looking up chains by ID. {@link ChainRegistry}
1818
1818
  */
1819
- function create$19(chains) {
1819
+ function create$20(chains) {
1820
1820
  const byId = /* @__PURE__ */ new Map();
1821
1821
  for (const chain of chains) byId.set(chain.id, chain);
1822
1822
  return {
@@ -2174,7 +2174,8 @@ const OfferSchema = () => {
2174
2174
  callback: z$2.object({
2175
2175
  address: z$2.string().transform(transformAddress),
2176
2176
  data: z$2.string().transform(transformHex)
2177
- })
2177
+ }),
2178
+ receiverIfMakerIsSeller: z$2.string().transform(transformAddress)
2178
2179
  }).refine((data) => data.start < data.expiry, {
2179
2180
  message: "start must be before expiry",
2180
2181
  path: ["start"]
@@ -2190,8 +2191,12 @@ const OfferSchema = () => {
2190
2191
  * @returns The created offer.
2191
2192
  */
2192
2193
  function from$12(input) {
2194
+ const normalizedInput = {
2195
+ ...input,
2196
+ receiverIfMakerIsSeller: input.receiverIfMakerIsSeller ?? input.maker
2197
+ };
2193
2198
  try {
2194
- return OfferSchema().parse(input);
2199
+ return OfferSchema().parse(normalizedInput);
2195
2200
  } catch (error) {
2196
2201
  throw new InvalidOfferError(error);
2197
2202
  }
@@ -2243,6 +2248,7 @@ const serialize = (offer) => ({
2243
2248
  address: offer.callback.address,
2244
2249
  data: offer.callback.data
2245
2250
  },
2251
+ receiverIfMakerIsSeller: offer.receiverIfMakerIsSeller,
2246
2252
  hash: hash(offer)
2247
2253
  });
2248
2254
  /**
@@ -2285,8 +2291,9 @@ function random(config) {
2285
2291
  address: zeroAddress,
2286
2292
  data: "0x"
2287
2293
  };
2294
+ const maker = config?.maker ?? address();
2288
2295
  return from$12({
2289
- maker: config?.maker ?? address(),
2296
+ maker,
2290
2297
  assets: assetsScaled,
2291
2298
  obligationUnits: config?.obligationUnits ?? 0n,
2292
2299
  obligationShares: config?.obligationShares ?? 0n,
@@ -2303,7 +2310,8 @@ function random(config) {
2303
2310
  ...random$1(),
2304
2311
  lltv
2305
2312
  })).sort((a, b) => a.asset.localeCompare(b.asset)),
2306
- callback: config?.callback ?? emptyCallback
2313
+ callback: config?.callback ?? emptyCallback,
2314
+ receiverIfMakerIsSeller: config?.receiverIfMakerIsSeller ?? maker
2307
2315
  });
2308
2316
  }
2309
2317
  const weightedChoice = (pairs) => {
@@ -2389,6 +2397,10 @@ const types = {
2389
2397
  {
2390
2398
  name: "callback",
2391
2399
  type: "Callback"
2400
+ },
2401
+ {
2402
+ name: "receiverIfMakerIsSeller",
2403
+ type: "address"
2392
2404
  }
2393
2405
  ],
2394
2406
  Collateral: [
@@ -2434,7 +2446,8 @@ function hash(offer) {
2434
2446
  callback: {
2435
2447
  address: offer.callback.address.toLowerCase(),
2436
2448
  data: offer.callback.data
2437
- }
2449
+ },
2450
+ receiverIfMakerIsSeller: offer.receiverIfMakerIsSeller.toLowerCase()
2438
2451
  },
2439
2452
  primaryType: "Offer",
2440
2453
  types
@@ -2529,6 +2542,12 @@ const takeEvent = {
2529
2542
  indexed: false,
2530
2543
  internalType: "bool"
2531
2544
  },
2545
+ {
2546
+ name: "sellerReceiver",
2547
+ type: "address",
2548
+ indexed: false,
2549
+ internalType: "address"
2550
+ },
2532
2551
  {
2533
2552
  name: "group",
2534
2553
  type: "bytes32",
@@ -2650,44 +2669,6 @@ function from$10(parameters) {
2650
2669
  };
2651
2670
  }
2652
2671
 
2653
- //#endregion
2654
- //#region src/core/Quote.ts
2655
- const QuoteSchema = z$2.object({
2656
- obligationId: z$2.string().transform(transformHex),
2657
- ask: z$2.object({ price: z$2.bigint({ coerce: true }).min(0n).max(maxUint256) }),
2658
- bid: z$2.object({ price: z$2.bigint({ coerce: true }).min(0n).max(maxUint256) })
2659
- });
2660
- /**
2661
- * Creates a quote for a given obligation.
2662
- * @constructor
2663
- * @param parameters - {@link from.Parameters}
2664
- * @returns The created quote. {@link Quote}
2665
- * @throws If the quote is invalid. {@link InvalidQuoteError}
2666
- *
2667
- * @example
2668
- * ```ts
2669
- * const quote = Quote.from({ obligationId: "0x123", ask: { price: 100n }, bid: { price: 100n } });
2670
- * ```
2671
- */
2672
- function from$9(parameters) {
2673
- try {
2674
- const parsedQuote = QuoteSchema.parse(parameters);
2675
- return {
2676
- obligationId: parsedQuote.obligationId,
2677
- ask: parsedQuote.ask,
2678
- bid: parsedQuote.bid
2679
- };
2680
- } catch (error) {
2681
- throw new InvalidQuoteError(error);
2682
- }
2683
- }
2684
- var InvalidQuoteError = class extends BaseError {
2685
- name = "Quote.InvalidQuoteError";
2686
- constructor(error) {
2687
- super("Invalid quote.", { cause: error });
2688
- }
2689
- };
2690
-
2691
2672
  //#endregion
2692
2673
  //#region src/core/Tick.ts
2693
2674
  /** ln(1 + 0.025), scaled by 1e18. Matches TickLib onchain constant. */
@@ -2731,6 +2712,51 @@ var InvalidTickError = class extends BaseError {
2731
2712
  }
2732
2713
  };
2733
2714
 
2715
+ //#endregion
2716
+ //#region src/core/Quote.ts
2717
+ const SideInputSchema = z$2.object({ tick: z$2.number().int().min(0).max(TICK_RANGE).nullable() }).strict();
2718
+ const QuoteInputSchema = z$2.object({
2719
+ obligationId: z$2.string().transform(transformHex),
2720
+ ask: SideInputSchema,
2721
+ bid: SideInputSchema
2722
+ }).strict();
2723
+ /**
2724
+ * Creates a quote for a given obligation.
2725
+ * @constructor
2726
+ * @param parameters - {@link from.Parameters}
2727
+ * @returns The created quote. {@link Quote}
2728
+ * @throws If the quote is invalid. {@link InvalidQuoteError}
2729
+ *
2730
+ * @example
2731
+ * ```ts
2732
+ * const quote = Quote.from({ obligationId: "0x123", ask: { tick: 500 }, bid: { tick: 510 } });
2733
+ * ```
2734
+ */
2735
+ function from$9(parameters) {
2736
+ try {
2737
+ const parsedQuote = QuoteInputSchema.parse(parameters);
2738
+ return {
2739
+ obligationId: parsedQuote.obligationId,
2740
+ ask: sideFromTick(parsedQuote.ask),
2741
+ bid: sideFromTick(parsedQuote.bid)
2742
+ };
2743
+ } catch (error) {
2744
+ throw new InvalidQuoteError(error);
2745
+ }
2746
+ }
2747
+ var InvalidQuoteError = class extends BaseError {
2748
+ name = "Quote.InvalidQuoteError";
2749
+ constructor(error) {
2750
+ super("Invalid quote.", { cause: error });
2751
+ }
2752
+ };
2753
+ function sideFromTick(side) {
2754
+ return {
2755
+ tick: side.tick,
2756
+ price: side.tick === null ? 0n : tickToPrice(side.tick)
2757
+ };
2758
+ }
2759
+
2734
2760
  //#endregion
2735
2761
  //#region src/core/TradingFee.ts
2736
2762
  /** WAD constant (1e18) for fee scaling. */
@@ -3011,7 +3037,7 @@ const chains$1 = ({ chains }) => single("chain_ids", `Validates that offer chain
3011
3037
  });
3012
3038
  const maturity = ({ maturities }) => single("maturity", `Validates that offer maturity is one of: [${maturities.join(", ")}]`, (offer) => {
3013
3039
  const allowedMaturities = maturities.map((m) => from$16(m));
3014
- 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}` };
3015
3041
  });
3016
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) => {
3017
3043
  if (!isEmptyCallback(offer)) return { message: "Non-empty callbacks are not supported." };
@@ -3091,7 +3117,7 @@ const morphoRules = (chains) => {
3091
3117
  sameMaker(),
3092
3118
  amountMutualExclusivity(),
3093
3119
  chains$1({ chains }),
3094
- maturity({ maturities: [MaturityType.EndOfMonth, MaturityType.EndOfNextMonth] }),
3120
+ maturity({ maturities: [MaturityType.EndOfWeek, MaturityType.EndOfNextWeek] }),
3095
3121
  callback({
3096
3122
  callbacks: [Type$1.BuyWithEmptyCallback, Type$1.SellWithEmptyCallback],
3097
3123
  allowedAddresses: []
@@ -3239,6 +3265,7 @@ const RouterStatusResponse = z$1.object({
3239
3265
  * Creates an `ObligationResponse` from a `Obligation`.
3240
3266
  * @constructor
3241
3267
  * @param obligation - {@link Obligation}
3268
+ * @param quote - {@link Quote}
3242
3269
  * @returns The created `ObligationResponse`. {@link ObligationResponse}
3243
3270
  */
3244
3271
  function from$5(obligation, quote) {
@@ -3252,8 +3279,14 @@ function from$5(obligation, quote) {
3252
3279
  oracle: c.oracle
3253
3280
  })),
3254
3281
  maturity: obligation.maturity,
3255
- ask: { price: quote.ask.price.toString() },
3256
- 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
+ }
3257
3290
  };
3258
3291
  }
3259
3292
 
@@ -3300,7 +3333,8 @@ function from$4(input) {
3300
3333
  group: input.group,
3301
3334
  session: input.session,
3302
3335
  callback: input.callback.address,
3303
- callback_data: input.callback.data
3336
+ callback_data: input.callback.data,
3337
+ receiver_if_maker_is_seller: input.receiverIfMakerIsSeller
3304
3338
  },
3305
3339
  offer_hash: input.hash,
3306
3340
  obligation_id: id({
@@ -3367,7 +3401,7 @@ var InternalServerError = class extends APIError {
3367
3401
  super(STATUS_CODE.INTERNAL_SERVER_ERROR, message, "INTERNAL_SERVER_ERROR");
3368
3402
  }
3369
3403
  };
3370
- var BadRequestError = class extends APIError {
3404
+ var BadRequestError$1 = class extends APIError {
3371
3405
  constructor(message = "Invalid JSON format", details) {
3372
3406
  super(STATUS_CODE.BAD_REQUEST, message, "BAD_REQUEST", details);
3373
3407
  }
@@ -3389,7 +3423,7 @@ function success(args) {
3389
3423
  */
3390
3424
  function failure(err) {
3391
3425
  if (err instanceof APIError) return handleAPIError(err);
3392
- if (err instanceof SyntaxError) return handleAPIError(new BadRequestError(err.message));
3426
+ if (err instanceof SyntaxError) return handleAPIError(new BadRequestError$1(err.message));
3393
3427
  if (err instanceof z$2.ZodError) return handleAPIError(handleZodError(err));
3394
3428
  return handleAPIError(new InternalServerError());
3395
3429
  }
@@ -3439,7 +3473,7 @@ function __decorate(decorators, target, key, desc) {
3439
3473
  //#region src/api/Schema/openapi.ts
3440
3474
  const timestampExample = "2024-01-01T12:00:00.000Z";
3441
3475
  const offerCursorExample = "eyJvZmZzZXQiOjEwMH0";
3442
- const obligationCursorExample = "0x25690ae1aee324a005be565f3bcdd16dbf8daf7969b26c181c8b8f467dad9abc";
3476
+ const obligationCursorExample = "eyJzb3J0IjpbImlkIl0sImlkIjoiMHgxMjM0NTY3ODkwMTIzNDU2Nzg5MDEyMzQ1Njc4OTAxMjM0NTY3ODkwMTIzNDU2Nzg5MDEyMzQ1Njc4OTAiLCJhc2siOiIwIiwiYmlkIjoiMCIsIm1hdHVyaXR5IjoxNzYxOTIyODAwfQ";
3443
3477
  const offerExample = {
3444
3478
  offer: {
3445
3479
  obligation: {
@@ -3462,7 +3496,8 @@ const offerExample = {
3462
3496
  group: "0x000000000000000000000000000000000000000000000000000000000008b8f4",
3463
3497
  session: "0x0000000000000000000000000000000000000000000000000000000000000000",
3464
3498
  callback: "0x0000000000000000000000000000000000000000",
3465
- callback_data: "0x"
3499
+ callback_data: "0x",
3500
+ receiver_if_maker_is_seller: "0x7b093658BE7f90B63D7c359e8f408e503c2D9401"
3466
3501
  },
3467
3502
  offer_hash: "0xac4bd8318ec914f89f8af913f162230575b0ac0696a19256bc12138c5cfe1427",
3468
3503
  obligation_id: "0x25690ae1aee324a005be565f3bcdd16dbf8daf7969b26c181c8b8f467dad9abc",
@@ -3516,7 +3551,8 @@ const validateOfferExample = {
3516
3551
  callback: {
3517
3552
  address: "0x0000000000000000000000000000000000000000",
3518
3553
  data: "0x"
3519
- }
3554
+ },
3555
+ receiver_if_maker_is_seller: "0x7b093658BE7f90B63D7c359e8f408e503c2D9401"
3520
3556
  };
3521
3557
  const routerStatusExample = {
3522
3558
  status: "live",
@@ -3587,11 +3623,23 @@ __decorate([ApiProperty({
3587
3623
  example: validateOfferExample.callback.data
3588
3624
  })], ValidateCallbackRequest.prototype, "data", void 0);
3589
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);
3590
3632
  __decorate([ApiProperty({
3591
3633
  type: "string",
3592
3634
  example: "1000000000000000000"
3593
3635
  })], AskResponse.prototype, "price", void 0);
3594
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);
3595
3643
  __decorate([ApiProperty({
3596
3644
  type: "string",
3597
3645
  example: "1000000000000000000"
@@ -3664,6 +3712,10 @@ __decorate([ApiProperty({
3664
3712
  type: "string",
3665
3713
  example: offerExample.offer.callback_data
3666
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);
3667
3719
  var OfferListItemResponse = class {};
3668
3720
  __decorate([ApiProperty({
3669
3721
  type: () => OfferDataResponse,
@@ -3933,6 +3985,10 @@ __decorate([ApiProperty({
3933
3985
  type: () => ValidateCallbackRequest,
3934
3986
  example: validateOfferExample.callback
3935
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);
3936
3992
  var ValidateOffersRequest = class {};
3937
3993
  __decorate([ApiProperty({
3938
3994
  type: () => [ValidateOfferRequest],
@@ -4485,13 +4541,13 @@ __decorate([
4485
4541
  methods: ["get"],
4486
4542
  path: "/v1/obligations",
4487
4543
  summary: "List all obligations",
4488
- 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."
4489
4545
  }),
4490
4546
  ApiQuery({
4491
4547
  name: "cursor",
4492
4548
  type: "string",
4493
4549
  example: obligationCursorExample,
4494
- description: "Obligation id cursor for pagination."
4550
+ description: "Pagination cursor in base64url-encoded format."
4495
4551
  }),
4496
4552
  ApiQuery({
4497
4553
  name: "limit",
@@ -4535,6 +4591,15 @@ __decorate([
4535
4591
  style: "form",
4536
4592
  explode: false
4537
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
+ }),
4538
4603
  ApiResponse({
4539
4604
  status: 200,
4540
4605
  description: "Success",
@@ -4669,11 +4734,13 @@ function from$3(position) {
4669
4734
  //#endregion
4670
4735
  //#region src/api/Schema/requests.ts
4671
4736
  const MAX_LIMIT = 100;
4672
- const DEFAULT_LIMIT$4 = 20;
4737
+ const DEFAULT_LIMIT$5 = 20;
4738
+ const MAX_OBLIGATION_SORT_FIELDS = 3;
4673
4739
  const CONFIG_RULES_MAX_LIMIT = 1e3;
4674
4740
  const CONFIG_RULES_DEFAULT_LIMIT = 100;
4675
4741
  const CONFIG_CONTRACTS_MAX_LIMIT = 1e3;
4676
4742
  const CONFIG_CONTRACTS_DEFAULT_LIMIT = 1e3;
4743
+ const OBLIGATION_SORT_ENTRY_REGEX = /^-?(id|ask|bid|maturity)$/;
4677
4744
  /** Validate cursor is a valid base64url-encoded JSON object.
4678
4745
  * Domain layer handles semantic validation of cursor fields. */
4679
4746
  function isValidBase64urlJson(val) {
@@ -4705,8 +4772,8 @@ const PaginationQueryParams = z$2.object({
4705
4772
  description: "Pagination cursor in base64url-encoded format",
4706
4773
  example: "eyJzaWRlIjoic2VsbCIsImN1cnJlbnRQcmljZSI6IjEwMDAwMDAwMDAwMDAwMDAwMDAiLCJibG9ja051bWJlciI6MSwiYXNzZXRzIjoiMTAwMDAwMDAwMDAwMDAwMDAwMCIsImhhc2giOiIweGRmZDY4NTllM2UwODJkMTkzODlhMWFlYzFiZGFkN2U4ZDkyZDk2YjFhYTc5NDBkYTkxYTMxMjVkMzFlM2JlNWIiLCJ0b3RhbFJldHVybmVkIjoxMCwibm93IjoxNjAwMDAwMDAwfQ"
4707
4774
  }),
4708
- 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({
4709
- 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}`,
4710
4777
  example: 10
4711
4778
  })
4712
4779
  });
@@ -4803,9 +4870,12 @@ const GetOffersQueryParams = PaginationQueryParams.omit({ cursor: true }).extend
4803
4870
  });
4804
4871
  const GetObligationsQueryParams = z$2.object({
4805
4872
  ...PaginationQueryParams.shape,
4806
- cursor: z$2.string().optional().meta({
4807
- description: "Obligation id cursor",
4808
- 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"
4809
4879
  }),
4810
4880
  chains: csvArray(z$2.string().regex(/^[1-9]\d*$/, { message: "Chain must be a positive integer" }).transform((val) => Number.parseInt(val, 10))).meta({
4811
4881
  description: "Filter by chain IDs (comma-separated).",
@@ -4822,6 +4892,24 @@ const GetObligationsQueryParams = z$2.object({
4822
4892
  maturities: csvArray(z$2.string().regex(/^[1-9]\d*$/, { message: "Maturity must be a positive integer" }).transform((val) => Number.parseInt(val, 10))).meta({
4823
4893
  description: "Filter by exact maturity timestamps (comma-separated, unix seconds).",
4824
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"
4825
4913
  })
4826
4914
  });
4827
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({
@@ -4845,8 +4933,8 @@ const BookPaginationQueryParams = z$2.object({
4845
4933
  description: "Pagination cursor in base64url-encoded format for book levels",
4846
4934
  example: "eyJzaWRlIjoiYnV5IiwibGFzdFJhdGUiOiIxMDAwMDAwMDAwMDAwMDAwMDAwIiwib2ZmZXJzQ3Vyc29yIjpudWxsfQ"
4847
4935
  }),
4848
- 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({
4849
- 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}`,
4850
4938
  example: 10
4851
4939
  })
4852
4940
  });
@@ -4921,8 +5009,8 @@ async function getConfigRules(query, chains) {
4921
5009
  } catch (err) {
4922
5010
  return failure(err);
4923
5011
  }
4924
- if (cursorRule && typeFilter && !typeFilter.has(cursorRule.type)) return failure(new BadRequestError("Cursor type must match requested rule types"));
4925
- 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"));
4926
5014
  const startIndex = cursorRule ? findStartIndex$1(filteredRules, cursorRule) : 0;
4927
5015
  const page = filteredRules.slice(startIndex, startIndex + limit);
4928
5016
  const nextCursor = startIndex + limit < filteredRules.length && page.length > 0 ? formatCursor$1(page.at(-1)) : null;
@@ -4942,15 +5030,15 @@ function formatCursor$1(rule) {
4942
5030
  }
4943
5031
  function parseCursor$1(cursor) {
4944
5032
  const [type, chain, ...rest] = cursor.split(":");
4945
- if (!type || !chain || rest.length === 0) throw new BadRequestError("Cursor must be in the format type:chain_id:<value>");
4946
- 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");
4947
5035
  const chain_id = Number.parseInt(chain, 10);
4948
- 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");
4949
5037
  if (type === "maturity") {
4950
5038
  const timestampValue = Number.parseInt(rest[0] ?? "", 10);
4951
5039
  const nameValue = rest.slice(1).join(":");
4952
- if (!Number.isFinite(timestampValue) || nameValue.length === 0) throw new BadRequestError("Cursor must be in the format maturity:chain_id:timestamp:name");
4953
- 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");
4954
5042
  return {
4955
5043
  type,
4956
5044
  chain_id,
@@ -4961,8 +5049,8 @@ function parseCursor$1(cursor) {
4961
5049
  if (type === "callback") {
4962
5050
  const callbackTypeValue = rest[0] ?? "";
4963
5051
  const addressValue = rest.slice(1).join(":");
4964
- if (!callbackTypeValue || !addressValue) throw new BadRequestError("Cursor must be in the format callback:chain_id:callback_type:address");
4965
- 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");
4966
5054
  return {
4967
5055
  type,
4968
5056
  chain_id,
@@ -4972,14 +5060,14 @@ function parseCursor$1(cursor) {
4972
5060
  }
4973
5061
  if (type === "loan_token" || type === "collateral_token" || type === "oracle") {
4974
5062
  const addressValue = rest.join(":");
4975
- 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`);
4976
5064
  return {
4977
5065
  type,
4978
5066
  chain_id,
4979
5067
  address: parseAddress(addressValue, "Cursor address")
4980
5068
  };
4981
5069
  }
4982
- throw new BadRequestError("Cursor has an invalid rule type");
5070
+ throw new BadRequestError$1("Cursor has an invalid rule type");
4983
5071
  }
4984
5072
  function findStartIndex$1(rules, cursor) {
4985
5073
  let low = 0;
@@ -4993,7 +5081,7 @@ function findStartIndex$1(rules, cursor) {
4993
5081
  return low;
4994
5082
  }
4995
5083
  function parseAddress(address, label) {
4996
- 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`);
4997
5085
  return address.toLowerCase();
4998
5086
  }
4999
5087
  function isConfigRuleType(value) {
@@ -5006,7 +5094,7 @@ function parseMaturity(value) {
5006
5094
  try {
5007
5095
  return from$16(value);
5008
5096
  } catch (err) {
5009
- throw new BadRequestError(err instanceof Error ? err.message : "Invalid maturity timestamp");
5097
+ throw new BadRequestError$1(err instanceof Error ? err.message : "Invalid maturity timestamp");
5010
5098
  }
5011
5099
  }
5012
5100
  function isCallbackType(value) {
@@ -5089,7 +5177,7 @@ function formatValue(value) {
5089
5177
  async function validateOffers(body, gatekeeper) {
5090
5178
  const logger = getLogger();
5091
5179
  const result = safeParse("validate_offers", body, (issue) => issue.message);
5092
- 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"));
5093
5181
  const { offers: rawOffers } = result.data;
5094
5182
  const parsedOffers = [];
5095
5183
  const offerIndexByHash = /* @__PURE__ */ new Map();
@@ -5105,7 +5193,7 @@ async function validateOffers(body, gatekeeper) {
5105
5193
  } catch (err) {
5106
5194
  let message = err instanceof Error ? err.message : String(err);
5107
5195
  if (err instanceof InvalidOfferError) message = err.formattedMessage;
5108
- 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}`));
5109
5197
  }
5110
5198
  }
5111
5199
  try {
@@ -5164,7 +5252,7 @@ function createApp(parameters) {
5164
5252
  return c.json(failure$4.body, failure$4.statusCode);
5165
5253
  }
5166
5254
  if (body === null || typeof body !== "object") {
5167
- 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"));
5168
5256
  return c.json(failure$3.body, failure$3.statusCode);
5169
5257
  }
5170
5258
  const { statusCode, body: payload } = await validateOffers(body, gatekeeper);
@@ -5433,6 +5521,7 @@ const offers = s.table(EnumTableName.OFFERS, {
5433
5521
  buy: boolean("buy").notNull(),
5434
5522
  callbackAddress: varchar("callback_address", { length: 42 }).notNull(),
5435
5523
  callbackData: text("callback_data").notNull(),
5524
+ receiverIfMakerIsSeller: varchar("receiver_if_maker_is_seller", { length: 42 }),
5436
5525
  blockNumber: bigint("block_number", { mode: "number" }).notNull(),
5437
5526
  updatedAt: timestamp("updated_at").defaultNow().notNull()
5438
5527
  }, (table) => [
@@ -6814,7 +6903,7 @@ async function* collectPrices(parameters) {
6814
6903
  //#region src/indexer/collectors/CollectorBuilder.ts
6815
6904
  function createBuilder(parameters) {
6816
6905
  const { client, db, gatekeeper, options: { maxBlockNumber, blockWindow, interval } = {} } = parameters;
6817
- const createCollector = (name, collect) => create$16({
6906
+ const createCollector = (name, collect) => create$17({
6818
6907
  name,
6819
6908
  collect,
6820
6909
  client,
@@ -6918,7 +7007,7 @@ function from$1(config) {
6918
7007
  retryAttempts,
6919
7008
  retryDelayMs
6920
7009
  });
6921
- return create$18({
7010
+ return create$19({
6922
7011
  client,
6923
7012
  collectors: [
6924
7013
  offersCollector,
@@ -6928,7 +7017,7 @@ function from$1(config) {
6928
7017
  ]
6929
7018
  });
6930
7019
  }
6931
- function create$18(params) {
7020
+ function create$19(params) {
6932
7021
  const { collectors, client } = params;
6933
7022
  const indexerId = `${client.chain.id.toString()}.indexer`;
6934
7023
  const tracer = getTracer(`router.${indexerId}`);
@@ -6957,7 +7046,7 @@ function create$18(params) {
6957
7046
 
6958
7047
  //#endregion
6959
7048
  //#region src/indexer/collectors/Admin.ts
6960
- function create$17(parameters) {
7049
+ function create$18(parameters) {
6961
7050
  const collector = "admin";
6962
7051
  const { client, db, options: { maxBatchSize = 25, maxBlockNumber } = {} } = parameters;
6963
7052
  const maxBlockNumberBI = maxBlockNumber !== void 0 ? BigInt(maxBlockNumber) : void 0;
@@ -7197,8 +7286,8 @@ const names = [
7197
7286
  "positions",
7198
7287
  "prices"
7199
7288
  ];
7200
- function create$16({ name, collect, client, db, options }) {
7201
- const admin = create$17({
7289
+ function create$17({ name, collect, client, db, options }) {
7290
+ const admin = create$18({
7202
7291
  client,
7203
7292
  db,
7204
7293
  options
@@ -7325,7 +7414,7 @@ function start(collector) {
7325
7414
  //#endregion
7326
7415
  //#region src/database/domains/Blocks.ts
7327
7416
  /** Postgres implementation. */
7328
- const create$15 = (config) => {
7417
+ const create$16 = (config) => {
7329
7418
  const { db, chainRegistry } = config;
7330
7419
  const getChain = async (chainId) => {
7331
7420
  const rows = await db.select({
@@ -7504,14 +7593,14 @@ const create$15 = (config) => {
7504
7593
 
7505
7594
  //#endregion
7506
7595
  //#region src/database/domains/Book.ts
7507
- const DEFAULT_LIMIT$3 = 100;
7596
+ const DEFAULT_LIMIT$4 = 100;
7508
7597
  const MAX_TOTAL_OFFERS = 500;
7509
- function create$14(config) {
7598
+ function create$15(config) {
7510
7599
  const db = config.db;
7511
7600
  const logger = getLogger();
7512
7601
  const getOffers = async (parameters) => {
7513
7602
  const { side, obligationId, cursor: cursorString } = parameters;
7514
- const requestedLimit = parameters.limit ?? DEFAULT_LIMIT$3;
7603
+ const requestedLimit = parameters.limit ?? DEFAULT_LIMIT$4;
7515
7604
  const priceSortDirection = side === "sell" ? "asc" : "desc";
7516
7605
  const inputCursor = Cursor.decode(cursorString, logger);
7517
7606
  if (cursorString != null && inputCursor === null) return {
@@ -7543,7 +7632,7 @@ function create$14(config) {
7543
7632
  };
7544
7633
  return {
7545
7634
  get: async (parameters) => {
7546
- const { side, obligationId, cursor: cursorString, limit = DEFAULT_LIMIT$3 } = parameters;
7635
+ const { side, obligationId, cursor: cursorString, limit = DEFAULT_LIMIT$4 } = parameters;
7547
7636
  const tickSortDirection = side === "sell" ? "asc" : "desc";
7548
7637
  const inputCursor = LevelCursor.decode(cursorString, logger);
7549
7638
  if (cursorString != null && inputCursor === null) return {
@@ -7708,6 +7797,7 @@ async function _getOffers(db, params) {
7708
7797
  p.buy,
7709
7798
  p.callback_address,
7710
7799
  p.callback_data,
7800
+ p.receiver_if_maker_is_seller,
7711
7801
  p.block_number,
7712
7802
  p.group_chain_id,
7713
7803
  p.group_maker,
@@ -7785,6 +7875,7 @@ async function _getOffers(db, params) {
7785
7875
  buy,
7786
7876
  callback_address,
7787
7877
  callback_data,
7878
+ receiver_if_maker_is_seller,
7788
7879
  block_number,
7789
7880
  group_chain_id,
7790
7881
  group_maker,
@@ -7802,8 +7893,8 @@ async function _getOffers(db, params) {
7802
7893
  ORDER BY clc.hash, clc.position_chain_id, clc.position_contract, clc.position_user, clc.contribution_in_loan DESC
7803
7894
  ) deduped
7804
7895
  GROUP BY hash, obligation_id, assets, tick, obligation_units, obligation_shares, maturity, expiry, start, group_group, buy,
7805
- callback_address, callback_data, block_number, group_chain_id, group_maker,
7806
- consumed, chain_id, loan_token, session
7896
+ callback_address, callback_data, block_number, group_chain_id, group_maker,
7897
+ consumed, chain_id, loan_token, session, receiver_if_maker_is_seller
7807
7898
  UNION ALL
7808
7899
  -- Sell offers without callbacks: collateral positions not indexed, takeable = assets - consumed
7809
7900
  SELECT
@@ -7811,6 +7902,7 @@ async function _getOffers(db, params) {
7811
7902
  p.obligation_units, p.obligation_shares,
7812
7903
  p.maturity, p.expiry, p.start, p.group_group,
7813
7904
  p.buy, p.callback_address, p.callback_data,
7905
+ p.receiver_if_maker_is_seller,
7814
7906
  p.block_number, p.group_chain_id, p.group_maker,
7815
7907
  p.consumed, p.chain_id, p.loan_token, p.session,
7816
7908
  0 AS total_available
@@ -7839,6 +7931,7 @@ async function _getOffers(db, params) {
7839
7931
  oc.loan_token,
7840
7932
  oc.callback_address,
7841
7933
  oc.callback_data,
7934
+ oc.receiver_if_maker_is_seller,
7842
7935
  oc.block_number,
7843
7936
  oc.session,
7844
7937
  COALESCE(oc.total_available, 0) AS available,
@@ -7868,6 +7961,7 @@ async function _getOffers(db, params) {
7868
7961
  `);
7869
7962
  return {
7870
7963
  rows: raw.rows.map((row) => {
7964
+ const receiverIfMakerIsSeller = (row.receiver_if_maker_is_seller ?? row.group_maker).toLowerCase();
7871
7965
  return {
7872
7966
  hash: row.hash,
7873
7967
  maker: row.group_maker,
@@ -7892,6 +7986,7 @@ async function _getOffers(db, params) {
7892
7986
  address: row.callback_address,
7893
7987
  data: row.callback_data
7894
7988
  },
7989
+ receiverIfMakerIsSeller,
7895
7990
  blockNumber: row.block_number,
7896
7991
  consumed: BigInt(row.consumed ?? 0),
7897
7992
  available: BigInt(String(row.available ?? "0").split(".")[0] ?? "0"),
@@ -7983,7 +8078,7 @@ const DEFAULT_BATCH_SIZE = 4e3;
7983
8078
  * @param db - Database core instance.
7984
8079
  * @returns Callbacks domain. {@link CallbacksDomain}
7985
8080
  */
7986
- function create$13(db) {
8081
+ function create$14(db) {
7987
8082
  return {
7988
8083
  upsert: async (inputs) => {
7989
8084
  if (inputs.length === 0) return;
@@ -8038,7 +8133,7 @@ function create$13(db) {
8038
8133
 
8039
8134
  //#endregion
8040
8135
  //#region src/database/domains/Consumed.ts
8041
- function create$12(db) {
8136
+ function create$13(db) {
8042
8137
  return {
8043
8138
  create: async (events) => {
8044
8139
  if (events.length === 0) return;
@@ -8086,7 +8181,7 @@ function create$12(db) {
8086
8181
  * @param db - Database core instance.
8087
8182
  * @returns Groups domain. {@link GroupsDomain}
8088
8183
  */
8089
- function create$11(db) {
8184
+ function create$12(db) {
8090
8185
  return { create: async (groups$1) => {
8091
8186
  if (groups$1.length === 0) return;
8092
8187
  const rows = groups$1.map((group) => ({
@@ -8102,7 +8197,7 @@ function create$11(db) {
8102
8197
 
8103
8198
  //#endregion
8104
8199
  //#region src/database/domains/Lots.ts
8105
- function create$10(db) {
8200
+ function create$11(db) {
8106
8201
  return {
8107
8202
  get: async (parameters) => {
8108
8203
  const { chainId, user, contract, group, obligationId } = parameters ?? {};
@@ -8155,45 +8250,67 @@ function create$10(db) {
8155
8250
  * @param db - Database core instance.
8156
8251
  * @returns Obligations domain. {@link ObligationsDomain}
8157
8252
  */
8158
- function create$9(db) {
8159
- return { create: async (obligations$1) => {
8160
- if (obligations$1.length === 0) return;
8161
- const obligationsById = /* @__PURE__ */ new Map();
8162
- for (const obligation of obligations$1) {
8163
- const id$1 = id(obligation).toLowerCase();
8164
- if (!obligationsById.get(id$1)) obligationsById.set(id$1, obligation);
8165
- }
8166
- try {
8167
- await db.transaction(async (dbTx) => {
8168
- const obligationRows = obligations$1.map((obligation) => ({
8169
- obligationId: id(obligation),
8170
- chainId: obligation.chainId,
8171
- loanToken: obligation.loanToken.toLowerCase(),
8172
- maturity: obligation.maturity
8173
- }));
8174
- for (const batch of batch$1(obligationRows, DEFAULT_BATCH_SIZE)) await dbTx.insert(obligations).values(batch).onConflictDoNothing();
8175
- const collateralRows = obligations$1.flatMap((obligation) => {
8176
- 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) => ({
8177
8285
  obligationId: id(obligation),
8178
- asset: collateral.asset.toLowerCase(),
8179
- oracleChainId: obligation.chainId,
8180
- oracleAddress: collateral.oracle.toLowerCase(),
8181
- lltv: collateral.lltv
8286
+ chainId: obligation.chainId,
8287
+ loanToken: obligation.loanToken.toLowerCase(),
8288
+ maturity: obligation.maturity
8182
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();
8183
8301
  });
8184
- for (const batch of batch$1(collateralRows, DEFAULT_BATCH_SIZE)) await dbTx.insert(obligationCollateralsV2).values(batch).onConflictDoNothing();
8185
- });
8186
- } catch (err) {
8187
- const error = err instanceof Error ? err : new Error(String(err));
8188
- 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
+ }
8189
8306
  }
8190
- } };
8307
+ };
8191
8308
  }
8192
8309
 
8193
8310
  //#endregion
8194
8311
  //#region src/database/domains/Offers.ts
8195
- const DEFAULT_LIMIT$2 = 100;
8196
- function create$8(config) {
8312
+ const DEFAULT_LIMIT$3 = 100;
8313
+ function create$9(config) {
8197
8314
  const { db } = config;
8198
8315
  return {
8199
8316
  create: async (batches) => {
@@ -8205,6 +8322,7 @@ function create$8(config) {
8205
8322
  groupMaker: offer.maker.toLowerCase(),
8206
8323
  callbackAddress: offer.callback.address.toLowerCase(),
8207
8324
  callbackData: offer.callback.data,
8325
+ receiverIfMakerIsSeller: offer.receiverIfMakerIsSeller.toLowerCase(),
8208
8326
  blockNumber
8209
8327
  })));
8210
8328
  if (offersRows.length === 0) return [];
@@ -8233,7 +8351,7 @@ function create$8(config) {
8233
8351
  }
8234
8352
  },
8235
8353
  get: async (parameters) => {
8236
- const limit = parameters?.limit ?? DEFAULT_LIMIT$2;
8354
+ const limit = parameters?.limit ?? DEFAULT_LIMIT$3;
8237
8355
  const cursor = parameters?.cursor;
8238
8356
  const maker = parameters?.maker;
8239
8357
  if (cursor !== null && cursor !== void 0) {
@@ -8267,9 +8385,11 @@ function create$8(config) {
8267
8385
  loanToken: obligations.loanToken,
8268
8386
  callbackAddress: offers.callbackAddress,
8269
8387
  callbackData: offers.callbackData,
8388
+ receiverIfMakerIsSeller: offers.receiverIfMakerIsSeller,
8270
8389
  collaterals: collateralsLateral.collaterals,
8271
8390
  blockNumber: offers.blockNumber
8272
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();
8273
8393
  return {
8274
8394
  hash: row.hash,
8275
8395
  maker: row.maker,
@@ -8294,6 +8414,7 @@ function create$8(config) {
8294
8414
  address: row.callbackAddress,
8295
8415
  data: row.callbackData
8296
8416
  },
8417
+ receiverIfMakerIsSeller,
8297
8418
  consumed: 0n,
8298
8419
  available: 0n,
8299
8420
  takeable: 0n,
@@ -8318,64 +8439,30 @@ function create$8(config) {
8318
8439
  }
8319
8440
  throw new Error("Invalid parameters");
8320
8441
  },
8321
- getObligations: async (parameters) => {
8322
- const { ids, chainId: chainIds, loanToken: loanTokens, collateralToken: collateralTokens, maturity: maturities, cursor, limit = DEFAULT_LIMIT$2 } = parameters ?? {};
8323
- const now$1 = now();
8324
- const loanTokenFilter = loanTokens !== void 0 && loanTokens.length > 0 ? sql`(${sql.join(loanTokens.map((token) => sql`LOWER(${obligations.loanToken}) = ${token.toLowerCase()}`), sql` OR `)})` : void 0;
8325
- const collateralFilter = collateralTokens !== void 0 && collateralTokens.length > 0 ? sql`EXISTS (
8326
- SELECT 1 FROM ${obligationCollateralsV2} oc
8327
- WHERE oc.obligation_id = ${obligations.obligationId}
8328
- AND (${sql.join(collateralTokens.map((token) => sql`LOWER(oc.asset) = ${token.toLowerCase()}`), sql` OR `)})
8329
- )` : void 0;
8330
- const result = await db.select({
8331
- obligationId: obligations.obligationId,
8332
- chainId: obligations.chainId,
8333
- loanToken: obligations.loanToken,
8334
- collaterals: sql`ARRAY_AGG(jsonb_build_object('asset', ${obligationCollateralsV2.asset}, 'oracle', ${oracles.address}, 'lltv', ${obligationCollateralsV2.lltv}))`.as("collaterals"),
8335
- maturity: obligations.maturity
8336
- }).from(obligations).innerJoin(obligationCollateralsV2, eq(obligations.obligationId, obligationCollateralsV2.obligationId)).innerJoin(oracles, sql`${obligationCollateralsV2.oracleChainId} = ${oracles.chainId}
8337
- 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);
8338
- const items = [];
8339
- for (const row of result) items.push(from$13({
8340
- chainId: row.chainId,
8341
- loanToken: row.loanToken,
8342
- collaterals: row.collaterals.sort((a, b) => a.asset.localeCompare(b.asset)).map((c) => from$14({
8343
- asset: c.asset,
8344
- oracle: c.oracle,
8345
- lltv: from$15(BigInt(c.lltv))
8346
- })),
8347
- maturity: row.maturity
8348
- }));
8349
- const returnedItems = Array.from(items.values());
8350
- return {
8351
- obligations: returnedItems,
8352
- nextCursor: returnedItems.length === limit && returnedItems.length > 0 ? result[result.length - 1].obligationId : null
8353
- };
8354
- },
8355
8442
  getQuotes: async (parameters) => {
8356
8443
  const { obligationIds } = parameters;
8357
8444
  if (obligationIds.length === 0) return [];
8358
8445
  const now$2 = now();
8359
8446
  const query = ({ side }) => db.selectDistinctOn([offers.obligationId], {
8360
8447
  obligationId: offers.obligationId,
8361
- price: offers.tick
8448
+ tick: offers.tick
8362
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`);
8363
8450
  const [bestBuys, bestSells] = await Promise.all([query({ side: "buy" }), query({ side: "sell" })]);
8364
8451
  const quotes = /* @__PURE__ */ new Map();
8365
8452
  for (const row of bestSells) quotes.set(row.obligationId, {
8366
- ask: { price: BigInt(row.price) },
8367
- bid: { price: 0n }
8453
+ ask: { tick: row.tick },
8454
+ bid: { tick: null }
8368
8455
  });
8369
8456
  for (const row of bestBuys) {
8370
8457
  const quote = quotes.get(row.obligationId);
8371
8458
  if (!quote) {
8372
8459
  quotes.set(row.obligationId, {
8373
- ask: { price: 0n },
8374
- bid: { price: BigInt(row.price) }
8460
+ ask: { tick: null },
8461
+ bid: { tick: row.tick }
8375
8462
  });
8376
8463
  continue;
8377
8464
  }
8378
- quote.bid = { price: BigInt(row.price) };
8465
+ quote.bid = { tick: row.tick };
8379
8466
  }
8380
8467
  return Array.from(quotes.entries()).map(([id, quote]) => {
8381
8468
  return from$9({
@@ -8392,7 +8479,7 @@ function create$8(config) {
8392
8479
 
8393
8480
  //#endregion
8394
8481
  //#region src/database/domains/Offsets.ts
8395
- function create$7(db) {
8482
+ function create$8(db) {
8396
8483
  return { get: async (parameters) => {
8397
8484
  const { chainId, user, contract, group, obligationId } = parameters ?? {};
8398
8485
  const conditions = [];
@@ -8414,7 +8501,7 @@ function create$7(db) {
8414
8501
 
8415
8502
  //#endregion
8416
8503
  //#region src/database/domains/Oracles.ts
8417
- function create$6(db) {
8504
+ function create$7(db) {
8418
8505
  return {
8419
8506
  get: async ({ chainId }) => {
8420
8507
  return (await db.select({
@@ -8456,8 +8543,8 @@ function create$6(db) {
8456
8543
 
8457
8544
  //#endregion
8458
8545
  //#region src/database/domains/Positions.ts
8459
- const DEFAULT_LIMIT$1 = 100;
8460
- const create$5 = (db) => {
8546
+ const DEFAULT_LIMIT$2 = 100;
8547
+ const create$6 = (db) => {
8461
8548
  return {
8462
8549
  upsert: async (positions$1) => {
8463
8550
  const positionsMap = /* @__PURE__ */ new Map();
@@ -8512,7 +8599,7 @@ const create$5 = (db) => {
8512
8599
  return totalUpdated;
8513
8600
  },
8514
8601
  get: async (parameters) => {
8515
- const { limit = DEFAULT_LIMIT$1, cursor: encodedCursor, chainId, type, filled } = parameters ?? {};
8602
+ const { limit = DEFAULT_LIMIT$2, cursor: encodedCursor, chainId, type, filled } = parameters ?? {};
8516
8603
  let cursor = null;
8517
8604
  if (encodedCursor !== null && encodedCursor !== void 0) {
8518
8605
  const parsed = JSON.parse(Buffer.from(encodedCursor, "base64url").toString("utf8"));
@@ -8549,7 +8636,7 @@ const create$5 = (db) => {
8549
8636
  };
8550
8637
  },
8551
8638
  getByUser: async (parameters) => {
8552
- const { user, limit = DEFAULT_LIMIT$1, cursor: encodedCursor } = parameters;
8639
+ const { user, limit = DEFAULT_LIMIT$2, cursor: encodedCursor } = parameters;
8553
8640
  let cursor = null;
8554
8641
  if (encodedCursor !== null && encodedCursor !== void 0) {
8555
8642
  const parsed = JSON.parse(Buffer.from(encodedCursor, "base64url").toString("utf8"));
@@ -8699,7 +8786,7 @@ const create$5 = (db) => {
8699
8786
 
8700
8787
  //#endregion
8701
8788
  //#region src/database/domains/Transfers.ts
8702
- const create$4 = (db) => ({ create: async (transfers$1) => {
8789
+ const create$5 = (db) => ({ create: async (transfers$1) => {
8703
8790
  if (transfers$1.length === 0) return 0;
8704
8791
  return await db.transaction(async (dbTx) => {
8705
8792
  let totalInserted = 0;
@@ -8804,7 +8891,7 @@ const create$4 = (db) => ({ create: async (transfers$1) => {
8804
8891
  * @param config - Configuration with database instance
8805
8892
  * @returns TreesDomain instance
8806
8893
  */
8807
- function create$3(config) {
8894
+ function create$4(config) {
8808
8895
  const db = config.db;
8809
8896
  return {
8810
8897
  create: async (trees$1) => {
@@ -8887,11 +8974,11 @@ function splitProofs(concatenated) {
8887
8974
 
8888
8975
  //#endregion
8889
8976
  //#region src/database/domains/Validations.ts
8890
- const DEFAULT_LIMIT = 100;
8891
- function create$2(db) {
8977
+ const DEFAULT_LIMIT$1 = 100;
8978
+ function create$3(db) {
8892
8979
  return {
8893
8980
  get: async (params) => {
8894
- const { status: status$2, cursor, limit = DEFAULT_LIMIT } = params ?? {};
8981
+ const { status: status$2, cursor, limit = DEFAULT_LIMIT$1 } = params ?? {};
8895
8982
  if (cursor !== null && cursor !== void 0) {
8896
8983
  if (!cursor.startsWith("0x") || cursor.length !== 66) throw new Error("Invalid cursor format");
8897
8984
  }
@@ -8945,29 +9032,260 @@ function create$2(db) {
8945
9032
  };
8946
9033
  }
8947
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
+
8948
9263
  //#endregion
8949
9264
  //#region src/database/Database.ts
8950
9265
  function createDomains(core, chainRegistry) {
8951
9266
  return {
8952
- book: create$14({ db: core }),
8953
- blocks: create$15({
9267
+ book: create$15({ db: core }),
9268
+ blocks: create$16({
8954
9269
  db: core,
8955
9270
  chainRegistry
8956
9271
  }),
8957
- callbacks: create$13(core),
8958
- offers: create$8({ db: core }),
8959
- consumed: create$12(core),
8960
- groups: create$11(core),
8961
- lots: create$10(core),
8962
- obligations: create$9(core),
8963
- offsets: create$7(core),
8964
- oracles: create$6(core),
8965
- trees: create$3({ db: core }),
8966
- validations: create$2(core),
8967
- positions: create$5(core),
8968
- 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)
8969
9284
  };
8970
9285
  }
9286
+ function createReaders(core) {
9287
+ return { obligations: create$2({ db: core }) };
9288
+ }
8971
9289
  const AUGMENT_CACHE = /* @__PURE__ */ new WeakMap();
8972
9290
  function augmentWithDomains(base, chainRegistry) {
8973
9291
  const cached = AUGMENT_CACHE.get(base)?.get(chainRegistry);
@@ -8979,6 +9297,7 @@ function augmentWithDomains(base, chainRegistry) {
8979
9297
  });
8980
9298
  };
8981
9299
  const dms = createDomains(wrapped, chainRegistry);
9300
+ const readers = createReaders(wrapped);
8982
9301
  Object.defineProperties(wrapped, {
8983
9302
  book: {
8984
9303
  value: dms.book,
@@ -9035,6 +9354,10 @@ function augmentWithDomains(base, chainRegistry) {
9035
9354
  transfers: {
9036
9355
  value: dms.transfers,
9037
9356
  enumerable: true
9357
+ },
9358
+ readers: {
9359
+ value: readers,
9360
+ enumerable: true
9038
9361
  }
9039
9362
  });
9040
9363
  const chainRegistryMap = AUGMENT_CACHE.get(base);
@@ -9696,7 +10019,7 @@ var RouterCmd = class RouterCmd extends Command {
9696
10019
  const configPath = resolveConfigPath(options.configFile);
9697
10020
  const config = configPath !== null ? loadRouterConfig(configPath) : createDefaultConfig();
9698
10021
  const logger = defaultLogger(config.logging.level, config.logging.pretty);
9699
- 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));
9700
10023
  const clients = (config.indexer?.chains ?? []).map((name) => {
9701
10024
  const chainConfig = config.chains[name];
9702
10025
  if (!chainConfig) throw new Error(`Indexer chain ${name} is not defined under [chains].`);
@@ -9744,7 +10067,7 @@ const gatekeeperCmd = new RouterCmd("gatekeeper");
9744
10067
  gatekeeperCmd.description("Start Gatekeeper validation service.").action(async (opts) => {
9745
10068
  const { gatekeeper: gatekeeperConfig, chainRegistry, logger } = opts;
9746
10069
  await runWithLogger(logger, async () => {
9747
- const gatekeeperCore = create$20({ rules: morphoRules(chainRegistry.list()) });
10070
+ const gatekeeperCore = create$21({ rules: morphoRules(chainRegistry.list()) });
9748
10071
  const handle = await start$1({
9749
10072
  gatekeeper: gatekeeperCore,
9750
10073
  chainRegistry,
@@ -9890,7 +10213,7 @@ async function getConfigContracts(query, chainRegistry) {
9890
10213
  }
9891
10214
  function parseCursor(cursor) {
9892
10215
  const [chain, address] = cursor.split(":", 2);
9893
- 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...");
9894
10217
  return {
9895
10218
  chain_id: Number.parseInt(chain, 10),
9896
10219
  address: address.toLowerCase()
@@ -10211,37 +10534,44 @@ async function getHealthCollectors(query, db, chainRegistry) {
10211
10534
 
10212
10535
  //#endregion
10213
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
+ }
10214
10541
  async function getObligation(params, db) {
10215
10542
  const logger = getLogger();
10216
10543
  const result = safeParse("get_obligation", params, (issue) => issue.message);
10217
10544
  if (!result.success) return failure(result.error);
10218
10545
  const query = result.data;
10219
10546
  try {
10220
- const { obligations } = await db.offers.getObligations({ ids: [query.obligation_id] });
10221
- if (obligations.length === 0) return failure(new NotFoundError("Obligation not found"));
10222
- const obligation = obligations[0];
10223
- 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];
10224
10553
  return success({
10225
- data: from$5(obligation, quote ?? {
10226
- obligationId: id(obligation),
10227
- ask: { price: 0n },
10228
- bid: { price: 0n }
10229
- }),
10554
+ data: from$5(obligation.obligation, obligation.quote),
10230
10555
  cursor: null
10231
10556
  });
10232
10557
  } catch (err) {
10558
+ const payloadError = toPayloadError$1(err);
10233
10559
  logger.error({
10234
- err,
10560
+ err: payloadError,
10235
10561
  msg: "Error get obligation",
10236
- errorMessage: err instanceof Error ? err.message : String(err),
10237
- 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
10238
10564
  });
10239
- return failure(err);
10565
+ return failure(payloadError);
10240
10566
  }
10241
10567
  }
10242
10568
 
10243
10569
  //#endregion
10244
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
+ }
10245
10575
  async function getObligations(queryParameters, db) {
10246
10576
  const logger = getLogger();
10247
10577
  const result = safeParse("get_obligations", queryParameters, (issue) => issue.message);
@@ -10252,31 +10582,28 @@ async function getObligations(queryParameters, db) {
10252
10582
  const loanTokens = query.loan_tokens?.length ? query.loan_tokens : void 0;
10253
10583
  const collateralTokens = query.collateral_tokens?.length ? query.collateral_tokens : void 0;
10254
10584
  const maturities = query.maturities?.length ? query.maturities : void 0;
10255
- const { obligations, nextCursor } = await db.offers.getObligations({
10256
- cursor: query.cursor,
10257
- limit: query.limit,
10585
+ const listing = await db.readers.obligations.list({
10258
10586
  chainId: chainIds,
10259
10587
  loanToken: loanTokens,
10260
10588
  collateralToken: collateralTokens,
10261
- maturity: maturities
10589
+ maturity: maturities,
10590
+ sort: query.sort,
10591
+ cursor: query.cursor,
10592
+ limit: query.limit
10262
10593
  });
10263
- const quotes = await db.offers.getQuotes({ obligationIds: obligations.map((o) => id(o)) });
10264
10594
  return success({
10265
- data: obligations.map((o) => from$5(o, quotes.find((q) => q.obligationId === id(o)) ?? {
10266
- obligationId: id(o),
10267
- ask: { price: 0n },
10268
- bid: { price: 0n }
10269
- })),
10270
- cursor: nextCursor ?? null
10595
+ data: listing.obligations.map((item) => from$5(item.obligation, item.quote)),
10596
+ cursor: listing.nextCursor
10271
10597
  });
10272
10598
  } catch (err) {
10599
+ const payloadError = toPayloadError(err);
10273
10600
  logger.error({
10274
- err,
10601
+ err: payloadError,
10275
10602
  msg: "Error get obligations",
10276
- errorMessage: err instanceof Error ? err.message : String(err),
10277
- 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
10278
10605
  });
10279
- return failure(err);
10606
+ return failure(payloadError);
10280
10607
  }
10281
10608
  }
10282
10609
 
@@ -10289,7 +10616,7 @@ async function getObligations(queryParameters, db) {
10289
10616
  * @returns The offers with pagination cursor.
10290
10617
  */
10291
10618
  async function getOffersQuery(db, parameters) {
10292
- const limit = parameters?.limit ?? DEFAULT_LIMIT$2;
10619
+ const limit = parameters?.limit ?? DEFAULT_LIMIT$3;
10293
10620
  const cursor = parameters?.cursor;
10294
10621
  const maker = parameters?.maker;
10295
10622
  if (cursor !== null && cursor !== void 0) {
@@ -10376,6 +10703,7 @@ async function getOffersQuery(db, parameters) {
10376
10703
  loanToken: obligations.loanToken,
10377
10704
  callbackAddress: offers.callbackAddress,
10378
10705
  callbackData: offers.callbackData,
10706
+ receiverIfMakerIsSeller: offers.receiverIfMakerIsSeller,
10379
10707
  collaterals: collateralsLateral.collaterals,
10380
10708
  blockNumber: offers.blockNumber,
10381
10709
  available: sql`${availableExpr}::numeric`.as("available"),
@@ -10397,6 +10725,7 @@ async function getOffersQuery(db, parameters) {
10397
10725
  )
10398
10726
  END
10399
10727
  ) > 0` : void 0)).orderBy(asc(offers.hash)).limit(limit)).map((row) => {
10728
+ const receiverIfMakerIsSeller = (row.receiverIfMakerIsSeller ?? row.maker).toLowerCase();
10400
10729
  return {
10401
10730
  hash: row.hash,
10402
10731
  maker: row.maker,
@@ -10421,6 +10750,7 @@ async function getOffersQuery(db, parameters) {
10421
10750
  address: row.callbackAddress,
10422
10751
  data: row.callbackData
10423
10752
  },
10753
+ receiverIfMakerIsSeller,
10424
10754
  consumed: BigInt(row.consumed),
10425
10755
  available: BigInt(String(row.available ?? "0").split(".")[0] ?? "0"),
10426
10756
  takeable: BigInt(String(row.takeable ?? "0").split(".")[0] ?? "0"),
@@ -10734,7 +11064,7 @@ startCmd.description("Start Router services.").addOption(new Option("--mock <n>"
10734
11064
  let gatekeeperUrl = gatekeeperConfig?.url;
10735
11065
  let gatekeeperHandle = null;
10736
11066
  if (!gatekeeperUrl) {
10737
- const gatekeeperCore = create$20({ rules: morphoRules(chainRegistry.list()) });
11067
+ const gatekeeperCore = create$21({ rules: morphoRules(chainRegistry.list()) });
10738
11068
  gatekeeperHandle = await start$1({
10739
11069
  gatekeeper: gatekeeperCore,
10740
11070
  chainRegistry,