@morpho-dev/router 0.1.10 → 0.1.11

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.
@@ -6,7 +6,7 @@ import { parseUnits, maxUint256, encodeAbiParameters, publicActions, parseEventL
6
6
  import { Base64 } from 'js-base64';
7
7
  import { getBlockNumber } from 'viem/actions';
8
8
  import { AsyncLocalStorage } from 'async_hooks';
9
- import { desc, and, eq, sql, gte, lte, asc, inArray } from 'drizzle-orm';
9
+ import { asc, desc, and, eq, sql, gte, lte, inArray } from 'drizzle-orm';
10
10
  import { pgSchema, timestamp, varchar, bigint, text, boolean, integer, numeric, index, serial, jsonb, uniqueIndex, primaryKey } from 'drizzle-orm/pg-core';
11
11
  import path from 'path';
12
12
  import { PGlite } from '@electric-sql/pglite';
@@ -736,46 +736,95 @@ var WhitelistedCallbackAddresses = {
736
736
  "0x1111111111111111111111111111111111111111",
737
737
  "0x2222222222222222222222222222222222222222"
738
738
  // @TODO: update once deployed and add mapping per chain if needed
739
- ]
739
+ ].map((address) => address.toLowerCase())
740
740
  };
741
741
  function buildLiquidity(parameters) {
742
- const { type, user, contract, chainId, amount, index: index2 = 0, updatedAt = /* @__PURE__ */ new Date() } = parameters;
743
- if (type !== "buy_with_empty_callback" /* BuyWithEmptyCallback */)
744
- throw new Error(`CallbackType not implemented: ${type}`);
745
- const amountStr = amount.toString();
746
- const id = `${user}-${chainId.toString()}-${type}-${contract}`.toLowerCase();
747
- return {
748
- userPosition: {
749
- id,
750
- availableLiquidityQueueId: id,
751
- user: user.toLowerCase(),
752
- chainId,
753
- amount: amountStr,
754
- updatedAt
755
- },
756
- queues: [
757
- {
758
- queue: {
759
- queueId: id,
760
- availableLiquidityPoolId: id,
761
- index: index2,
742
+ switch (parameters.type) {
743
+ case "buy_with_empty_callback" /* BuyWithEmptyCallback */: {
744
+ const { user, loanToken, chainId, amount, index: index2 = 0, updatedAt = /* @__PURE__ */ new Date() } = parameters;
745
+ const amountStr = amount.toString();
746
+ const id = `${user}-${chainId.toString()}-${parameters.type}-${loanToken}`.toLowerCase();
747
+ const poolId = `${user}-${chainId.toString()}-${loanToken}`.toLowerCase();
748
+ return {
749
+ userPosition: {
750
+ id,
751
+ availableLiquidityQueueId: id,
752
+ user: user.toLowerCase(),
753
+ chainId,
754
+ amount: amountStr,
762
755
  updatedAt
763
756
  },
764
- pool: {
757
+ queues: [
758
+ {
759
+ queue: {
760
+ queueId: id,
761
+ availableLiquidityPoolId: poolId,
762
+ index: index2,
763
+ callbackAmount: "0",
764
+ updatedAt
765
+ },
766
+ pool: {
767
+ id: poolId,
768
+ amount: amountStr,
769
+ updatedAt
770
+ }
771
+ }
772
+ ]
773
+ };
774
+ }
775
+ case "sell_withdraw_from_wallet" /* SellWithdrawFromWallet */: {
776
+ const {
777
+ user,
778
+ termId,
779
+ chainId,
780
+ amount,
781
+ collaterals,
782
+ index: index2 = 0,
783
+ updatedAt = /* @__PURE__ */ new Date()
784
+ } = parameters;
785
+ const amountStr = amount.toString();
786
+ const id = `${user}-${chainId.toString()}-${parameters.type}-${termId}`.toLowerCase();
787
+ return {
788
+ userPosition: {
765
789
  id,
790
+ availableLiquidityQueueId: id,
791
+ user: user.toLowerCase(),
792
+ chainId,
766
793
  amount: amountStr,
767
794
  updatedAt
768
- }
769
- }
770
- ]
771
- };
795
+ },
796
+ queues: collaterals.map((collateral) => {
797
+ const poolId = `${user}-${chainId.toString()}-${collateral.collateralAddress}`.toLowerCase();
798
+ return {
799
+ queue: {
800
+ queueId: id,
801
+ availableLiquidityPoolId: poolId,
802
+ index: index2,
803
+ callbackAmount: collateral.callbackAmount.toString(),
804
+ updatedAt
805
+ },
806
+ pool: {
807
+ id: poolId,
808
+ amount: collateral.balance.toString(),
809
+ updatedAt
810
+ }
811
+ };
812
+ })
813
+ };
814
+ }
815
+ default: {
816
+ throw new Error(`CallbackType not implemented`);
817
+ }
818
+ }
772
819
  }
773
820
  function getCallbackIdForOffer(offer) {
774
821
  if (offer.buy && offer.callback.data === "0x") {
775
- const type = "buy_with_empty_callback" /* BuyWithEmptyCallback */;
776
- const user = offer.offering;
777
- const loanToken = offer.loanToken;
778
- return `${user}-${offer.chainId.toString()}-${type}-${loanToken}`.toLowerCase();
822
+ return `${offer.offering}-${offer.chainId.toString()}-${"buy_with_empty_callback" /* BuyWithEmptyCallback */}-${offer.loanToken}`.toLowerCase();
823
+ }
824
+ if (!offer.buy && offer.callback.data !== "0x" && WhitelistedCallbackAddresses["sell_withdraw_from_wallet" /* SellWithdrawFromWallet */].includes(
825
+ offer.callback.address.toLowerCase()
826
+ )) {
827
+ return `${offer.offering}-${offer.chainId.toString()}-${"sell_withdraw_from_wallet" /* SellWithdrawFromWallet */}-${Offer.termId(offer)}`.toLowerCase();
779
828
  }
780
829
  return null;
781
830
  }
@@ -895,20 +944,20 @@ async function fetch2(parameters) {
895
944
  const map = await fetchBalancesAndAllowances({
896
945
  client,
897
946
  spender,
898
- pairs: pairs.map(({ user, contract }) => ({ user, token: contract })),
947
+ pairs: pairs.map(({ user, loanToken }) => ({ user, token: loanToken })),
899
948
  options
900
949
  });
901
950
  const out = [];
902
- for (const [user, perContract] of map) {
903
- for (const [contract, { balance, allowance }] of perContract) {
951
+ for (const [user, perLoanToken] of map) {
952
+ for (const [loanToken, { balance, allowance }] of perLoanToken) {
904
953
  const amount = balance < allowance ? balance : allowance;
905
954
  out.push(
906
955
  buildLiquidity({
907
956
  type,
908
957
  user,
909
- contract,
958
+ loanToken,
910
959
  chainId,
911
- amount: amount.toString(),
960
+ amount,
912
961
  index: 0
913
962
  })
914
963
  );
@@ -966,16 +1015,16 @@ __export(Logger_exports, {
966
1015
  silentLogger: () => silentLogger
967
1016
  });
968
1017
  var LogLevelValues = [
969
- "silent",
970
1018
  "trace",
971
1019
  "debug",
972
1020
  "info",
973
1021
  "warn",
974
1022
  "error",
975
- "fatal"
1023
+ "fatal",
1024
+ "silent"
976
1025
  ];
977
1026
  function defaultLogger(minLevel) {
978
- const threshold = minLevel ?? "info";
1027
+ const threshold = minLevel ?? process.env.ROUTER_LOG_LEVEL ?? "info";
979
1028
  const levelIndexByName = LogLevelValues.reduce(
980
1029
  (acc, lvl, idx) => {
981
1030
  acc[lvl] = idx;
@@ -1071,7 +1120,7 @@ function createBuyWithEmptyCallbackLiquidityCollector(params) {
1071
1120
  chainId: chain.id,
1072
1121
  spender: chain.morpho,
1073
1122
  type: "buy_with_empty_callback" /* BuyWithEmptyCallback */,
1074
- pairs: pairs.map(({ user, token }) => ({ user, contract: token })),
1123
+ pairs: pairs.map(({ user, token }) => ({ user, loanToken: token })),
1075
1124
  options: {
1076
1125
  blockNumber: latestBlockNumber,
1077
1126
  batchSize: maxBatchSize
@@ -1122,14 +1171,15 @@ function createBuyWithEmptyCallbackLiquidityCollector(params) {
1122
1171
  };
1123
1172
  }
1124
1173
  function createChainReorgsCollector(parameters) {
1125
- const collectorName = "chain_reorgs";
1174
+ const collector = "chain_reorgs";
1126
1175
  const {
1127
1176
  client,
1128
1177
  subscribers,
1129
1178
  collectorStore,
1130
1179
  chain,
1131
- options: { maxBatchSize = 25, interval } = {}
1180
+ options: { maxBatchSize = 25, interval = 3e4, maxBlockNumber } = {}
1132
1181
  } = parameters;
1182
+ const maxBlockNumberBI = maxBlockNumber !== void 0 ? BigInt(maxBlockNumber) : void 0;
1133
1183
  let finalizedBlock = null;
1134
1184
  let unfinalizedBlocks = [];
1135
1185
  const commonAncestor = (block) => {
@@ -1138,67 +1188,79 @@ function createChainReorgsCollector(parameters) {
1138
1188
  if (finalizedBlock == null) throw new Error("Failed to get common ancestor");
1139
1189
  return finalizedBlock;
1140
1190
  };
1141
- const reconcile = async (block) => {
1142
- if (block.hash === null || block.number === null || block.parentHash === null)
1143
- throw new Error("Failed to get block");
1144
- const latestBlock = unfinalizedBlocks[unfinalizedBlocks.length - 1];
1145
- if (latestBlock === void 0) {
1146
- unfinalizedBlocks.push({
1191
+ const collect = Utils.lazy((emit, { stop }) => {
1192
+ const logger = getLogger();
1193
+ const reconcile = async (block) => {
1194
+ if (block.hash === null || block.number === null || block.parentHash === null)
1195
+ throw new Error("Failed to get block");
1196
+ const latestBlock = unfinalizedBlocks[unfinalizedBlocks.length - 1];
1197
+ if (latestBlock === void 0) {
1198
+ const newBlock2 = {
1199
+ hash: block.hash,
1200
+ number: block.number,
1201
+ parentHash: block.parentHash
1202
+ };
1203
+ unfinalizedBlocks.push(newBlock2);
1204
+ return newBlock2;
1205
+ }
1206
+ if (latestBlock.hash === block.hash) return latestBlock;
1207
+ if (latestBlock.number >= block.number) {
1208
+ const ancestor = commonAncestor(block);
1209
+ logger.info({
1210
+ collector,
1211
+ chainId: chain.id,
1212
+ msg: `reorg detected, latestBlock.number: ${latestBlock.number} >= block.number: ${block.number} on chain ${chain.id}. Ancestor: ${ancestor.number}`
1213
+ });
1214
+ subscribers.forEach((subscriber) => subscriber.onReorg(Number(ancestor.number)));
1215
+ unfinalizedBlocks = [];
1216
+ return ancestor;
1217
+ }
1218
+ if (latestBlock.number + 1n < block.number) {
1219
+ logger.debug({
1220
+ collector,
1221
+ chainId: chain.id,
1222
+ msg: `missing blocks between block ${latestBlock.number} and block ${block.number} on chain ${chain.id}`
1223
+ });
1224
+ const missingBlockNumbers = (() => {
1225
+ const missingBlockNumbers2 = [];
1226
+ let start2 = latestBlock.number + 1n;
1227
+ const threshold = latestBlock.number + BigInt(maxBatchSize) > block.number ? block.number : latestBlock.number + BigInt(maxBatchSize);
1228
+ while (start2 < threshold) {
1229
+ missingBlockNumbers2.push(start2);
1230
+ start2 = start2 + 1n;
1231
+ }
1232
+ return missingBlockNumbers2;
1233
+ })();
1234
+ const missingBlocks = await Promise.all(
1235
+ missingBlockNumbers.map(
1236
+ (blockNumber) => client.getBlock({ blockNumber, includeTransactions: false })
1237
+ )
1238
+ );
1239
+ for (const missingBlock of missingBlocks) {
1240
+ const returnedBlock = await reconcile(missingBlock);
1241
+ if (returnedBlock.number !== missingBlock.number) return returnedBlock;
1242
+ }
1243
+ return reconcile(block);
1244
+ }
1245
+ if (block.parentHash !== latestBlock.hash) {
1246
+ const ancestor = commonAncestor(block);
1247
+ logger.info({
1248
+ collector,
1249
+ chainId: chain.id,
1250
+ msg: `reorg detected, block.parentHash: ${block.parentHash} !== latestBlock.hash: ${latestBlock.hash} on chain ${chain.id}. Ancestor: ${ancestor.number}`
1251
+ });
1252
+ subscribers.forEach((subscriber) => subscriber.onReorg(Number(ancestor.number)));
1253
+ unfinalizedBlocks = [];
1254
+ return ancestor;
1255
+ }
1256
+ const newBlock = {
1147
1257
  hash: block.hash,
1148
1258
  number: block.number,
1149
1259
  parentHash: block.parentHash
1150
- });
1151
- return;
1152
- }
1153
- if (latestBlock.hash === block.hash) return;
1154
- if (latestBlock.number >= block.number) {
1155
- const ancestor = commonAncestor(block);
1156
- console.log(
1157
- `reorg detected, latestBlock.number: ${latestBlock.number} > block.number: ${block.number} on chain ${chain.id}. Ancestor: ${ancestor.number}`
1158
- );
1159
- subscribers.forEach((subscriber) => subscriber.onReorg(Number(ancestor.number)));
1160
- unfinalizedBlocks = [];
1161
- return;
1162
- }
1163
- if (latestBlock.number + 1n < block.number) {
1164
- console.log(
1165
- `missing blocks between block ${latestBlock.number} and block ${block.number} on chain ${chain.id}`
1166
- );
1167
- const missingBlockNumbers = (() => {
1168
- const missingBlockNumbers2 = [];
1169
- let start2 = latestBlock.number + 1n;
1170
- const threshold = latestBlock.number + BigInt(maxBatchSize) > block.number ? block.number : latestBlock.number + BigInt(maxBatchSize);
1171
- while (start2 < threshold) {
1172
- missingBlockNumbers2.push(start2);
1173
- start2 = start2 + 1n;
1174
- }
1175
- return missingBlockNumbers2;
1176
- })();
1177
- const missingBlocks = await Promise.all(
1178
- missingBlockNumbers.map(
1179
- (blockNumber) => client.getBlock({ blockNumber, includeTransactions: false })
1180
- )
1181
- );
1182
- for (const missingBlock of missingBlocks) await reconcile(missingBlock);
1183
- await reconcile(block);
1184
- return;
1185
- }
1186
- if (block.parentHash !== latestBlock.hash) {
1187
- const ancestor = commonAncestor(block);
1188
- console.log(
1189
- `reorg detected, block.parentHash: ${block.parentHash} !== latestBlock.hash: ${latestBlock.hash} on chain ${chain.id}. Ancestor: ${ancestor.number}`
1190
- );
1191
- subscribers.forEach((subscriber) => subscriber.onReorg(Number(ancestor.number)));
1192
- unfinalizedBlocks = [];
1193
- return;
1194
- }
1195
- unfinalizedBlocks.push({
1196
- hash: block.hash,
1197
- number: block.number,
1198
- parentHash: block.parentHash
1199
- });
1200
- };
1201
- const collect = Utils.lazy((emit) => {
1260
+ };
1261
+ unfinalizedBlocks.push(newBlock);
1262
+ return newBlock;
1263
+ };
1202
1264
  let tick = 0;
1203
1265
  const fetchFinalizedBlock = async () => {
1204
1266
  if (tick % 20 === 0 || finalizedBlock === null) {
@@ -1206,32 +1268,58 @@ function createChainReorgsCollector(parameters) {
1206
1268
  blockTag: "finalized",
1207
1269
  includeTransactions: false
1208
1270
  });
1209
- if (finalizedBlock === null) throw new Error("Failed to get finalized block");
1271
+ if (finalizedBlock === null) {
1272
+ const msg = "Failed to get finalized block";
1273
+ logger.fatal({ collector, chainId: chain.id, msg });
1274
+ throw new Error(msg);
1275
+ }
1276
+ unfinalizedBlocks = unfinalizedBlocks.filter((b) => b.number >= finalizedBlock.number);
1210
1277
  }
1211
1278
  tick++;
1212
1279
  };
1213
- return Utils.poll(
1280
+ let isMaxBlockNumberReached = false;
1281
+ const unpoll = Utils.poll(
1214
1282
  async () => {
1215
- await fetchFinalizedBlock();
1216
- const latestBlock = await client.getBlock({
1283
+ if (isMaxBlockNumberReached) {
1284
+ stop();
1285
+ return;
1286
+ }
1287
+ const head = await client.getBlock({
1217
1288
  blockTag: "latest",
1218
1289
  includeTransactions: false
1219
1290
  });
1220
- await reconcile(latestBlock);
1221
- const lastBlockNumber = Number(latestBlock.number);
1291
+ if (maxBlockNumberBI !== void 0 && head.number >= maxBlockNumberBI) {
1292
+ logger.info({
1293
+ collector,
1294
+ chainId: chain.id,
1295
+ msg: `head is greater than max block number, head.number: ${head.number} > maxBlockNumber: ${maxBlockNumber} on chain ${chain.id}.`
1296
+ });
1297
+ isMaxBlockNumberReached = true;
1298
+ subscribers.forEach((subscriber) => subscriber.onReorg(maxBlockNumber));
1299
+ await collectorStore.saveBlockNumber({
1300
+ collectorName: collector,
1301
+ chainId: chain.id,
1302
+ blockNumber: maxBlockNumber
1303
+ });
1304
+ emit(maxBlockNumber);
1305
+ return;
1306
+ }
1307
+ await fetchFinalizedBlock();
1308
+ const blockNumber = Number((await reconcile(head)).number);
1222
1309
  await collectorStore.saveBlockNumber({
1223
- collectorName: "chain_reorgs",
1310
+ collectorName: collector,
1224
1311
  chainId: chain.id,
1225
- blockNumber: lastBlockNumber
1312
+ blockNumber
1226
1313
  });
1227
- emit(lastBlockNumber);
1314
+ emit(blockNumber);
1228
1315
  },
1229
1316
  { interval: interval ?? 3e4 }
1230
1317
  );
1318
+ return unpoll;
1231
1319
  });
1232
1320
  return {
1233
- name: collectorName,
1234
- lastSyncedBlock: async () => await collectorStore.getBlockNumber({ collectorName, chainId: chain.id }),
1321
+ name: collector,
1322
+ lastSyncedBlock: async () => await collectorStore.getBlockNumber({ collectorName: collector, chainId: chain.id }),
1235
1323
  collect,
1236
1324
  onReorg: (_) => {
1237
1325
  }
@@ -1676,11 +1764,19 @@ var offers = s.table(
1676
1764
  index("offers_expiry_idx").on(table.expiry),
1677
1765
  index("offers_rate_idx").on(table.rate),
1678
1766
  index("offers_assets_idx").on(table.assets),
1767
+ index("offers_created_at_idx").on(table.createdAt),
1679
1768
  // Compound indices for cursor pagination with hash
1680
1769
  index("offers_rate_hash_idx").on(table.rate, table.hash),
1681
1770
  index("offers_maturity_hash_idx").on(table.maturity, table.hash),
1682
1771
  index("offers_expiry_hash_idx").on(table.expiry, table.hash),
1683
- index("offers_assets_hash_idx").on(table.assets, table.hash)
1772
+ index("offers_assets_hash_idx").on(table.assets, table.hash),
1773
+ // Compound index for multi-level sorting optimization (rate, createdAt, assets, hash)
1774
+ index("offers_rate_created_at_assets_hash_idx").on(
1775
+ table.rate,
1776
+ asc(table.createdAt),
1777
+ desc(table.assets),
1778
+ asc(table.hash)
1779
+ )
1684
1780
  ]
1685
1781
  );
1686
1782
  var offerCollaterals = s.table(
@@ -1754,6 +1850,7 @@ var availableLiquidityQueues = s.table(
1754
1850
  queueId: varchar("queue_id", { length: 255 }).notNull(),
1755
1851
  availableLiquidityPoolId: varchar("available_liquidity_pool_id", { length: 255 }).notNull().references(() => availableLiquidityPools.id, { onDelete: "cascade" }),
1756
1852
  index: integer("index").notNull(),
1853
+ callbackAmount: numeric("callback_amount", { precision: 78, scale: 0 }).default("0").notNull(),
1757
1854
  updatedAt: timestamp("updated_at").defaultNow().notNull()
1758
1855
  },
1759
1856
  (table) => [
@@ -1898,6 +1995,7 @@ var create2 = (config) => {
1898
1995
  queueId: qp.queue.queueId,
1899
1996
  availableLiquidityPoolId: qp.pool.id,
1900
1997
  index: qp.queue.index,
1998
+ callbackAmount: qp.queue.callbackAmount,
1901
1999
  updatedAt: /* @__PURE__ */ new Date()
1902
2000
  }).onConflictDoUpdate({
1903
2001
  target: [
@@ -1906,6 +2004,7 @@ var create2 = (config) => {
1906
2004
  ],
1907
2005
  set: {
1908
2006
  index: qp.queue.index,
2007
+ callbackAmount: qp.queue.callbackAmount,
1909
2008
  updatedAt: /* @__PURE__ */ new Date()
1910
2009
  }
1911
2010
  });
@@ -1984,6 +2083,7 @@ function memory2() {
1984
2083
  queueId: qid,
1985
2084
  availableLiquidityPoolId: qp.pool.id,
1986
2085
  index: qp.queue.index,
2086
+ callbackAmount: qp.queue.callbackAmount,
1987
2087
  updatedAt: /* @__PURE__ */ new Date()
1988
2088
  });
1989
2089
  if (!queueIndexByQueueId.has(qid)) queueIndexByQueueId.set(qid, /* @__PURE__ */ new Set());
@@ -2497,7 +2597,8 @@ function create3(config) {
2497
2597
  signature: offers.signature,
2498
2598
  callbackId: offers.callbackId,
2499
2599
  status: latestStatus.status,
2500
- metadata: latestStatus.metadata
2600
+ metadata: latestStatus.metadata,
2601
+ createdAt: offers.createdAt
2501
2602
  }
2502
2603
  ).from(offers).leftJoinLateral(latestStatus, sql`true`).leftJoinLateral(sumConsumed, sql`true`).where(
2503
2604
  and(
@@ -2552,6 +2653,7 @@ function create3(config) {
2552
2653
  callbackId: bestOffers.callbackId,
2553
2654
  status: bestOffers.status,
2554
2655
  metadata: bestOffers.metadata,
2656
+ createdAt: bestOffers.createdAt,
2555
2657
  // liquidity caps
2556
2658
  userAmount: sql`COALESCE(${queueLiquidity.userAmount}, 0)`.as("user_amount"),
2557
2659
  queueLiquidity: sql`COALESCE(${queueLiquidity.queueLiquidity}, 0)`.as(
@@ -2561,7 +2663,7 @@ function create3(config) {
2561
2663
  cumulativeRemaining: sql`
2562
2664
  SUM(${bestOffers.remaining}) OVER (
2563
2665
  PARTITION BY ${bestOffers.callbackId}
2564
- ORDER BY ${sortExpr}, ${asc(bestOffers.hash)}
2666
+ ORDER BY ${sortExpr}, ${asc(bestOffers.createdAt)}, ${bestOffers.assets} DESC, ${asc(bestOffers.hash)}
2565
2667
  ROWS UNBOUNDED PRECEDING
2566
2668
  )
2567
2669
  `.as("cumulative_remaining"),
@@ -2571,7 +2673,7 @@ function create3(config) {
2571
2673
  WHEN ${bestOffers.remaining} <= 0 THEN false
2572
2674
  ELSE ( SUM(${bestOffers.remaining}) OVER (
2573
2675
  PARTITION BY ${bestOffers.callbackId}
2574
- ORDER BY ${sortExpr}, ${asc(bestOffers.hash)}
2676
+ ORDER BY ${sortExpr}, ${asc(bestOffers.createdAt)}, ${bestOffers.assets} DESC, ${asc(bestOffers.hash)}
2575
2677
  ROWS UNBOUNDED PRECEDING
2576
2678
  )
2577
2679
  <= LEAST(
@@ -2605,6 +2707,7 @@ function create3(config) {
2605
2707
  callbackId: offersWithEligibility.callbackId,
2606
2708
  status: offersWithEligibility.status,
2607
2709
  metadata: offersWithEligibility.metadata,
2710
+ createdAt: offersWithEligibility.createdAt,
2608
2711
  userAmount: offersWithEligibility.userAmount,
2609
2712
  queueLiquidity: offersWithEligibility.queueLiquidity,
2610
2713
  cumulativeRemaining: offersWithEligibility.cumulativeRemaining,
@@ -2614,6 +2717,8 @@ function create3(config) {
2614
2717
  ROW_NUMBER() OVER (
2615
2718
  ORDER BY
2616
2719
  CASE WHEN ${offersWithEligibility.buy} THEN ${offersWithEligibility.rate} ELSE -${offersWithEligibility.rate} END,
2720
+ ${offersWithEligibility.createdAt},
2721
+ ${offersWithEligibility.assets} DESC,
2617
2722
  ${asc(offersWithEligibility.hash)}
2618
2723
  )
2619
2724
  `.as("row_number")
@@ -2655,6 +2760,10 @@ function create3(config) {
2655
2760
  )
2656
2761
  ).orderBy(
2657
2762
  rateSortDirection === "asc" ? asc(validOffers.rate) : desc(validOffers.rate),
2763
+ asc(validOffers.createdAt),
2764
+ // earlier createdAt first
2765
+ desc(validOffers.assets),
2766
+ // higher assets first
2658
2767
  asc(validOffers.hash)
2659
2768
  );
2660
2769
  const buildOffersMap = (rows, skipHash) => {