@morpho-dev/router 0.1.9 → 0.1.10

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.
@@ -2,7 +2,7 @@ import { Errors, LLTV, Offer, Format, Utils, Chain, Maturity, Time, Mempool } fr
2
2
  export * from '@morpho-dev/mempool';
3
3
  import { z } from 'zod/v4';
4
4
  import { createDocument } from 'zod-openapi';
5
- import { parseUnits, maxUint256, publicActions, parseEventLogs, formatUnits, createWalletClient, http, erc20Abi, stringify } from 'viem';
5
+ import { parseUnits, maxUint256, encodeAbiParameters, publicActions, parseEventLogs, formatUnits, createWalletClient, http, decodeAbiParameters, erc20Abi, stringify } from 'viem';
6
6
  import { Base64 } from 'js-base64';
7
7
  import { getBlockNumber } from 'viem/actions';
8
8
  import { AsyncLocalStorage } from 'async_hooks';
@@ -719,13 +719,25 @@ function fromResponse(offerResponse) {
719
719
  var Callback_exports = {};
720
720
  __export(Callback_exports, {
721
721
  CallbackType: () => CallbackType,
722
+ WhitelistedCallbackAddresses: () => WhitelistedCallbackAddresses,
722
723
  buildLiquidity: () => buildLiquidity,
724
+ decode: () => decode2,
725
+ encode: () => encode2,
723
726
  getCallbackIdForOffer: () => getCallbackIdForOffer
724
727
  });
725
728
  var CallbackType = /* @__PURE__ */ ((CallbackType2) => {
726
729
  CallbackType2["BuyWithEmptyCallback"] = "buy_with_empty_callback";
730
+ CallbackType2["SellWithdrawFromWallet"] = "sell_withdraw_from_wallet";
727
731
  return CallbackType2;
728
732
  })(CallbackType || {});
733
+ var WhitelistedCallbackAddresses = {
734
+ ["buy_with_empty_callback" /* BuyWithEmptyCallback */]: [],
735
+ ["sell_withdraw_from_wallet" /* SellWithdrawFromWallet */]: [
736
+ "0x1111111111111111111111111111111111111111",
737
+ "0x2222222222222222222222222222222222222222"
738
+ // @TODO: update once deployed and add mapping per chain if needed
739
+ ]
740
+ };
729
741
  function buildLiquidity(parameters) {
730
742
  const { type, user, contract, chainId, amount, index: index2 = 0, updatedAt = /* @__PURE__ */ new Date() } = parameters;
731
743
  if (type !== "buy_with_empty_callback" /* BuyWithEmptyCallback */)
@@ -767,6 +779,38 @@ function getCallbackIdForOffer(offer) {
767
779
  }
768
780
  return null;
769
781
  }
782
+ function decodeSellWithdrawFromWalletData(data) {
783
+ if (!data || data === "0x") throw new Error("Empty callback data");
784
+ try {
785
+ const [collaterals, amounts] = decodeAbiParameters(
786
+ [{ type: "address[]" }, { type: "uint256[]" }],
787
+ data
788
+ );
789
+ if (collaterals.length !== amounts.length) {
790
+ throw new Error("Mismatched array lengths");
791
+ }
792
+ return collaterals.map((c, i) => ({ collateral: c, amount: amounts[i] }));
793
+ } catch (_) {
794
+ throw new Error("Invalid SellWithdrawFromWallet callback data");
795
+ }
796
+ }
797
+ function decode2(parameters) {
798
+ const { type, data } = parameters;
799
+ if (type === "sell_withdraw_from_wallet" /* SellWithdrawFromWallet */) {
800
+ return decodeSellWithdrawFromWalletData(data);
801
+ }
802
+ throw new Error(`CallbackType not implemented: ${type}`);
803
+ }
804
+ function encode2(parameters) {
805
+ const { type, data } = parameters;
806
+ if (type === "sell_withdraw_from_wallet" /* SellWithdrawFromWallet */) {
807
+ return encodeAbiParameters(
808
+ [{ type: "address[]" }, { type: "uint256[]" }],
809
+ [data.collaterals, data.amounts]
810
+ );
811
+ }
812
+ throw new Error(`CallbackType not implemented: ${type}`);
813
+ }
770
814
 
771
815
  // src/core/Collector/index.ts
772
816
  var Collector_exports = {};
@@ -1386,18 +1430,89 @@ function morpho() {
1386
1430
  return { message: "Expiry mismatch" };
1387
1431
  }
1388
1432
  });
