Collateral Swap (Withdraw & Deposit)
This guide covers collateral swaps, which allow users to exchange their collateral assets without closing positions or repaying debt.
Overview
Collateral swaps differ from leverage operations in one key way: you can withdraw your entire collateral balance. Since collateral balances change with each block due to interest accrual, the exact amount isn't always predictable when querying off-chain. This creates challenges when working with swap aggregators, which typically require exact input amounts for quotes.
Example Scenario
We'll demonstrate switching collateral on Aave V3 from WETH to USDT while maintaining a leveraged position. The process involves:
- Withdrawing WETH collateral
- Swapping WETH to USDT via 1inch
- Depositing USDT as new collateral
We'll wrap this entire sequence in a Flash Loan using Morpho Blue as the flash loan provider.
Starting Position:
- Collateral: 3 WETH
- Debt: 8,000 USDC
Target:
- Convert 3 WETH to 12,000 USDT as new collateral
Constants
// The collateral amount to exchange
uint256 USER_AMOUNT = 3.0e18;
// Default forwarder address
address CALL_FORWARDER = 0xfCa1154C643C32638AEe9a43eeE7f377f515c801;
// 1Delta composer
IComposer composer = IComposer(0x...);
// Aave V3 pool address
address AAVE_V3_POOL = address(0x...);
// Aave V3 WETH collateral token
address AAVE_V3_A_TOKEN_WETH = address(0x...);
// 1inch aggregation router
address oneInchAggregationRouter = address(0x111...);
// Morpho Blue flash loan source
address MORPHO_BLUE = address(0xbbb...);
Operation Sequence
1. Deposit New Collateral
After receiving USDT from the swap, we deposit it into Aave V3. Setting the amount to 0
deposits whatever USDT balance the contract holds.
Important: Ensure all required approvals are granted beforehand (see Approvals section).
bytes memory deposit = abi.encodePacked(
uint8(ComposerCommands.LENDING),
uint8(LenderOps.DEPOSIT),
uint16(LenderIds.UP_TO_AAVE_V3 - 1), // Aave V3 identifier
address(USDT), // Asset to deposit
uint128(0), // 0 = deposit full contract balance
address(user), // Deposit recipient
address(AAVE_V3_POOL) // Aave V3 pool address
);
2. Withdraw Original Collateral
We withdraw the entire WETH collateral balance and split it between the swap operation and any remainder for the user.
bytes memory withdraw = abi.encodePacked(
uint8(ComposerCommands.LENDING),
uint8(LenderOps.WITHDRAW),
uint16(LenderIds.UP_TO_AAVE_V3 - 1), // Aave V3 identifier
address(WETH), // Asset to withdraw
uint128(0), // 0 = withdraw full balance
address(COMPOSER_ADDRESS), // Temporary recipient for splitting
address(AAVE_V3_A_TOKEN_WETH), // Collateral token address
address(AAVE_V3_POOL) // Aave V3 pool address
);
// Send exact swap amount to forwarder
bytes memory transferToForwarder = abi.encodePacked(
uint8(ComposerCommands.TRANSFERS),
uint8(TransferIds.SWEEP),
address(WETH), // Asset to transfer
address(CALL_FORWARDER), // Recipient (swap forwarder)
uint128(USER_AMOUNT) // Exact quoted amount
);
// Send any remainder to user
bytes memory transferToUser = abi.encodePacked(
uint8(ComposerCommands.TRANSFERS),
uint8(TransferIds.SWEEP),
address(WETH), // Asset to transfer
address(user), // Recipient
uint128(0) // Send remaining balance
);
// Combine all withdrawal operations
withdraw = abi.encodePacked(
withdraw,
transferToForwarder,
transferToUser
);
3. Approvals
Required approvals for the operation to succeed:
// Approve Aave V3 pool to spend USDT for deposit
bytes memory approvePool = abi.encodePacked(
uint8(ComposerCommands.TRANSFERS),
uint8(TransferIds.APPROVE),
address(USDT), // Asset to approve
address(AAVE_V3_POOL) // Spender
);
// Approve Morpho Blue to pull WETH for flash loan repayment
bytes memory approveMorpho = abi.encodePacked(
uint8(ComposerCommands.TRANSFERS),
uint8(TransferIds.APPROVE),
address(WETH), // Asset to approve
address(MORPHO_BLUE) // Spender
);
4. Meta Swap Configuration
The swap operation uses the external call pattern described in External Call:
// Configure the forwarder call to 1inch
bytes memory callForwarderCall = abi.encodePacked(
uint8(ComposerCommands.EXT_CALL),
address(oneInchAggregationRouter), // Target contract
uint128(0), // No ETH value for ERC20 swap
uint16(data.length), // Call data length
data // 1inch swap call data
);
// Approve 1inch to spend WETH
bytes memory approve1inch = abi.encodePacked(
uint8(ComposerCommands.TRANSFERS),
uint8(TransferIds.APPROVE),
address(WETH), // Asset to approve
address(oneInchAggregationRouter) // Spender
);
// Verify minimum output and transfer to recipient
uint256 amountExpected = 12000.0e6; // Expected USDT amount (6 decimals)
bytes memory sweepAndCheckSlippage = abi.encodePacked(
uint8(ComposerCommands.TRANSFERS),
uint8(TransferIds.SWEEP),
address(USDT), // Asset to sweep
address(receiver), // Final recipient
uint8(SweepType.AMOUNT),
amountExpected // Minimum required amount
);
// Combine forwarder operations
callForwarderCall = abi.encodePacked(
approve1inch,
callForwarderCall,
sweepAndCheckSlippage
);
// Create meta swap call through forwarder
bytes memory metaSwap = abi.encodePacked(
uint8(ComposerCommands.EXT_CALL),
callForwarderAddress, // Forwarder contract
uint128(0), // No ETH value
uint16(callForwarderCall.length), // Call data length
callForwarderCall // Forwarder operations
);
Complete Operation Assembly
Putting it all together with the flash loan wrapper:
uint128 flashLoanAmount = uint128(3.0e18); // Amount to flash loan
// Inner operations executed within flash loan callback
bytes memory innerOperation = abi.encodePacked(
withdraw, // Withdraw WETH collateral
metaSwap, // Swap WETH to USDT
deposit // Deposit USDT as new collateral
);
// Flash loan wrapper
bytes memory flashLoan = abi.encodePacked(
uint8(ComposerCommands.FLASH_LOAN),
uint8(FlashLoanIds.MORPHO_BLUE),
address(WETH), // Flash loan asset
address(MORPHO_BLUE), // Flash loan provider
flashLoanAmount, // Flash loan amount
uint16(innerOperation.length + 1), // Callback data length
uint8(0), // Morpho Blue pool ID
innerOperation // Operations to execute
);
// Complete operation sequence
bytes memory composerOps = abi.encodePacked(
approvePool, // Pre-approve Aave pool
approveMorpho, // Pre-approve Morpho Blue
flashLoan // Execute flash loan with inner operations
);
// Execute the complete collateral swap
composer.deltaCompose(composerOps);
Key Considerations
Exact Amounts: When withdrawing the entire collateral balance, split transfers ensure the swap gets exactly the quoted amount while the user receives any remainder.
Permissions: The operation requires prior approval of the WETH collateral token:
IERC20(AAVE_V3_A_TOKEN_WETH).approve(composer, type(uint256).max)
Slippage Protection: The sweep operation with
SweepType.AMOUNT
ensures you receive at least the expected minimum output from the swap.Gas Optimization: Using the forwarder pattern eliminates unnecessary token transfers between operations.