@morpho-dev/router 0.1.9 → 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.
@@ -724,54 +724,147 @@ function fromResponse(offerResponse) {
724
724
  var Callback_exports = {};
725
725
  __export(Callback_exports, {
726
726
  CallbackType: () => CallbackType,
727
+ WhitelistedCallbackAddresses: () => WhitelistedCallbackAddresses,
727
728
  buildLiquidity: () => buildLiquidity,
729
+ decode: () => decode2,
730
+ encode: () => encode2,
728
731
  getCallbackIdForOffer: () => getCallbackIdForOffer
729
732
  });
730
733
  var CallbackType = /* @__PURE__ */ ((CallbackType2) => {
731
734
  CallbackType2["BuyWithEmptyCallback"] = "buy_with_empty_callback";
735
+ CallbackType2["SellWithdrawFromWallet"] = "sell_withdraw_from_wallet";
732
736
  return CallbackType2;
733
737
  })(CallbackType || {});
738
+ var WhitelistedCallbackAddresses = {
739
+ ["buy_with_empty_callback" /* BuyWithEmptyCallback */]: [],
740
+ ["sell_withdraw_from_wallet" /* SellWithdrawFromWallet */]: [
741
+ "0x1111111111111111111111111111111111111111",
742
+ "0x2222222222222222222222222222222222222222"
743
+ // @TODO: update once deployed and add mapping per chain if needed
744
+ ].map((address) => address.toLowerCase())
745
+ };
734
746
  function buildLiquidity(parameters) {
735
- const { type, user, contract, chainId, amount, index: index2 = 0, updatedAt = /* @__PURE__ */ new Date() } = parameters;
736
- if (type !== "buy_with_empty_callback" /* BuyWithEmptyCallback */)
737
- throw new Error(`CallbackType not implemented: ${type}`);
738
- const amountStr = amount.toString();
739
- const id = `${user}-${chainId.toString()}-${type}-${contract}`.toLowerCase();
740
- return {
741
- userPosition: {
742
- id,
743
- availableLiquidityQueueId: id,
744
- user: user.toLowerCase(),
745
- chainId,
746
- amount: amountStr,
747
- updatedAt
748
- },
749
- queues: [
750
- {
751
- queue: {
752
- queueId: id,
753
- availableLiquidityPoolId: id,
754
- 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,
755
760
  updatedAt
756
761
  },
757
- 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: {
758
794
  id,
795
+ availableLiquidityQueueId: id,
796
+ user: user.toLowerCase(),
797
+ chainId,
759
798
  amount: amountStr,
760
799
  updatedAt
761
- }
762
- }
763
- ]
764
- };
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
+ }
765
824
  }
766
825
  function getCallbackIdForOffer(offer) {
767
826
  if (offer.buy && offer.callback.data === "0x") {
768
- const type = "buy_with_empty_callback" /* BuyWithEmptyCallback */;
769
- const user = offer.offering;
770
- const loanToken = offer.loanToken;
771
- 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();
772
833
  }
773
834
  return null;
774
835
  }
836
+ function decodeSellWithdrawFromWalletData(data) {
837
+ if (!data || data === "0x") throw new Error("Empty callback data");
838
+ try {
839
+ const [collaterals, amounts] = viem.decodeAbiParameters(
840
+ [{ type: "address[]" }, { type: "uint256[]" }],
841
+ data
842
+ );
843
+ if (collaterals.length !== amounts.length) {
844
+ throw new Error("Mismatched array lengths");
845
+ }
846
+ return collaterals.map((c, i) => ({ collateral: c, amount: amounts[i] }));
847
+ } catch (_) {
848
+ throw new Error("Invalid SellWithdrawFromWallet callback data");
849
+ }
850
+ }
851
+ function decode2(parameters) {
852
+ const { type, data } = parameters;
853
+ if (type === "sell_withdraw_from_wallet" /* SellWithdrawFromWallet */) {
854
+ return decodeSellWithdrawFromWalletData(data);
855
+ }
856
+ throw new Error(`CallbackType not implemented: ${type}`);
857
+ }
858
+ function encode2(parameters) {
859
+ const { type, data } = parameters;
860
+ if (type === "sell_withdraw_from_wallet" /* SellWithdrawFromWallet */) {
861
+ return viem.encodeAbiParameters(
862
+ [{ type: "address[]" }, { type: "uint256[]" }],
863
+ [data.collaterals, data.amounts]
864
+ );
865
+ }
866
+ throw new Error(`CallbackType not implemented: ${type}`);
867
+ }
775
868
 