1389
- const callback = single("empty_callback", (offer, _) => {
1390
- if (!offer.buy || offer.callback.data !== "0x") {
1391
- return { message: "Callback not supported yet." };
1433
+ const sellEmptyCallback = single(
1434
+ "sell_offers_empty_callback",
1435
+ (offer, _) => {
1436
+ if (!offer.buy && offer.callback.data === "0x") {
1437
+ return { message: "Sell offers require a non-empty callback." };
1438
+ }
1392
1439
  }
1393
- });
1440
+ );
1441
+ const buyNonEmptyCallback = single(
1442
+ "buy_offers_non_empty_callback",
1443
+ (offer, _) => {
1444
+ if (offer.buy && offer.callback.data !== "0x") {
1445
+ return { message: "Buy offers must use an empty callback." };
1446
+ }
1447
+ }
1448
+ );
1449
+ const sellNonWhitelistedCallback = single(
1450
+ "sell_offers_non_whitelisted_callback",
1451
+ (offer, _) => {
1452
+ if (!offer.buy && offer.callback.data !== "0x") {
1453
+ const allowed = new Set(
1454
+ WhitelistedCallbackAddresses["sell_withdraw_from_wallet" /* SellWithdrawFromWallet */].map(
1455
+ (a) => a.toLowerCase()
1456
+ )
1457
+ );
1458
+ const callbackAddress = offer.callback.address?.toLowerCase();
1459
+ if (!callbackAddress || !allowed.has(callbackAddress)) {
1460
+ return { message: "Sell offer callback address is not whitelisted." };
1461
+ }
1462
+ }
1463
+ }
1464
+ );
1465
+ const sellCallbackDataInvalid = single(
1466
+ "sell_offers_callback_data_invalid",
1467
+ (offer, _) => {
1468
+ if (!offer.buy && offer.callback.data !== "0x") {
1469
+ try {
1470
+ const decoded = decode2({
1471
+ type: "sell_withdraw_from_wallet" /* SellWithdrawFromWallet */,
1472
+ data: offer.callback.data
1473
+ });
1474
+ if (decoded.length === 0) {
1475
+ return { message: "Sell offer callback data must include at least one collateral." };
1476
+ }
1477
+ } catch (_2) {
1478
+ return { message: "Sell offer callback data cannot be decoded." };
1479
+ }
1480
+ }
1481
+ }
1482
+ );
1483
+ const sellCallbackCollateralInvalid = single(
1484
+ "sell_offers_callback_collateral_invalid",
1485
+ (offer, _) => {
1486
+ if (!offer.buy && offer.callback.data !== "0x") {
1487
+ try {
1488
+ const decoded = decode2({
1489
+ type: "sell_withdraw_from_wallet" /* SellWithdrawFromWallet */,
1490
+ data: offer.callback.data
1491
+ });
1492
+ const offerCollaterals2 = new Set(
1493
+ offer.collaterals.map((c) => c.asset.toLowerCase())
1494
+ );
1495
+ for (const { collateral } of decoded) {
1496
+ if (!offerCollaterals2.has(collateral.toLowerCase())) {
1497
+ return { message: "Sell callback collateral is not part of offer collaterals." };
1498
+ }
1499
+ }
1500
+ } catch (_2) {
1501
+ }
1502
+ }
1503
+ }
1504
+ );
1394
1505
  return [
1395
1506
  chainId,
1396
1507
  loanToken,
1397
1508
  expiry,
1398
- // note: callback rule should be the last one, since it does not mean that the offer is forever invalid
1509
+ // note: callback rules should be the last ones, since they do not mean that the offer is forever invalid
1399
1510
  // integrators should be able to choose if they want to keep the offer or not
1400
- callback
1511
+ sellEmptyCallback,
1512
+ buyNonEmptyCallback,
1513
+ sellNonWhitelistedCallback,
1514
+ sellCallbackDataInvalid,
1515
+ sellCallbackCollateralInvalid
1401
1516
  ];
1402
1517
  }
1403
1518
 
@@ -1446,9 +1561,11 @@ function createMempoolCollector(parameters) {
1446
1561
  });
1447
1562
  const invalidOffersToSave = [];
1448
1563
  const issueToStatus = {
1449
- empty_callback: "callback_not_supported",
1450
1564
  sell_offers_empty_callback: "callback_not_supported",
1451
- buy_offers_empty_callback: "callback_error"
1565
+ buy_offers_non_empty_callback: "callback_not_supported",
1566
+ sell_offers_non_whitelisted_callback: "callback_not_supported",
1567
+ sell_offers_callback_data_invalid: "callback_error",
1568
+ sell_offers_callback_collateral_invalid: "callback_error"
1452
1569
  };
1453
1570
  for (const issue of issues) {
1454
1571
  const status = issueToStatus[issue.ruleName];