@morpho-dev/router 0.1.8 → 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.
@@ -1,7 +1,7 @@
1
1
  import { Errors, LLTV, Utils, Offer, Format } from '@morpho-dev/mempool';
2
2
  export * from '@morpho-dev/mempool';
3
+ import { parseUnits, encodeAbiParameters, maxUint256, formatUnits, decodeAbiParameters, erc20Abi } from 'viem';
3
4
  import { Base64 } from 'js-base64';
4
- import { parseUnits, maxUint256, formatUnits, erc20Abi } from 'viem';
5
5
  import { z } from 'zod/v4';
6
6
  import { createDocument } from 'zod-openapi';
7
7
 
@@ -17,13 +17,25 @@ var __publicField = (obj, key, value) => __defNormalProp(obj, key + "" , value);
17
17
  var Callback_exports = {};
18
18
  __export(Callback_exports, {
19
19
  CallbackType: () => CallbackType,
20
+ WhitelistedCallbackAddresses: () => WhitelistedCallbackAddresses,
20
21
  buildLiquidity: () => buildLiquidity,
22
+ decode: () => decode,
23
+ encode: () => encode,
21
24
  getCallbackIdForOffer: () => getCallbackIdForOffer
22
25
  });
23
26
  var CallbackType = /* @__PURE__ */ ((CallbackType2) => {
24
27
  CallbackType2["BuyWithEmptyCallback"] = "buy_with_empty_callback";
28
+ CallbackType2["SellWithdrawFromWallet"] = "sell_withdraw_from_wallet";
25
29
  return CallbackType2;
26
30
  })(CallbackType || {});
31
+ var WhitelistedCallbackAddresses = {
32
+ ["buy_with_empty_callback" /* BuyWithEmptyCallback */]: [],
33
+ ["sell_withdraw_from_wallet" /* SellWithdrawFromWallet */]: [
34
+ "0x1111111111111111111111111111111111111111",
35
+ "0x2222222222222222222222222222222222222222"
36
+ // @TODO: update once deployed and add mapping per chain if needed
37
+ ]
38
+ };
27
39
  function buildLiquidity(parameters) {
28
40
  const { type, user, contract, chainId, amount, index = 0, updatedAt = /* @__PURE__ */ new Date() } = parameters;
29
41
  if (type !== "buy_with_empty_callback" /* BuyWithEmptyCallback */)
@@ -65,12 +77,44 @@ function getCallbackIdForOffer(offer) {
65
77
  }
66
78
  return null;
67
79
  }
80
+ function decodeSellWithdrawFromWalletData(data) {
81
+ if (!data || data === "0x") throw new Error("Empty callback data");
82
+ try {
83
+ const [collaterals, amounts] = decodeAbiParameters(
84
+ [{ type: "address[]" }, { type: "uint256[]" }],
85
+ data
86
+ );
87
+ if (collaterals.length !== amounts.length) {
88
+ throw new Error("Mismatched array lengths");
89
+ }
90
+ return collaterals.map((c, i) => ({ collateral: c, amount: amounts[i] }));
91
+ } catch (_) {
92
+ throw new Error("Invalid SellWithdrawFromWallet callback data");
93
+ }
94
+ }
95
+ function decode(parameters) {
96
+ const { type, data } = parameters;
97
+ if (type === "sell_withdraw_from_wallet" /* SellWithdrawFromWallet */) {
98
+ return decodeSellWithdrawFromWalletData(data);
99
+ }
100
+ throw new Error(`CallbackType not implemented: ${type}`);
101
+ }
102
+ function encode(parameters) {
103
+ const { type, data } = parameters;
104
+ if (type === "sell_withdraw_from_wallet" /* SellWithdrawFromWallet */) {
105
+ return encodeAbiParameters(
106
+ [{ type: "address[]" }, { type: "uint256[]" }],
107
+ [data.collaterals, data.amounts]
108
+ );
109
+ }
110
+ throw new Error(`CallbackType not implemented: ${type}`);
111
+ }
68
112
 
69
113
  // src/core/Cursor.ts
70
114
  var Cursor_exports = {};
71
115
  __export(Cursor_exports, {
72
- decode: () => decode,
73
- encode: () => encode,
116
+ decode: () => decode2,
117
+ encode: () => encode2,
74
118
  validate: () => validate
75
119
  });
76
120
  function validate(cursor) {
@@ -142,10 +186,10 @@ function validate(cursor) {
142
186
  }
143
187
  return true;
144
188
  }
