Using Bundlers with TypeScript
The BundlerAction
module from @morpho-org/morpho-blue-bundlers
provides a comprehensive set of utility functions for encoding various actions that can be bundled together in a single transaction. This tutorial covers the main functions and provides examples of how to use them.
Table of Contents
Installation
First, install the package in your project:
npm install @morpho-org/morpho-blue-bundlers
# or
yarn add @morpho-org/morpho-blue-bundlers
Importing BundlerAction
Import the BundlerAction
module in your TypeScript or JavaScript file:
import { BundlerAction } from "@morpho-org/morpho-blue-bundlers";
Available Actions
All available actions are displayed here:
https://github.com/morpho-org/morpho-blue-bundlers/blob/main/pkg/BundlerAction.ts
ERC20 and Native Token Actions
- nativeTransfer: Transfer native tokens (e.g., ETH, MATIC)
- erc20Transfer: Transfer ERC20 tokens
- erc20TransferFrom: Transfer ERC20 tokens from sender to the Bundler
Example:
const recipient = "0x1234...";
const amount = 1000000000000000000n; // 1 ETH
const nativeTransferAction = BundlerAction.nativeTransfer(recipient, amount);
const tokenAddress = "0x5678...";
const erc20TransferAction = BundlerAction.erc20Transfer(
tokenAddress,
recipient,
amount
);
Permit and Permit2 Actions
- permit: Permit an ERC20 token
- permitDai: Permit DAI token
- approve2: Approve using Permit2
- transferFrom2: Transfer tokens using Permit2
Example:
import { Signature } from "ethers";
const permitSingle = {
details: {
token: "0x1234...",
amount: 1000000000n,
nonce: 0n,
expiration: 2n ** 48n - 1,
},
spender: bundlerAddress,
sigDeadline: 2n ** 48n - 1,
};
const signature = Signature.from("0xabcdef...");
const approveAction = BundlerAction.approve2(permitSingle, signature, false);
Morpho Actions
- morphoSetAuthorizationWithSig: Authorize an account on Morpho
- morphoSupply: Supply assets to a Morpho market
- morphoSupplyCollateral: Supply collateral to a Morpho market
- morphoBorrow: Borrow from a Morpho market
- morphoRepay: Repay a Morpho loan
- morphoWithdraw: Withdraw supplied assets from Morpho
- morphoWithdrawCollateral: Withdraw supplied collateral from Morpho
- morphoFlashLoan: Execute a flash loan on Morpho
- metaMorphoReallocateTo: Trigger a public reallocation on the PublicAllocator
Example:
const marketParams = {
collateralToken: "0x1234...",
loanToken: "0x5678...",
irm: "0xABCD...",
oracle: "0xEFGH...",
lltv: 860000000000000000n, // 86% LLTV
};
const borrowAmount = 100000000n; // 100 USDC (6 decimals)
const borrower = "0x9876...";
const slippageAmount = 99500000n; // 99.50 USDC, minimum amount of assets to borrow
const borrowAction = BundlerAction.morphoBorrow(
marketParams,
borrowAmount,
0n,
slippageAmount,
borrower
);
ERC4626 Actions
- erc4626Mint: Mint shares of an ERC4626 vault
- erc4626Deposit: Deposit assets into an ERC4626 vault
- erc4626Withdraw: Withdraw assets from an ERC4626 vault
- erc4626Redeem: Redeem shares from an ERC4626 vault
Example:
const vaultAddress = "0xABCD...";
const assets = 1000000000000000000n; // 1 token
const minShares = 900000000000000000n; // 0.9 shares
const receiver = "0xEFGH...";
const depositAction = BundlerAction.erc4626Deposit(
vaultAddress,
assets,
minShares,
receiver
);
Universal Rewards Distributor Actions
- urdClaim: Claim rewards from the Universal Rewards Distributor
Example:
const distributor = "0x1234...";
const account = "0x5678...";
const reward = "0xABCD...";
const amount = 1000000000000000000n; // 1 token
const proof = ["0xabcd...", "0xdef..."];
const skipRevert = false;
const claimAction = BundlerAction.urdClaim(
distributor,
account,
reward,
amount,
proof,
skipRevert
);
Wrapped Native Token Actions
- wrapNative: Wrap native tokens (e.g., ETH to WETH)
- unwrapNative: Unwrap native tokens (e.g., WETH to ETH)
Example:
const wrapAmount = 1000000000000000000n; // 1 ETH
const wrapAction = BundlerAction.wrapNative(wrapAmount);
stETH and wstETH Actions
- stakeEth: Stake ETH using Lido
- wrapStEth: Wrap stETH to wstETH
- unwrapStEth: Unwrap wstETH to stETH
Example:
const stakeAmount = 1000000000000000000n; // 1 ETH
const minShares = 950000000000000000n; // 0.95 stETH
const referral = "0x0000...";
const stakeAction = BundlerAction.stakeEth(stakeAmount, minShares, referral);
Protocol-Specific Migration Actions
These actions are available on specific migration bundler instances:
- AaveV2: repay, withdraw
- AaveV3: repay, withdraw
- AaveV3 Optimizer: repay, withdraw, withdrawCollateral, approveManagerWithSig
- CompoundV2: repay, redeem
- CompoundV3: repay, withdrawFrom, allowBySig
Example (AaveV3):
const asset = "0x1234...";
const amount = 1000000000000000000n; // 1 token
const rateMode = 2; // Variable rate
const aaveV3RepayAction = BundlerAction.aaveV3Repay(asset, amount, rateMode);
Combining Actions
You can combine multiple actions into a single transaction using the multicall
function of your bundler contract:
await bundler
.connect(signer)
.multicall([
BundlerAction.wrapNative(1000000000000000000n),
BundlerAction.erc20Transfer(wethAddress, recipient, 500000000000000000n),
BundlerAction.morphoSupplyCollateral(
marketParams,
500000000000000000n,
borrower,
"0x"
),
BundlerAction.morphoBorrow(
marketParams,
100000000n,
0n,
slippageAmount,
borrower
),
]);
This executes all the encoded actions in a single transaction, saving gas and simplifying complex operations.
Slippage Considerations
Several Bundler actions, particularly those involving Morpho and ERC4626 vaults, include slippage protection parameters.
These parameters help protect users from unexpected price movements between transaction submission and execution.
For example, morphoSupply
, morphoBorrow
, morphoRepay
, morphoWithdraw
, and other ERC4626 actions all include a slippageAmount
or similar parameter.
This value represents the minimum (for withdrawals/borrows) or maximum (for deposits/repayments) amount of assets or shares to be transferred, ensuring the transaction reverts if market conditions change unfavorably.
When using these actions, carefully calculate and set appropriate slippage tolerances based on the asset's volatility and expected transaction time to balance between execution success and protection against adverse price movements.
For more details about the slippage considerations in the Morpho Interface, refer to the Slippage section of the Morpho Interface documentation.
Best Practices
- Always use
bigint
for numeric values to avoid precision loss. - Double-check addresses and parameters before encoding actions.
- Use descriptive variable names to improve code readability.
- Leverage TypeScript for better type checking and autocomplete support.
- Test your encoded actions thoroughly before deploying to mainnet.
- Be aware of the specific bundler instances required for certain actions (e.g., protocol-specific migration actions).
- Implement proper error handling and transaction confirmation in your application.
- Consider gas optimization by ordering actions efficiently within your multicall.
By mastering these BundlerAction
functions, you can create complex, gas-efficient interactions with Morpho and various DeFi protocols in a streamlined manner.