@morpho-org/bundler-sdk-viem 3.0.0-next.9 → 3.0.0

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.
package/lib/actions.js CHANGED
@@ -5,6 +5,7 @@ import { getCurrent, simulateOperation, } from "@morpho-org/simulation-sdk";
5
5
  import { blueAbi, getAuthorizationTypedData, getDaiPermitTypedData, getPermit2PermitTypedData, getPermitTypedData, } from "@morpho-org/blue-sdk-viem";
6
6
  import { signTypedData } from "viem/actions";
7
7
  import { ActionBundle, ActionBundleRequirements } from "./ActionBundle.js";
8
+ import { BundlerErrors } from "./errors.js";
8
9
  export const APPROVE_ONLY_ONCE_TOKENS = {
9
10
  [ChainId.EthMainnet]: [
10
11
  "0xdAC17F958D2ee523a2206206994597C13D831ec7", // USDT
@@ -92,6 +93,9 @@ export const encodeOperation = (operation, dataBefore, supportsSignature = true,
92
93
  switch (operation.type) {
93
94
  case "Blue_SetAuthorization": {
94
95
  const { owner, isAuthorized, authorized } = operation.args;
96
+ // Never authorize bundler3 otherwise the signature can be used independently.
97
+ if (authorized === bundler3)
98
+ throw new BundlerErrors.UnexpectedSignature(authorized);
95
99
  if (supportsSignature) {
96
100
  const ownerData = dataBefore.getUser(owner);
97
101
  const authorization = {
@@ -103,7 +107,7 @@ export const encodeOperation = (operation, dataBefore, supportsSignature = true,
103
107
  };
104
108
  const action = {
105
109
  type: "morphoSetAuthorizationWithSig",
106
- args: [authorization, null],
110
+ args: [authorization, null, operation.skipRevert],
107
111
  };
108
112
  actions.push(action);
109
113
  requirements.signatures.push({
@@ -158,15 +162,33 @@ export const encodeOperation = (operation, dataBefore, supportsSignature = true,
158
162
  if (address === NATIVE_ADDRESS)
159
163
  break;
160
164
  const { amount, spender, nonce } = operation.args;
165
+ // Never permit any other address than the GeneralAdapter1 otherwise
166
+ // the signature can be used independently.
167
+ if (spender !== generalAdapter1)
168
+ throw new BundlerErrors.UnexpectedSignature(spender);
161
169
  if (supportsSignature) {
162
170
  const action = address === dai
163
171
  ? {
164
172
  type: "permitDai",
165
- args: [sender, nonce, deadline, true, null, spender],
173
+ args: [
174
+ sender,
175
+ nonce,
176
+ deadline,
177
+ true,
178
+ null,
179
+ operation.skipRevert,
180
+ ],
166
181
  }
167
182
  : {
168
183
  type: "permit",
169
- args: [sender, address, amount, deadline, null, spender],
184
+ args: [
185
+ sender,
186
+ address,
187
+ amount,
188
+ deadline,
189
+ null,
190
+ operation.skipRevert,
191
+ ],
170
192
  };
171
193
  actions.push(action);
172
194
  const tokenData = dataBefore.getToken(address);
@@ -239,22 +261,24 @@ export const encodeOperation = (operation, dataBefore, supportsSignature = true,
239
261
  nonce: Number(nonce),
240
262
  expiration: Number(expiration),
241
263
  },
242
- spender: bundler3,
243
264
  sigDeadline: deadline,
244
265
  },
245
266
  null,
267
+ operation.skipRevert,
246
268
  ],
247
269
  };
248
270
  actions.push(action);
249
271
  requirements.signatures.push({
250
272
  action,
251
273
  async sign(client, account = client.account) {
252
- const { details, spender, sigDeadline } = action.args[1];
274
+ const { details, sigDeadline } = action.args[1];
253
275
  let signature = action.args[2];
254
276
  if (signature != null)
255
277
  return signature; // action is already signed
256
278
  const typedData = getPermit2PermitTypedData({
257
- spender,
279
+ // Never permit any other address than the GeneralAdapter1 otherwise
280
+ // the signature can be used independently.
281
+ spender: generalAdapter1,
258
282
  allowance: details.amount,
259
283
  erc20: details.token,
260
284
  nonce: details.nonce,
@@ -284,7 +308,7 @@ export const encodeOperation = (operation, dataBefore, supportsSignature = true,
284
308
  if (address === NATIVE_ADDRESS) {
285
309
  actions.push({
286
310
  type: "nativeTransfer",
287
- args: [from, to, amount],
311
+ args: [from, to, amount, operation.skipRevert],
288
312
  });
289
313
  break;
290
314
  }
@@ -292,29 +316,29 @@ export const encodeOperation = (operation, dataBefore, supportsSignature = true,
292
316
  if (from === generalAdapter1) {
293
317
  actions.push({
294
318
  type: "erc20Transfer",
295
- args: [address, to, amount],
319
+ args: [address, to, amount, generalAdapter1, operation.skipRevert],
296
320
  });
297
321
  break;
298
322
  }
299
323
  actions.push({
300
324
  type: "erc20TransferFrom",
301
- args: [address, amount, to],
325
+ args: [address, amount, to, operation.skipRevert],
302
326
  });
303
327
  break;
304
328
  }
305
329
  case "Erc20_Transfer2": {
306
- const { amount, from, to } = operation.args;
330
+ const { amount, to } = operation.args;
307
331
  if (supportsSignature) {
308
332
  actions.push({
309
333
  type: "transferFrom2",
310
- args: [address, from, amount, to],
334
+ args: [address, amount, to, operation.skipRevert],
311
335
  });
312
336
  break;
313
337
  }
314
338
  // Signatures are not supported, fallback to standard transfer.
315
339
  actions.push({
316
340
  type: "erc20TransferFrom",
317
- args: [address, amount, to],
341
+ args: [address, amount, to, operation.skipRevert],
318
342
  });
319
343
  break;
320
344
  }
@@ -324,21 +348,27 @@ export const encodeOperation = (operation, dataBefore, supportsSignature = true,
324
348
  case wNative: {
325
349
  actions.push({
326
350
  type: "wrapNative",
327
- args: [amount],
351
+ args: [amount, generalAdapter1, operation.skipRevert],
328
352
  });
329
353
  break;
330
354
  }
331
355
  case wstEth: {
332
356
  actions.push({
333
357
  type: "wrapStEth",
334
- args: [amount],
358
+ args: [amount, generalAdapter1, operation.skipRevert],
335
359
  });
336
360
  break;
337
361
  }
338
362
  case stEth: {
339
363
  actions.push({
340
364
  type: "stakeEth",
341
- args: [amount, MathLib.MAX_UINT_256, zeroAddress],
365
+ args: [
366
+ amount,
367
+ MathLib.MAX_UINT_256,
368
+ zeroAddress,
369
+ generalAdapter1,
370
+ operation.skipRevert,
371
+ ],
342
372
  });
343
373
  break;
344
374
  }
@@ -349,7 +379,7 @@ export const encodeOperation = (operation, dataBefore, supportsSignature = true,
349
379
  throw Error(`unknown wrapped token: ${address}`);
350
380
  actions.push({
351
381
  type: "erc20WrapperDepositFor",
352
- args: [address, underlying, amount],
382
+ args: [address, underlying, amount, operation.skipRevert],
353
383
  });
354
384
  break;
355
385
  }
@@ -366,14 +396,14 @@ export const encodeOperation = (operation, dataBefore, supportsSignature = true,
366
396
  case wNative: {
367
397
  actions.push({
368
398
  type: "unwrapNative",
369
- args: [amount],
399
+ args: [amount, generalAdapter1, operation.skipRevert],
370
400
  });
371
401
  break;
372
402
  }
373
403
  case wstEth: {
374
404
  actions.push({
375
405
  type: "unwrapStEth",
376
- args: [amount],
406
+ args: [amount, generalAdapter1, operation.skipRevert],
377
407
  });
378
408
  break;
379
409
  }
@@ -382,7 +412,7 @@ export const encodeOperation = (operation, dataBefore, supportsSignature = true,
382
412
  throw Error(`unexpected token unwrap: ${address}`);
383
413
  actions.push({
384
414
  type: "erc20WrapperWithdrawTo",
385
- args: [address, receiver, amount],
415
+ args: [address, receiver, amount, operation.skipRevert],
386
416
  });
387
417
  }
388
418
  }
@@ -390,7 +420,10 @@ export const encodeOperation = (operation, dataBefore, supportsSignature = true,
390
420
  }
391
421
  case "Blue_Supply": {
392
422
  const { id, assets = 0n, shares = 0n, onBehalf, slippage = DEFAULT_SLIPPAGE_TOLERANCE, } = operation.args;
393
- const market = dataBefore.getMarket(id);
423
+ // Accrue interest to calculate the expected share price.
424
+ const market = dataBefore
425
+ .getMarket(id)
426
+ .accrueInterest(dataBefore.block.timestamp);
394
427
  const maxSharePrice = market.toSupplyAssets(MathLib.wToRay(MathLib.WAD + slippage));
395
428
  actions.push({
396
429
  type: "morphoSupply",
@@ -401,33 +434,57 @@ export const encodeOperation = (operation, dataBefore, supportsSignature = true,
401
434
  maxSharePrice,
402
435
  onBehalf,
403
436
  callbackBundle?.actions ?? [],
437
+ operation.skipRevert,
404
438
  ],
405
439
  });
406
440
  break;
407
441
  }
408
442
  case "Blue_Withdraw": {
409
443
  const { id, assets = 0n, shares = 0n, receiver, slippage = DEFAULT_SLIPPAGE_TOLERANCE, } = operation.args;
410
- const market = dataBefore.getMarket(id);
444
+ // Accrue interest to calculate the expected share price.
445
+ const market = dataBefore
446
+ .getMarket(id)
447
+ .accrueInterest(dataBefore.block.timestamp);
411
448
  const minSharePrice = market.toSupplyAssets(MathLib.wToRay(MathLib.WAD - slippage));
412
449
  actions.push({
413
450
  type: "morphoWithdraw",
414
- args: [market.params, assets, shares, minSharePrice, receiver],
451
+ args: [
452
+ market.params,
453
+ assets,
454
+ shares,
455
+ minSharePrice,
456
+ receiver,
457
+ operation.skipRevert,
458
+ ],
415
459
  });
416
460
  break;
417
461
  }
418
462
  case "Blue_Borrow": {
419
463
  const { id, assets = 0n, shares = 0n, receiver, slippage = DEFAULT_SLIPPAGE_TOLERANCE, } = operation.args;
420
- const market = dataBefore.getMarket(id);
464
+ // Accrue interest to calculate the expected share price.
465
+ const market = dataBefore
466
+ .getMarket(id)
467
+ .accrueInterest(dataBefore.block.timestamp);
421
468
  const minSharePrice = market.toBorrowAssets(MathLib.wToRay(MathLib.WAD - slippage));
422
469
  actions.push({
423
470
  type: "morphoBorrow",
424
- args: [market.params, assets, shares, minSharePrice, receiver],
471
+ args: [
472
+ market.params,
473
+ assets,
474
+ shares,
475
+ minSharePrice,
476
+ receiver,
477
+ operation.skipRevert,
478
+ ],
425
479
  });
426
480
  break;
427
481
  }
428
482
  case "Blue_Repay": {
429
483
  const { id, assets = 0n, shares = 0n, onBehalf, slippage = DEFAULT_SLIPPAGE_TOLERANCE, } = operation.args;
430
- const market = dataBefore.getMarket(id);
484
+ // Accrue interest to calculate the expected share price.
485
+ const market = dataBefore
486
+ .getMarket(id)
487
+ .accrueInterest(dataBefore.block.timestamp);
431
488
  const maxSharePrice = market.toBorrowAssets(MathLib.wToRay(MathLib.WAD + slippage));
432
489
  actions.push({
433
490
  type: "morphoRepay",
@@ -438,6 +495,7 @@ export const encodeOperation = (operation, dataBefore, supportsSignature = true,
438
495
  maxSharePrice,
439
496
  onBehalf,
440
497
  callbackBundle?.actions ?? [],
498
+ operation.skipRevert,
441
499
  ],
442
500
  });
443
501
  break;
@@ -451,13 +509,24 @@ export const encodeOperation = (operation, dataBefore, supportsSignature = true,
451
509
  throw Error(`unknown wrapped token: ${address}`);
452
510
  actions.push({
453
511
  type: "erc20WrapperDepositFor",
454
- args: [params.collateralToken, underlying, assets],
512
+ args: [
513
+ params.collateralToken,
514
+ underlying,
515
+ assets,
516
+ operation.skipRevert,
517
+ ],
455
518
  });
456
519
  break;
457
520
  }
458
521
  actions.push({
459
522
  type: "morphoSupplyCollateral",
460
- args: [params, assets, onBehalf, callbackBundle?.actions ?? []],
523
+ args: [
524
+ params,
525
+ assets,
526
+ onBehalf,
527
+ callbackBundle?.actions ?? [],
528
+ operation.skipRevert,
529
+ ],
461
530
  });
462
531
  break;
463
532
  }
@@ -466,39 +535,59 @@ export const encodeOperation = (operation, dataBefore, supportsSignature = true,
466
535
  const { params } = dataBefore.getMarket(id);
467
536
  actions.push({
468
537
  type: "morphoWithdrawCollateral",
469
- args: [params, assets, receiver],
538
+ args: [params, assets, receiver, operation.skipRevert],
470
539
  });
471
540
  break;
472
541
  }
473
542
  case "MetaMorpho_Deposit": {
474
543
  const { assets = 0n, shares = 0n, owner, slippage = DEFAULT_SLIPPAGE_TOLERANCE, } = operation.args;
475
- const vault = dataBefore.getVault(address);
544
+ // Accrue interest to calculate the expected share price.
545
+ const vault = dataBefore
546
+ .getAccrualVault(address)
547
+ .accrueInterest(dataBefore.block.timestamp);
476
548
  const maxSharePrice = vault.toAssets(MathLib.wToRay(MathLib.WAD + slippage));
477
549
  if (shares === 0n)
478
550
  actions.push({
479
551
  type: "erc4626Deposit",
480
- args: [address, assets, maxSharePrice, owner],
552
+ args: [address, assets, maxSharePrice, owner, operation.skipRevert],
481
553
  });
482
554
  else
483
555
  actions.push({
484
556
  type: "erc4626Mint",
485
- args: [address, shares, maxSharePrice, owner],
557
+ args: [address, shares, maxSharePrice, owner, operation.skipRevert],
486
558
  });
487
559
  break;
488
560
  }
489
561
  case "MetaMorpho_Withdraw": {
490
562
  const { assets = 0n, shares = 0n, owner, receiver, slippage = DEFAULT_SLIPPAGE_TOLERANCE, } = operation.args;
491
- const vault = dataBefore.getVault(address);
563
+ // Accrue interest to calculate the expected share price.
564
+ const vault = dataBefore
565
+ .getAccrualVault(address)
566
+ .accrueInterest(dataBefore.block.timestamp);
492
567
  const minSharePrice = vault.toAssets(MathLib.wToRay(MathLib.WAD - slippage));
493
568
  if (assets > 0n)
494
569
  actions.push({
495
570
  type: "erc4626Withdraw",
496
- args: [address, assets, minSharePrice, receiver, owner],
571
+ args: [
572
+ address,
573
+ assets,
574
+ minSharePrice,
575
+ receiver,
576
+ owner,
577
+ operation.skipRevert,
578
+ ],
497
579
  });
498
580
  else
499
581
  actions.push({
500
582
  type: "erc4626Redeem",
501
- args: [address, shares, minSharePrice, receiver, owner],
583
+ args: [
584
+ address,
585
+ shares,
586
+ minSharePrice,
587
+ receiver,
588
+ owner,
589
+ operation.skipRevert,
590
+ ],
502
591
  });
503
592
  break;
504
593
  }
@@ -516,6 +605,7 @@ export const encodeOperation = (operation, dataBefore, supportsSignature = true,
516
605
  amount: assets,
517
606
  })),
518
607
  dataBefore.getMarket(supplyMarketId).params,
608
+ operation.skipRevert,
519
609
  ],
520
610
  });
521
611
  break;
package/lib/errors.d.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  import type { SimulationResult } from "@morpho-org/simulation-sdk";
2
+ import type { Address } from "viem";
2
3
  import type { ActionType, InputBundlerOperation } from "./types/index.js";
3
4
  export declare namespace BundlerErrors {
4
5
  class Bundle extends Error {
@@ -14,4 +15,7 @@ export declare namespace BundlerErrors {
14
15
  class UnexpectedAction extends Error {
15
16
  constructor(type: ActionType, chainId: number);
16
17
  }
18
+ class UnexpectedSignature extends Error {
19
+ constructor(spender: Address);
20
+ }
17
21
  }
package/lib/errors.js CHANGED
@@ -27,4 +27,10 @@ export var BundlerErrors;
27
27
  }
28
28
  }
29
29
  BundlerErrors.UnexpectedAction = UnexpectedAction;
30
+ class UnexpectedSignature extends Error {
31
+ constructor(spender) {
32
+ super(`unexpected signature consumer "${spender}"`);
33
+ }
34
+ }
35
+ BundlerErrors.UnexpectedSignature = UnexpectedSignature;
30
36
  })(BundlerErrors || (BundlerErrors = {}));
package/lib/operations.js CHANGED
@@ -8,7 +8,7 @@ import { BundlerErrors } from "./errors.js";
8
8
  */
9
9
  export const DEFAULT_SUPPLY_TARGET_UTILIZATION = 905000000000000000n;
10
10
  export const populateInputTransfer = ({ address, args: { amount, from } }, data, { hasSimplePermit = false } = {}) => {
11
- const { bundler3: { bundler3, generalAdapter1 }, permit2, } = getChainAddresses(data.chainId);
11
+ const { bundler3: { generalAdapter1 }, permit2, } = getChainAddresses(data.chainId);
12
12
  // If native token, it is expected to be sent along as call value.
13
13
  if (address === NATIVE_ADDRESS)
14
14
  return [
@@ -105,7 +105,7 @@ export const populateInputTransfer = ({ address, args: { amount, from } }, data,
105
105
  });
106
106
  operations.push({
107
107
  type: "Erc20_Transfer2",
108
- sender: bundler3,
108
+ sender: generalAdapter1,
109
109
  address,
110
110
  args: {
111
111
  amount,
@@ -216,29 +216,32 @@ export const populateSubBundle = (inputOperation, data, options = {}) => {
216
216
  ? MathLib.MAX_UINT_160
217
217
  : MathLib.wDivDown(newTotalBorrowAssets, supplyTargetUtilization) -
218
218
  newTotalSupplyAssets;
219
- let { withdrawals, data: simulationStatePostFriendlyReallocation } = data.getMarketPublicReallocations(market.id, publicAllocatorOptions);
220
- const marketPostFriendlyReallocation = simulationStatePostFriendlyReallocation.getMarket(market.id);
221
- if (marketPostFriendlyReallocation.totalBorrowAssets + borrowedAssets >
222
- marketPostFriendlyReallocation.totalSupplyAssets - withdrawnAssets) {
219
+ const { withdrawals, data: friendlyReallocationData } = data.getMarketPublicReallocations(market.id, publicAllocatorOptions);
220
+ const friendlyReallocationMarket = friendlyReallocationData.getMarket(market.id);
221
+ if (friendlyReallocationMarket.totalBorrowAssets + borrowedAssets >
222
+ friendlyReallocationMarket.totalSupplyAssets - withdrawnAssets) {
223
223
  // If the "friendly" reallocations are not enough, we fully withdraw from every market.
224
224
  requiredAssets = newTotalBorrowAssets - newTotalSupplyAssets;
225
- ({ withdrawals } = data.getMarketPublicReallocations(market.id, {
225
+ withdrawals.push(...friendlyReallocationData.getMarketPublicReallocations(market.id, {
226
226
  ...publicAllocatorOptions,
227
227
  defaultMaxWithdrawalUtilization: MathLib.WAD,
228
228
  maxWithdrawalUtilization: {},
229
- }));
229
+ }).withdrawals);
230
230
  }
231
231
  for (const { vault, ...withdrawal } of withdrawals) {
232
232
  const vaultReallocations = (reallocations[vault] ??= []);
233
- if (withdrawal.assets > requiredAssets) {
233
+ const vaultMarketReallocation = vaultReallocations.find((item) => item.id === withdrawal.id);
234
+ const reallocatedAssets = MathLib.min(withdrawal.assets, requiredAssets);
235
+ if (vaultMarketReallocation != null)
236
+ vaultMarketReallocation.assets += reallocatedAssets;
237
+ else
234
238
  vaultReallocations.push({
235
239
  ...withdrawal,
236
- assets: requiredAssets,
240
+ assets: reallocatedAssets,
237
241
  });
242
+ requiredAssets -= reallocatedAssets;
243
+ if (requiredAssets === 0n)
238
244
  break;
239
- }
240
- requiredAssets -= withdrawal.assets;
241
- vaultReallocations.push(withdrawal);
242
245
  }
243
246
  // TODO: we know there are no unwrap native in the middle
244
247
  // of the bundle so we are certain we need to add an input transfer.