776
869
  // src/core/Collector/index.ts
777
870
  var Collector_exports = {};
@@ -856,20 +949,20 @@ async function fetch2(parameters) {
856
949
  const map = await fetchBalancesAndAllowances({
857
950
  client,
858
951
  spender,
859
- pairs: pairs.map(({ user, contract }) => ({ user, token: contract })),
952
+ pairs: pairs.map(({ user, loanToken }) => ({ user, token: loanToken })),
860
953
  options
861
954
  });
862
955
  const out = [];
863
- for (const [user, perContract] of map) {
864
- for (const [contract, { balance, allowance }] of perContract) {
956
+ for (const [user, perLoanToken] of map) {
957
+ for (const [loanToken, { balance, allowance }] of perLoanToken) {
865
958
  const amount = balance < allowance ? balance : allowance;
866
959
  out.push(
867
960
  buildLiquidity({
868
961
  type,
869
962
  user,
870
- contract,
963
+ loanToken,
871
964
  chainId,
872
- amount: amount.toString(),
965
+ amount,
873
966
  index: 0
874
967
  })
875
968
  );
@@ -927,16 +1020,16 @@ __export(Logger_exports, {
927
1020
  silentLogger: () => silentLogger
928
1021
  });
929
1022
  var LogLevelValues = [
930
- "silent",
931
1023
  "trace",
932
1024
  "debug",
933
1025
  "info",
934
1026
  "warn",
935
1027
  "error",
936
- "fatal"
1028
+ "fatal",
1029
+ "silent"
937
1030
  ];
938
1031
  function defaultLogger(minLevel) {
939
- const threshold = minLevel ?? "info";
1032
+ const threshold = minLevel ?? process.env.ROUTER_LOG_LEVEL ?? "info";
940
1033
  const levelIndexByName = LogLevelValues.reduce(
941
1034
  (acc, lvl, idx) => {
942
1035
  acc[lvl] = idx;
@@ -1032,7 +1125,7 @@ function createBuyWithEmptyCallbackLiquidityCollector(params) {
1032
1125
  chainId: chain.id,
1033
1126
  spender: chain.morpho,
1034
1127
  type: "buy_with_empty_callback" /* BuyWithEmptyCallback */,
1035
- pairs: pairs.map(({ user, token }) => ({ user, contract: token })),
1128
+ pairs: pairs.map(({ user, token }) => ({ user, loanToken: token })),
1036
1129
  options: {
1037
1130
  blockNumber: latestBlockNumber,
1038
1131
  batchSize: maxBatchSize
@@ -1083,14 +1176,15 @@ function createBuyWithEmptyCallbackLiquidityCollector(params) {
1083
1176
  };
1084
1177
  }
1085
1178
  function createChainReorgsCollector(parameters) {
1086
- const collectorName = "chain_reorgs";
1179
+ const collector = "chain_reorgs";
1087
1180
  const {
1088
1181
  client,
1089
1182
  subscribers,
1090
1183
  collectorStore,
1091
1184
  chain,
1092
- options: { maxBatchSize = 25, interval } = {}
1185
+ options: { maxBatchSize = 25, interval = 3e4, maxBlockNumber } = {}
1093
1186
  } = parameters;
1187
+ const maxBlockNumberBI = maxBlockNumber !== void 0 ? BigInt(maxBlockNumber) : void 0;
1094
1188
  let finalizedBlock = null;
1095
1189
  let unfinalizedBlocks = [];
1096
1190
  const commonAncestor = (block) => {
@@ -1099,67 +1193,79 @@ function createChainReorgsCollector(parameters) {
1099
1193
  if (finalizedBlock == null) throw new Error("Failed to get common ancestor");
1100
1194
  return finalizedBlock;
1101
1195
  };
1102
- const reconcile = async (block) => {
1103
- if (block.hash === null || block.number === null || block.parentHash === null)
1104
- throw new Error("Failed to get block");
1105
- const latestBlock = unfinalizedBlocks[unfinalizedBlocks.length - 1];
1106
- if (latestBlock === void 0) {
1107
- 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 = {
1108
1262
  hash: block.hash,
1109
1263
  number: block.number,
1110
1264
  parentHash: block.parentHash
1111
- });
1112
- return;
1113
- }
1114
- if (latestBlock.hash === block.hash) return;
1115
- if (latestBlock.number >= block.number) {
1116
- const ancestor = commonAncestor(block);
1117
- console.log(
1118
- `reorg detected, latestBlock.number: ${latestBlock.number} > block.number: ${block.number} on chain ${chain.id}. Ancestor: ${ancestor.number}`
1119
- );
1120
- subscribers.forEach((subscriber) => subscriber.onReorg(Number(ancestor.number)));
1121
- unfinalizedBlocks = [];
1122
- return;
1123
- }
1124
- if (latestBlock.number + 1n < block.number) {
1125
- console.log(
1126
- `missing blocks between block ${latestBlock.number} and block ${block.number} on chain ${chain.id}`
1127
- );
1128
- const missingBlockNumbers = (() => {
1129
- const missingBlockNumbers2 = [];
1130
- let start2 = latestBlock.number + 1n;
1131
- const threshold = latestBlock.number + BigInt(maxBatchSize) > block.number ? block.number : latestBlock.number + BigInt(maxBatchSize);
1132
- while (start2 < threshold) {
1133
- missingBlockNumbers2.push(start2);
1134
- start2 = start2 + 1n;
1135
- }
1136
- return missingBlockNumbers2;
1137
- })();
1138
- const missingBlocks = await Promise.all(
1139
- missingBlockNumbers.map(
1140
- (blockNumber) => client.getBlock({ blockNumber, includeTransactions: false })
1141
- )
1142
- );
1143
- for (const missingBlock of missingBlocks) await reconcile(missingBlock);
1144
- await reconcile(block);
1145
- return;
1146
- }
1147
- if (block.parentHash !== latestBlock.hash) {
1148
- const ancestor = commonAncestor(block);
1149
- console.log(
1150
- `reorg detected, block.parentHash: ${block.parentHash} !== latestBlock.hash: ${latestBlock.hash} on chain ${chain.id}. Ancestor: ${ancestor.number}`
1151
- );
1152
- subscribers.forEach((subscriber) => subscriber.onReorg(Number(ancestor.number)));
1153
- unfinalizedBlocks = [];
1154
- return;
1155
- }
1156
- unfinalizedBlocks.push({
1157
- hash: block.hash,
1158
- number: block.number,
1159
- parentHash: block.parentHash
1160
- });
1161
- };
1162
- const collect = mempool.Utils.lazy((emit) => {
1265
+ };
1266
+ unfinalizedBlocks.push(newBlock);
1267
+ return newBlock;
1268
+ };
1163
1269
  let tick = 0;
1164
1270
  const fetchFinalizedBlock = async () => {
1165
1271
  if (tick % 20 === 0 || finalizedBlock === null) {
@@ -1167,32 +1273,58 @@ function createChainReorgsCollector(parameters) {
1167
1273
  blockTag: "finalized",
1168
1274
  includeTransactions: false
1169
1275
  });
1170
- 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);
1171
1282
  }
1172
1283
  tick++;
1173
1284
  };
1174
- return mempool.Utils.poll(
1285
+ let isMaxBlockNumberReached = false;
1286
+ const unpoll = mempool.Utils.poll(
1175
1287
  async () => {
1176
- await fetchFinalizedBlock();
1177
- const latestBlock = await client.getBlock({
1288
+ if (isMaxBlockNumberReached) {
1289
+ stop();
1290
+ return;
1291
+ }
1292
+ const head = await client.getBlock({
1178
1293
  blockTag: "latest",
1179
1294
  includeTransactions: false
1180
1295
  });
1181
- await reconcile(latestBlock);
1182
- 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);
1183
1314
  await collectorStore.saveBlockNumber({
1184
- collectorName: "chain_reorgs",
1315
+ collectorName: collector,
1185
1316
  chainId: chain.id,
1186
- blockNumber: lastBlockNumber
1317
+ blockNumber
1187
1318
  });
1188
- emit(lastBlockNumber);
1319
+ emit(blockNumber);
1189
1320
  },
1190
1321
  { interval: interval ?? 3e4 }
1191
1322
  );
1323
+ return unpoll;
1192
1324
  });
1193
1325
  return {
1194
- name: collectorName,
1195
- lastSyncedBlock: async () => await collectorStore.getBlockNumber({ collectorName, chainId: chain.id }),
1326
+ name: collector,
1327
+ lastSyncedBlock: async () => await collectorStore.getBlockNumber({ collectorName: collector, chainId: chain.id }),
1196
1328
  collect,
1197
1329
  onReorg: (_) => {
1198
1330
  }
@@ -1391,18 +1523,89 @@ function morpho() {
1391
1523
  return { message: "Expiry mismatch" };
1392
1524
  }
1393
1525
  });
1394
- const callback = single("empty_callback", (offer, _) => {
1395
- if (!offer.buy || offer.callback.data !== "0x") {
1396
- return { message: "Callback not supported yet." };
1526
+ const sellEmptyCallback = single(
1527
+ "sell_offers_empty_callback",
1528
+ (offer, _) => {
1529
+ if (!offer.buy && offer.callback.data === "0x") {
1530
+ return { message: "Sell offers require a non-empty callback." };
1531
+ }
1397
1532
  }
1398
- });
1533
+ );
1534
+ const buyNonEmptyCallback = single(
1535
+ "buy_offers_non_empty_callback",
1536
+ (offer, _) => {
1537
+ if (offer.buy && offer.callback.data !== "0x") {
1538
+ return { message: "Buy offers must use an empty callback." };
1539
+ }
1540
+ }
1541
+ );
1542
+ const sellNonWhitelistedCallback = single(
1543
+ "sell_offers_non_whitelisted_callback",
1544
+ (offer, _) => {
1545
+ if (!offer.buy && offer.callback.data !== "0x") {
1546
+ const allowed = new Set(
1547
+ WhitelistedCallbackAddresses["sell_withdraw_from_wallet" /* SellWithdrawFromWallet */].map(
1548
+ (a) => a.toLowerCase()
1549
+ )
1550
+ );
1551
+ const callbackAddress = offer.callback.address?.toLowerCase();
1552
+ if (!callbackAddress || !allowed.has(callbackAddress)) {
1553
+ return { message: "Sell offer callback address is not whitelisted." };
1554
+ }
1555
+ }
1556
+ }
1557
+ );
1558
+ const sellCallbackDataInvalid = single(
1559
+ "sell_offers_callback_data_invalid",
1560
+ (offer, _) => {
1561
+ if (!offer.buy && offer.callback.data !== "0x") {
1562
+ try {
1563
+ const decoded = decode2({
1564
+ type: "sell_withdraw_from_wallet" /* SellWithdrawFromWallet */,
1565
+ data: offer.callback.data
1566
+ });
1567
+ if (decoded.length === 0) {
1568
+ return { message: "Sell offer callback data must include at least one collateral." };
1569
+ }
1570
+ } catch (_2) {
1571
+ return { message: "Sell offer callback data cannot be decoded." };
1572
+ }
1573
+ }
1574
+ }
1575
+ );
1576
+ const sellCallbackCollateralInvalid = single(
1577
+ "sell_offers_callback_collateral_invalid",
1578
+ (offer, _) => {
1579
+ if (!offer.buy && offer.callback.data !== "0x") {
1580
+ try {
1581
+ const decoded = decode2({
1582
+ type: "sell_withdraw_from_wallet" /* SellWithdrawFromWallet */,
1583
+ data: offer.callback.data
1584
+ });
1585
+ const offerCollaterals2 = new Set(
1586
+ offer.collaterals.map((c) => c.asset.toLowerCase())
1587
+ );
1588
+ for (const { collateral } of decoded) {
1589
+ if (!offerCollaterals2.has(collateral.toLowerCase())) {
1590
+ return { message: "Sell callback collateral is not part of offer collaterals." };
1591
+ }
1592
+ }
1593
+ } catch (_2) {
1594
+ }
1595
+ }
1596
+ }
1597
+ );
1399
1598
  return [
1400
1599
  chainId,
1401
1600
  loanToken,
1402
1601
  expiry,
1403
- // note: callback rule should be the last one, since it does not mean that the offer is forever invalid
1602
+ // note: callback rules should be the last ones, since they do not mean that the offer is forever invalid
1404
1603
  // integrators should be able to choose if they want to keep the offer or not
1405
- callback
1604
+ sellEmptyCallback,
1605
+ buyNonEmptyCallback,
1606
+ sellNonWhitelistedCallback,
1607
+ sellCallbackDataInvalid,
1608
+ sellCallbackCollateralInvalid
1406
1609
  ];
1407
1610
  }
1408
1611
 
@@ -1451,9 +1654,11 @@ function createMempoolCollector(parameters) {
1451
1654
  });
1452
1655
  const invalidOffersToSave = [];
1453
1656
  const issueToStatus = {
1454
- empty_callback: "callback_not_supported",
1455
1657
  sell_offers_empty_callback: "callback_not_supported",
1456
- buy_offers_empty_callback: "callback_error"
1658
+ buy_offers_non_empty_callback: "callback_not_supported",
1659
+ sell_offers_non_whitelisted_callback: "callback_not_supported",
1660
+ sell_offers_callback_data_invalid: "callback_error",
1661
+ sell_offers_callback_collateral_invalid: "callback_error"
1457
1662
  };
1458
1663
  for (const issue of issues) {
1459
1664
  const status = issueToStatus[issue.ruleName];
@@ -1564,11 +1769,19 @@ var offers = s.table(
1564
1769
  pgCore.index("offers_expiry_idx").on(table.expiry),
1565
1770
  pgCore.index("offers_rate_idx").on(table.rate),
1566
1771
  pgCore.index("offers_assets_idx").on(table.assets),
1772
+ pgCore.index("offers_created_at_idx").on(table.createdAt),
1567
1773
  // Compound indices for cursor pagination with hash
1568
1774
  pgCore.index("offers_rate_hash_idx").on(table.rate, table.hash),
1569
1775
  pgCore.index("offers_maturity_hash_idx").on(table.maturity, table.hash),
1570
1776
  pgCore.index("offers_expiry_hash_idx").on(table.expiry, table.hash),
1571
- 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
+ )
1572
1785
  ]
1573
1786
  );
1574
1787
  var offerCollaterals = s.table(
@@ -1642,6 +1855,7 @@ var availableLiquidityQueues = s.table(
1642
1855
  queueId: pgCore.varchar("queue_id", { length: 255 }).notNull(),
1643
1856
  availableLiquidityPoolId: pgCore.varchar("available_liquidity_pool_id", { length: 255 }).notNull().references(() => availableLiquidityPools.id, { onDelete: "cascade" }),
1644
1857
  index: pgCore.integer("index").notNull(),
1858
+ callbackAmount: pgCore.numeric("callback_amount", { precision: 78, scale: 0 }).default("0").notNull(),
1645
1859
  updatedAt: pgCore.timestamp("updated_at").defaultNow().notNull()
1646
1860
  },
1647
1861
  (table) => [
@@ -1786,6 +2000,7 @@ var create2 = (config) => {
1786
2000
  queueId: qp.queue.queueId,
1787
2001
  availableLiquidityPoolId: qp.pool.id,
1788
2002
  index: qp.queue.index,
2003
+ callbackAmount: qp.queue.callbackAmount,
1789
2004
  updatedAt: /* @__PURE__ */ new Date()
1790
2005
  }).onConflictDoUpdate({
1791
2006
  target: [
@@ -1794,6 +2009,7 @@ var create2 = (config) => {
1794
2009
  ],
1795
2010
  set: {
1796
2011
  index: qp.queue.index,
2012
+ callbackAmount: qp.queue.callbackAmount,
1797
2013
  updatedAt: /* @__PURE__ */ new Date()
1798
2014
  }
1799
2015
  });
@@ -1872,6 +2088,7 @@ function memory2() {
1872
2088
  queueId: qid,
1873
2089
  availableLiquidityPoolId: qp.pool.id,
1874
2090
  index: qp.queue.index,
2091
+ callbackAmount: qp.queue.callbackAmount,
1875
2092
  updatedAt: /* @__PURE__ */ new Date()
1876
2093
  });
1877
2094
  if (!queueIndexByQueueId.has(qid)) queueIndexByQueueId.set(qid, /* @__PURE__ */ new Set());
@@ -2385,7 +2602,8 @@ function create3(config) {
2385
2602
  signature: offers.signature,
2386
2603
  callbackId: offers.callbackId,
2387
2604
  status: latestStatus.status,
2388
- metadata: latestStatus.metadata
2605
+ metadata: latestStatus.metadata,
2606
+ createdAt: offers.createdAt
2389
2607
  }
2390
2608
  ).from(offers).leftJoinLateral(latestStatus, drizzleOrm.sql`true`).leftJoinLateral(sumConsumed, drizzleOrm.sql`true`).where(
2391
2609
  drizzleOrm.and(
@@ -2440,6 +2658,7 @@ function create3(config) {
2440
2658
  callbackId: bestOffers.callbackId,
2441
2659
  status: bestOffers.status,
2442
2660
  metadata: bestOffers.metadata,
2661
+ createdAt: bestOffers.createdAt,
2443
2662
  // liquidity caps
2444
2663
  userAmount: drizzleOrm.sql`COALESCE(${queueLiquidity.userAmount}, 0)`.as("user_amount"),
2445
2664
  queueLiquidity: drizzleOrm.sql`COALESCE(${queueLiquidity.queueLiquidity}, 0)`.as(
@@ -2449,7 +2668,7 @@ function create3(config) {
2449
2668
  cumulativeRemaining: drizzleOrm.sql`
2450
2669
  SUM(${bestOffers.remaining}) OVER (
2451
2670
  PARTITION BY ${bestOffers.callbackId}
2452
- ORDER BY ${sortExpr}, ${drizzleOrm.asc(bestOffers.hash)}
2671
+ ORDER BY ${sortExpr}, ${drizzleOrm.asc(bestOffers.createdAt)}, ${bestOffers.assets} DESC, ${drizzleOrm.asc(bestOffers.hash)}
2453
2672
  ROWS UNBOUNDED PRECEDING
2454
2673
  )
2455
2674
  `.as("cumulative_remaining"),
@@ -2459,7 +2678,7 @@ function create3(config) {
2459
2678
  WHEN ${bestOffers.remaining} <= 0 THEN false
2460
2679
  ELSE ( SUM(${bestOffers.remaining}) OVER (
2461
2680
  PARTITION BY ${bestOffers.callbackId}
2462
- ORDER BY ${sortExpr}, ${drizzleOrm.asc(bestOffers.hash)}
2681
+ ORDER BY ${sortExpr}, ${drizzleOrm.asc(bestOffers.createdAt)}, ${bestOffers.assets} DESC, ${drizzleOrm.asc(bestOffers.hash)}
2463
2682
  ROWS UNBOUNDED PRECEDING
2464
2683
  )
2465
2684
  <= LEAST(
@@ -2493,6 +2712,7 @@ function create3(config) {
2493
2712
  callbackId: offersWithEligibility.callbackId,
2494
2713
  status: offersWithEligibility.status,
2495
2714
  metadata: offersWithEligibility.metadata,
2715
+ createdAt: offersWithEligibility.createdAt,
2496
2716
  userAmount: offersWithEligibility.userAmount,
2497
2717
  queueLiquidity: offersWithEligibility.queueLiquidity,
2498
2718
  cumulativeRemaining: offersWithEligibility.cumulativeRemaining,
@@ -2502,6 +2722,8 @@ function create3(config) {
2502
2722
  ROW_NUMBER() OVER (
2503
2723
  ORDER BY
2504
2724
  CASE WHEN ${offersWithEligibility.buy} THEN ${offersWithEligibility.rate} ELSE -${offersWithEligibility.rate} END,
2725
+ ${offersWithEligibility.createdAt},
2726
+ ${offersWithEligibility.assets} DESC,
2505
2727
  ${drizzleOrm.asc(offersWithEligibility.hash)}
2506
2728
  )
2507
2729
  `.as("row_number")
@@ -2543,6 +2765,10 @@ function create3(config) {
2543
2765
  )
2544
2766
  ).orderBy(
2545
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
2546
2772
  drizzleOrm.asc(validOffers.hash)
2547
2773
  );
2548
2774
  const buildOffersMap = (rows, skipHash) => {