145
- function encode(c) {
189
+ function encode2(c) {
146
190
  return Base64.encodeURL(JSON.stringify(c));
147
191
  }
148
- function decode(token) {
192
+ function decode2(token) {
149
193
  if (!token) return null;
150
194
  const decoded = JSON.parse(Base64.decode(token));
151
195
  validate(decoded);
@@ -554,7 +598,7 @@ var GetOffersQueryParams = z.object({
554
598
  (val) => {
555
599
  if (!val) return true;
556
600
  try {
557
- const decoded = decode(val);
601
+ const decoded = decode2(val);
558
602
  return decoded !== null;
559
603
  } catch (_error) {
560
604
  return false;
@@ -726,7 +770,7 @@ var MatchOffersQueryParams = z.object({
726
770
  (val) => {
727
771
  if (!val) return true;
728
772
  try {
729
- const decoded = decode(val);
773
+ const decoded = decode2(val);
730
774
  return decoded !== null;
731
775
  } catch (_error) {
732
776
  return false;
@@ -1202,18 +1246,89 @@ function morpho() {
1202
1246
  return { message: "Expiry mismatch" };
1203
1247
  }
1204
1248
  });
1205
- const callback = single("empty_callback", (offer, _) => {
1206
- if (!offer.buy || offer.callback.data !== "0x") {
1207
- return { message: "Callback not supported yet." };
1249
+ const sellEmptyCallback = single(
1250
+ "sell_offers_empty_callback",
1251
+ (offer, _) => {
1252
+ if (!offer.buy && offer.callback.data === "0x") {
1253
+ return { message: "Sell offers require a non-empty callback." };
1254
+ }
1208
1255
  }
1209
- });
1256
+ );
1257
+ const buyNonEmptyCallback = single(
1258
+ "buy_offers_non_empty_callback",
1259
+ (offer, _) => {
1260
+ if (offer.buy && offer.callback.data !== "0x") {
1261
+ return { message: "Buy offers must use an empty callback." };
1262
+ }
1263
+ }
1264
+ );
1265
+ const sellNonWhitelistedCallback = single(
1266
+ "sell_offers_non_whitelisted_callback",
1267
+ (offer, _) => {
1268
+ if (!offer.buy && offer.callback.data !== "0x") {
1269
+ const allowed = new Set(
1270
+ WhitelistedCallbackAddresses["sell_withdraw_from_wallet" /* SellWithdrawFromWallet */].map(
1271
+ (a) => a.toLowerCase()
1272
+ )
1273
+ );
1274
+ const callbackAddress = offer.callback.address?.toLowerCase();
1275
+ if (!callbackAddress || !allowed.has(callbackAddress)) {
1276
+ return { message: "Sell offer callback address is not whitelisted." };
1277
+ }
1278
+ }
1279
+ }
1280
+ );
1281
+ const sellCallbackDataInvalid = single(
1282
+ "sell_offers_callback_data_invalid",
1283
+ (offer, _) => {
1284
+ if (!offer.buy && offer.callback.data !== "0x") {
1285
+ try {
1286
+ const decoded = decode({
1287
+ type: "sell_withdraw_from_wallet" /* SellWithdrawFromWallet */,
1288
+ data: offer.callback.data
1289
+ });
1290
+ if (decoded.length === 0) {
1291
+ return { message: "Sell offer callback data must include at least one collateral." };
1292
+ }
1293
+ } catch (_2) {
1294
+ return { message: "Sell offer callback data cannot be decoded." };
1295
+ }
1296
+ }
1297
+ }
1298
+ );
1299
+ const sellCallbackCollateralInvalid = single(
1300
+ "sell_offers_callback_collateral_invalid",
1301
+ (offer, _) => {
1302
+ if (!offer.buy && offer.callback.data !== "0x") {
1303
+ try {
1304
+ const decoded = decode({
1305
+ type: "sell_withdraw_from_wallet" /* SellWithdrawFromWallet */,
1306
+ data: offer.callback.data
1307
+ });
1308
+ const offerCollaterals = new Set(
1309
+ offer.collaterals.map((c) => c.asset.toLowerCase())
1310
+ );
1311
+ for (const { collateral } of decoded) {
1312
+ if (!offerCollaterals.has(collateral.toLowerCase())) {
1313
+ return { message: "Sell callback collateral is not part of offer collaterals." };
1314
+ }
1315
+ }
1316
+ } catch (_2) {
1317
+ }
1318
+ }
1319
+ }
1320
+ );
1210
1321
  return [
1211
1322
  chainId,
1212
1323
  loanToken,
1213
1324
  expiry,
1214
- // note: callback rule should be the last one, since it does not mean that the offer is forever invalid
1325
+ // note: callback rules should be the last ones, since they do not mean that the offer is forever invalid
1215
1326
  // integrators should be able to choose if they want to keep the offer or not
1216
- callback
1327
+ sellEmptyCallback,
1328
+ buyNonEmptyCallback,
1329
+ sellNonWhitelistedCallback,
1330
+ sellCallbackDataInvalid,
1331
+ sellCallbackCollateralInvalid
1217
1332
  ];
1218
1333
  }
1219
1334