@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.
@@ -741,46 +741,95 @@ var WhitelistedCallbackAddresses = {
741
741
  "0x1111111111111111111111111111111111111111",
742
742
  "0x2222222222222222222222222222222222222222"
743
743
  // @TODO: update once deployed and add mapping per chain if needed
744
- ]
744
+ ].map((address) => address.toLowerCase())
745
745
  };
746
746
  function buildLiquidity(parameters) {
747
- const { type, user, contract, chainId, amount, index: index2 = 0, updatedAt = /* @__PURE__ */ new Date() } = parameters;
748
- if (type !== "buy_with_empty_callback" /* BuyWithEmptyCallback */)
749
- throw new Error(`CallbackType not implemented: ${type}`);
750
- const amountStr = amount.toString();
751
- const id = `${user}-${chainId.toString()}-${type}-${contract}`.toLowerCase();
752
- return {
753
- userPosition: {
754
- id,
755
- availableLiquidityQueueId: id,
756
- user: user.toLowerCase(),
757
- chainId,
758
- amount: amountStr,
759
- updatedAt
760
- },
761
- queues: [
762
- {
763
- queue: {
764
- queueId: id,
765
- availableLiquidityPoolId: id,
766
- index: index2,
747
+ switch (parameters.type) {
748
+ case "buy_with_empty_callback" /* BuyWithEmptyCallback */: {
749
+ const { user, loanToken, chainId, amount, index: index2 = 0, updatedAt = /* @__PURE__ */ new Date() } = parameters;
750
+ const amountStr = amount.toString();
751
+ const id = `${user}-${chainId.toString()}-${parameters.type}-${loanToken}`.toLowerCase();
752
+ const poolId = `${user}-${chainId.toString()}-${loanToken}`.toLowerCase();
753
+ return {
754
+ userPosition: {
755
+ id,
756
+ availableLiquidityQueueId: id,
757
+ user: user.toLowerCase(),
758
+ chainId,
759
+ amount: amountStr,
767
760
  updatedAt
768
761
  },
769
- pool: {
762
+ queues: [
763
+ {
764
+ queue: {
765
+ queueId: id,
766
+ availableLiquidityPoolId: poolId,
767
+ index: index2,
768
+ callbackAmount: "0",
769
+ updatedAt
770
+ },
771
+ pool: {
772
+ id: poolId,
773
+ amount: amountStr,
774
+ updatedAt
775
+ }
776
+ }
777
+ ]
778
+ };
779
+ }
780
+ case "sell_withdraw_from_wallet" /* SellWithdrawFromWallet */: {
781
+ const {
782
+ user,
783
+ termId,
784
+ chainId,
785
+ amount,
786
+ collaterals,
787
+ index: index2 = 0,
788
+ updatedAt = /* @__PURE__ */ new Date()
789
+ } = parameters;
790
+ const amountStr = amount.toString();
791
+ const id = `${user}-${chainId.toString()}-${parameters.type}-${termId}`.toLowerCase();
792
+ return {
793
+ userPosition: {
770
794
  id,
795
+ availableLiquidityQueueId: id,
796
+ user: user.toLowerCase(),
797
+ chainId,
771
798
  amount: amountStr,
772
799
  updatedAt
773
- }
774
- }
775
- ]
776
- };
800
+ },
801
+ queues: collaterals.map((collateral) => {
802
+ const poolId = `${user}-${chainId.toString()}-${collateral.collateralAddress}`.toLowerCase();
803
+ return {
804
+ queue: {
805
+ queueId: id,
806
+ availableLiquidityPoolId: poolId,
807
+ index: index2,
808
+ callbackAmount: collateral.callbackAmount.toString(),
809
+ updatedAt
810
+ },
811
+ pool: {
812
+ id: poolId,
813
+ amount: collateral.balance.toString(),
814
+ updatedAt
815
+ }
816
+ };
817
+ })
818
+ };
819
+ }
820
+ default: {
821
+ throw new Error(`CallbackType not implemented`);
822
+ }
823
+ }
777
824
  }
778
825
  function getCallbackIdForOffer(offer) {
779
826
  if (offer.buy && offer.callback.data === "0x") {
780
- const type = "buy_with_empty_callback" /* BuyWithEmptyCallback */;
781
- const user = offer.offering;
782
- const loanToken = offer.loanToken;
783
- return `${user}-${offer.chainId.toString()}-${type}-${loanToken}`.toLowerCase();
827
+ return `${offer.offering}-${offer.chainId.toString()}-${"buy_with_empty_callback" /* BuyWithEmptyCallback */}-${offer.loanToken}`.toLowerCase();
828
+ }
829
+ if (!offer.buy && offer.callback.data !== "0x" && WhitelistedCallbackAddresses["sell_withdraw_from_wallet" /* SellWithdrawFromWallet */].includes(
830
+ offer.callback.address.toLowerCase()
831
+ )) {
832
+ return `${offer.offering}-${offer.chainId.toString()}-${"sell_withdraw_from_wallet" /* SellWithdrawFromWallet */}-${mempool.Offer.termId(offer)}`.toLowerCase();
784
833
  }
785
834
  return null;
786
835
  }
@@ -900,20 +949,20 @@ async function fetch2(parameters) {
900
949
  const map = await fetchBalancesAndAllowances({
901
950
  client,
902
951
  spender,
903
- pairs: pairs.map(({ user, contract }) => ({ user, token: contract })),
952
+ pairs: pairs.map(({ user, loanToken }) => ({ user, token: loanToken })),
904
953
  options
905
954
  });
906
955
  const out = [];
907
- for (const [user, perContract] of map) {
908
- for (const [contract, { balance, allowance }] of perContract) {
956
+ for (const [user, perLoanToken] of map) {
957
+ for (const [loanToken, { balance, allowance }] of perLoanToken) {
909
958
  const amount = balance < allowance ? balance : allowance;
910
959
  out.push(
911
960
  buildLiquidity({
912
961
  type,
913
962
  user,
914
- contract,
963
+ loanToken,
915
964
  chainId,
916
- amount: amount.toString(),
965
+ amount,
917
966
  index: 0
918
967
  })
919
968
  );
@@ -971,16 +1020,16 @@ __export(Logger_exports, {
971
1020
  silentLogger: () => silentLogger
972
1021
  });
973
1022
  var LogLevelValues = [
974
- "silent",
975
1023
  "trace",
976
1024
  "debug",
977
1025
  "info",
978
1026
  "warn",
979
1027
  "error",
980
- "fatal"
1028
+ "fatal",
1029
+ "silent"
981
1030
  ];
982
1031
  function defaultLogger(minLevel) {
983
- const threshold = minLevel ?? "info";
1032
+ const threshold = minLevel ?? process.env.ROUTER_LOG_LEVEL ?? "info";
984
1033
  const levelIndexByName = LogLevelValues.reduce(
985
1034
  (acc, lvl, idx) => {
986
1035
  acc[lvl] = idx;
@@ -1076,7 +1125,7 @@ function createBuyWithEmptyCallbackLiquidityCollector(params) {
1076
1125
  chainId: chain.id,
1077
1126
  spender: chain.morpho,
1078
1127
  type: "buy_with_empty_callback" /* BuyWithEmptyCallback */,
1079
- pairs: pairs.map(({ user, token }) => ({ user, contract: token })),
1128
+ pairs: pairs.map(({ user, token }) => ({ user, loanToken: token })),
1080
1129
  options: {
1081
1130
  blockNumber: latestBlockNumber,
1082
1131
  batchSize: maxBatchSize
@@ -1127,14 +1176,15 @@ function createBuyWithEmptyCallbackLiquidityCollector(params) {
1127
1176
  };
1128
1177
  }
1129
1178
  function createChainReorgsCollector(parameters) {
1130
- const collectorName = "chain_reorgs";
1179
+ const collector = "chain_reorgs";
1131
1180
  const {
1132
1181
  client,
1133
1182
  subscribers,
1134
1183
  collectorStore,
1135
1184
  chain,
1136
- options: { maxBatchSize = 25, interval } = {}
1185
+ options: { maxBatchSize = 25, interval = 3e4, maxBlockNumber } = {}
1137
1186
  } = parameters;
1187
+ const maxBlockNumberBI = maxBlockNumber !== void 0 ? BigInt(maxBlockNumber) : void 0;
1138
1188
  let finalizedBlock = null;
1139
1189
  let unfinalizedBlocks = [];
1140
1190
  const commonAncestor = (block) => {
@@ -1143,67 +1193,79 @@ function createChainReorgsCollector(parameters) {
1143
1193
  if (finalizedBlock == null) throw new Error("Failed to get common ancestor");
1144
1194
  return finalizedBlock;
1145
1195
  };
1146
- const reconcile = async (block) => {
1147
- if (block.hash === null || block.number === null || block.parentHash === null)
1148
- throw new Error("Failed to get block");
1149
- const latestBlock = unfinalizedBlocks[unfinalizedBlocks.length - 1];
1150
- if (latestBlock === void 0) {
1151
- unfinalizedBlocks.push({
1196
+ const collect = mempool.Utils.lazy((emit, { stop }) => {
1197
+ const logger = getLogger();
1198
+ const reconcile = async (block) => {
1199
+ if (block.hash === null || block.number === null || block.parentHash === null)
1200
+ throw new Error("Failed to get block");
1201
+ const latestBlock = unfinalizedBlocks[unfinalizedBlocks.length - 1];
1202
+ if (latestBlock === void 0) {
1203
+ const newBlock2 = {
1204
+ hash: block.hash,
1205
+ number: block.number,
1206
+ parentHash: block.parentHash
1207
+ };
1208
+ unfinalizedBlocks.push(newBlock2);
1209
+ return newBlock2;
1210
+ }
1211
+ if (latestBlock.hash === block.hash) return latestBlock;
1212
+ if (latestBlock.number >= block.number) {
1213
+ const ancestor = commonAncestor(block);
1214
+ logger.info({
1215
+ collector,
1216
+ chainId: chain.id,
1217
+ msg: `reorg detected, latestBlock.number: ${latestBlock.number} >= block.number: ${block.number} on chain ${chain.id}. Ancestor: ${ancestor.number}`
1218
+ });
1219
+ subscribers.forEach((subscriber) => subscriber.onReorg(Number(ancestor.number)));
1220
+ unfinalizedBlocks = [];
1221
+ return ancestor;
1222
+ }
1223
+ if (latestBlock.number + 1n < block.number) {
1224
+ logger.debug({
1225
+ collector,
1226
+ chainId: chain.id,
1227
+ msg: `missing blocks between block ${latestBlock.number} and block ${block.number} on chain ${chain.id}`
1228
+ });
1229
+ const missingBlockNumbers = (() => {
1230
+ const missingBlockNumbers2 = [];
1231
+ let start2 = latestBlock.number + 1n;
1232
+ const threshold = latestBlock.number + BigInt(maxBatchSize) > block.number ? block.number : latestBlock.number + BigInt(maxBatchSize);
1233
+ while (start2 < threshold) {
1234
+ missingBlockNumbers2.push(start2);
1235
+ start2 = start2 + 1n;
1236
+ }
1237
+ return missingBlockNumbers2;
1238
+ })();
1239
+ const missingBlocks = await Promise.all(
1240
+ missingBlockNumbers.map(
1241
+ (blockNumber) => client.getBlock({ blockNumber, includeTransactions: false })
1242
+ )
1243
+ );
1244
+ for (const missingBlock of missingBlocks) {
1245
+ const returnedBlock = await reconcile(missingBlock);
1246
+ if (returnedBlock.number !== missingBlock.number) return returnedBlock;
1247
+ }
1248
+ return reconcile(block);
1249
+ }
1250
+ if (block.parentHash !== latestBlock.hash) {
1251
+ const ancestor = commonAncestor(block);
1252
+ logger.info({
1253
+ collector,
1254
+ chainId: chain.id,
1255
+ msg: `reorg detected, block.parentHash: ${block.parentHash} !== latestBlock.hash: ${latestBlock.hash} on chain ${chain.id}. Ancestor: ${ancestor.number}`
1256
+ });
1257
+ subscribers.forEach((subscriber) => subscriber.onReorg(Number(ancestor.number)));
1258
+ unfinalizedBlocks = [];
1259
+ return ancestor;
1260
+ }
1261
+ const newBlock = {
1152
1262
  hash: block.hash,
1153
1263
  number: block.number,
1154
1264
  parentHash: block.parentHash
1155
- });
1156
- return;
1157
- }
1158
- if (latestBlock.hash === block.hash) return;
1159
- if (latestBlock.number >= block.number) {
1160
- const ancestor = commonAncestor(block);
1161
- console.log(
1162
- `reorg detected, latestBlock.number: ${latestBlock.number} > block.number: ${block.number} on chain ${chain.id}. Ancestor: ${ancestor.number}`
1163
- );
1164
- subscribers.forEach((subscriber) => subscriber.onReorg(Number(ancestor.number)));
1165
- unfinalizedBlocks = [];
1166
- return;
1167
- }
1168
- if (latestBlock.number + 1n < block.number) {
1169
- console.log(
1170
- `missing blocks between block ${latestBlock.number} and block ${block.number} on chain ${chain.id}`
1171
- );
1172
- const missingBlockNumbers = (() => {
1173
- const missingBlockNumbers2 = [];
1174
- let start2 = latestBlock.number + 1n;
1175
- const threshold = latestBlock.number + BigInt(maxBatchSize) > block.number ? block.number : latestBlock.number + BigInt(maxBatchSize);
1176
- while (start2 < threshold) {
1177
- missingBlockNumbers2.push(start2);
1178
- start2 = start2 + 1n;
1179
- }
1180
- return missingBlockNumbers2;
1181
- })();
1182
- const missingBlocks = await Promise.all(
1183
- missingBlockNumbers.map(
1184
- (blockNumber) => client.getBlock({ blockNumber, includeTransactions: false })
1185
- )
1186
- );
1187
- for (const missingBlock of missingBlocks) await reconcile(missingBlock);
1188
- await reconcile(block);
1189
- return;
1190
- }
1191
- if (block.parentHash !== latestBlock.hash) {
1192
- const ancestor = commonAncestor(block);
1193
- console.log(
1194
- `reorg detected, block.parentHash: ${block.parentHash} !== latestBlock.hash: ${latestBlock.hash} on chain ${chain.id}. Ancestor: ${ancestor.number}`
1195
- );
1196
- subscribers.forEach((subscriber) => subscriber.onReorg(Number(ancestor.number)));
1197
- unfinalizedBlocks = [];
1198
- return;
1199
- }
1200
- unfinalizedBlocks.push({
1201
- hash: block.hash,
1202
- number: block.number,
1203
- parentHash: block.parentHash
1204
- });
1205
- };
1206
- const collect = mempool.Utils.lazy((emit) => {
1265
+ };
1266
+ unfinalizedBlocks.push(newBlock);
1267
+ return newBlock;
1268
+ };
1207
1269
  let tick = 0;
1208
1270
  const fetchFinalizedBlock = async () => {
1209
1271
  if (tick % 20 === 0 || finalizedBlock === null) {
@@ -1211,32 +1273,58 @@ function createChainReorgsCollector(parameters) {
1211
1273
  blockTag: "finalized",
1212
1274
  includeTransactions: false
1213
1275
  });
1214
- if (finalizedBlock === null) throw new Error("Failed to get finalized block");
1276
+ if (finalizedBlock === null) {
1277
+ const msg = "Failed to get finalized block";
1278
+ logger.fatal({ collector, chainId: chain.id, msg });
1279
+ throw new Error(msg);
1280
+ }
1281
+ unfinalizedBlocks = unfinalizedBlocks.filter((b) => b.number >= finalizedBlock.number);
1215
1282
  }
1216
1283
  tick++;
1217
1284
  };
1218
- return mempool.Utils.poll(
1285
+ let isMaxBlockNumberReached = false;
1286
+ const unpoll = mempool.Utils.poll(
1219
1287
  async () => {
1220
- await fetchFinalizedBlock();
1221
- const latestBlock = await client.getBlock({
1288
+ if (isMaxBlockNumberReached) {
1289
+ stop();
1290
+ return;
1291
+ }
1292
+ const head = await client.getBlock({
1222
1293
  blockTag: "latest",
1223
1294
  includeTransactions: false
1224
1295
  });
1225
- await reconcile(latestBlock);
1226
- const lastBlockNumber = Number(latestBlock.number);
1296
+ if (maxBlockNumberBI !== void 0 && head.number >= maxBlockNumberBI) {
1297
+ logger.info({
1298
+ collector,
1299
+ chainId: chain.id,
1300
+ msg: `head is greater than max block number, head.number: ${head.number} > maxBlockNumber: ${maxBlockNumber} on chain ${chain.id}.`
1301
+ });
1302
+ isMaxBlockNumberReached = true;
1303
+ subscribers.forEach((subscriber) => subscriber.onReorg(maxBlockNumber));
1304
+ await collectorStore.saveBlockNumber({
1305
+ collectorName: collector,
1306
+ chainId: chain.id,
1307
+ blockNumber: maxBlockNumber
1308
+ });
1309
+ emit(maxBlockNumber);
1310
+ return;
1311
+ }
1312
+ await fetchFinalizedBlock();
1313
+ const blockNumber = Number((await reconcile(head)).number);
1227
1314
  await collectorStore.saveBlockNumber({
1228
- collectorName: "chain_reorgs",
1315
+ collectorName: collector,
1229
1316
  chainId: chain.id,
1230
- blockNumber: lastBlockNumber
1317
+ blockNumber
1231
1318
  });
1232
- emit(lastBlockNumber);
1319
+ emit(blockNumber);
1233
1320
  },
1234
1321
  { interval: interval ?? 3e4 }
1235
1322
  );
1323
+ return unpoll;
1236
1324
  });
1237
1325
  return {
1238
- name: collectorName,
1239
- lastSyncedBlock: async () => await collectorStore.getBlockNumber({ collectorName, chainId: chain.id }),
1326
+ name: collector,
1327
+ lastSyncedBlock: async () => await collectorStore.getBlockNumber({ collectorName: collector, chainId: chain.id }),
1240
1328
  collect,
1241
1329
  onReorg: (_) => {
1242
1330
  }
@@ -1681,11 +1769,19 @@ var offers = s.table(
1681
1769
  pgCore.index("offers_expiry_idx").on(table.expiry),
1682
1770
  pgCore.index("offers_rate_idx").on(table.rate),
1683
1771
  pgCore.index("offers_assets_idx").on(table.assets),
1772
+ pgCore.index("offers_created_at_idx").on(table.createdAt),
1684
1773
  // Compound indices for cursor pagination with hash
1685
1774
  pgCore.index("offers_rate_hash_idx").on(table.rate, table.hash),
1686
1775
  pgCore.index("offers_maturity_hash_idx").on(table.maturity, table.hash),
1687
1776
  pgCore.index("offers_expiry_hash_idx").on(table.expiry, table.hash),
1688
- pgCore.index("offers_assets_hash_idx").on(table.assets, table.hash)
1777
+ pgCore.index("offers_assets_hash_idx").on(table.assets, table.hash),
1778
+ // Compound index for multi-level sorting optimization (rate, createdAt, assets, hash)
1779
+ pgCore.index("offers_rate_created_at_assets_hash_idx").on(
1780
+ table.rate,
1781
+ drizzleOrm.asc(table.createdAt),
1782
+ drizzleOrm.desc(table.assets),
1783
+ drizzleOrm.asc(table.hash)
1784
+ )
1689
1785
  ]
1690
1786
  );
1691
1787
  var offerCollaterals = s.table(
@@ -1759,6 +1855,7 @@ var availableLiquidityQueues = s.table(
1759
1855
  queueId: pgCore.varchar("queue_id", { length: 255 }).notNull(),
1760
1856
  availableLiquidityPoolId: pgCore.varchar("available_liquidity_pool_id", { length: 255 }).notNull().references(() => availableLiquidityPools.id, { onDelete: "cascade" }),
1761
1857
  index: pgCore.integer("index").notNull(),
1858
+ callbackAmount: pgCore.numeric("callback_amount", { precision: 78, scale: 0 }).default("0").notNull(),
1762
1859
  updatedAt: pgCore.timestamp("updated_at").defaultNow().notNull()
1763
1860
  },
1764
1861
  (table) => [
@@ -1903,6 +2000,7 @@ var create2 = (config) => {
1903
2000
  queueId: qp.queue.queueId,
1904
2001
  availableLiquidityPoolId: qp.pool.id,
1905
2002
  index: qp.queue.index,
2003
+ callbackAmount: qp.queue.callbackAmount,
1906
2004
  updatedAt: /* @__PURE__ */ new Date()
1907
2005
  }).onConflictDoUpdate({
1908
2006
  target: [
@@ -1911,6 +2009,7 @@ var create2 = (config) => {
1911
2009
  ],
1912
2010
  set: {
1913
2011
  index: qp.queue.index,
2012
+ callbackAmount: qp.queue.callbackAmount,
1914
2013
  updatedAt: /* @__PURE__ */ new Date()
1915
2014
  }
1916
2015
  });
@@ -1989,6 +2088,7 @@ function memory2() {
1989
2088
  queueId: qid,
1990
2089
  availableLiquidityPoolId: qp.pool.id,
1991
2090
  index: qp.queue.index,
2091
+ callbackAmount: qp.queue.callbackAmount,
1992
2092
  updatedAt: /* @__PURE__ */ new Date()
1993
2093
  });
1994
2094
  if (!queueIndexByQueueId.has(qid)) queueIndexByQueueId.set(qid, /* @__PURE__ */ new Set());
@@ -2502,7 +2602,8 @@ function create3(config) {
2502
2602
  signature: offers.signature,
2503
2603
  callbackId: offers.callbackId,
2504
2604
  status: latestStatus.status,
2505
- metadata: latestStatus.metadata
2605
+ metadata: latestStatus.metadata,
2606
+ createdAt: offers.createdAt
2506
2607
  }
2507
2608
  ).from(offers).leftJoinLateral(latestStatus, drizzleOrm.sql`true`).leftJoinLateral(sumConsumed, drizzleOrm.sql`true`).where(
2508
2609
  drizzleOrm.and(
@@ -2557,6 +2658,7 @@ function create3(config) {
2557
2658
  callbackId: bestOffers.callbackId,
2558
2659
  status: bestOffers.status,
2559
2660
  metadata: bestOffers.metadata,
2661
+ createdAt: bestOffers.createdAt,
2560
2662
  // liquidity caps
2561
2663
  userAmount: drizzleOrm.sql`COALESCE(${queueLiquidity.userAmount}, 0)`.as("user_amount"),
2562
2664
  queueLiquidity: drizzleOrm.sql`COALESCE(${queueLiquidity.queueLiquidity}, 0)`.as(
@@ -2566,7 +2668,7 @@ function create3(config) {
2566
2668
  cumulativeRemaining: drizzleOrm.sql`
2567
2669
  SUM(${bestOffers.remaining}) OVER (
2568
2670
  PARTITION BY ${bestOffers.callbackId}
2569
- ORDER BY ${sortExpr}, ${drizzleOrm.asc(bestOffers.hash)}
2671
+ ORDER BY ${sortExpr}, ${drizzleOrm.asc(bestOffers.createdAt)}, ${bestOffers.assets} DESC, ${drizzleOrm.asc(bestOffers.hash)}
2570
2672
  ROWS UNBOUNDED PRECEDING
2571
2673
  )
2572
2674
  `.as("cumulative_remaining"),
@@ -2576,7 +2678,7 @@ function create3(config) {
2576
2678
  WHEN ${bestOffers.remaining} <= 0 THEN false
2577
2679
  ELSE ( SUM(${bestOffers.remaining}) OVER (
2578
2680
  PARTITION BY ${bestOffers.callbackId}
2579
- ORDER BY ${sortExpr}, ${drizzleOrm.asc(bestOffers.hash)}
2681
+ ORDER BY ${sortExpr}, ${drizzleOrm.asc(bestOffers.createdAt)}, ${bestOffers.assets} DESC, ${drizzleOrm.asc(bestOffers.hash)}
2580
2682
  ROWS UNBOUNDED PRECEDING
2581
2683
  )
2582
2684
  <= LEAST(
@@ -2610,6 +2712,7 @@ function create3(config) {
2610
2712
  callbackId: offersWithEligibility.callbackId,
2611
2713
  status: offersWithEligibility.status,
2612
2714
  metadata: offersWithEligibility.metadata,
2715
+ createdAt: offersWithEligibility.createdAt,
2613
2716
  userAmount: offersWithEligibility.userAmount,
2614
2717
  queueLiquidity: offersWithEligibility.queueLiquidity,
2615
2718
  cumulativeRemaining: offersWithEligibility.cumulativeRemaining,
@@ -2619,6 +2722,8 @@ function create3(config) {
2619
2722
  ROW_NUMBER() OVER (
2620
2723
  ORDER BY
2621
2724
  CASE WHEN ${offersWithEligibility.buy} THEN ${offersWithEligibility.rate} ELSE -${offersWithEligibility.rate} END,
2725
+ ${offersWithEligibility.createdAt},
2726
+ ${offersWithEligibility.assets} DESC,
2622
2727
  ${drizzleOrm.asc(offersWithEligibility.hash)}
2623
2728
  )
2624
2729
  `.as("row_number")
@@ -2660,6 +2765,10 @@ function create3(config) {
2660
2765
  )
2661
2766
  ).orderBy(
2662
2767
  rateSortDirection === "asc" ? drizzleOrm.asc(validOffers.rate) : drizzleOrm.desc(validOffers.rate),
2768
+ drizzleOrm.asc(validOffers.createdAt),
2769
+ // earlier createdAt first
2770
+ drizzleOrm.desc(validOffers.assets),
2771
+ // higher assets first
2663
2772
  drizzleOrm.asc(validOffers.hash)
2664
2773
  );
2665
2774
  const buildOffersMap = (rows, skipHash) => {