Skip to content

Migrator is a tool for seamless migration of lend or borrow position from AAVE v3, Morpho, or Spark to Compound v3.

License

Notifications You must be signed in to change notification settings

woof-software/migrator-v2

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 

History

28 Commits
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

MigratorV2

MigratorV2 is a flexible contract for migrating user positions from various lending protocols (Aave, Spark, Morpho) to Compound III (Comet), using Uniswap V3 flash loans. The contract is built with a modular architecture, utilizing adapters for each protocol, and supports extension without the need for redeployment.


πŸ“„ Key Features

  • Migration from Aave, Spark, and Morpho to Compound III

Users can transfer their collateral and debt positions from three major protocols (Aave V3, Spark Protocol, Morpho Blue) to Compound III (Comet). This avoids manual asset withdrawal and debt repayment, reduces gas costs, and minimizes risks of temporarily exiting positions.

  • 6 Adapters: 2 per Protocol

Each supported lending protocol has two types of adapters:

  • A base adapter β€” supports all networks where the protocol is available.
  • A USDS-supporting adapter β€” designed for Ethereum, handling USDS-specific behavior.
  • Flexible Position Migration

Users can choose which specific collaterals and debts to migrate. For example, out of four collateral assets, they can choose to migrate only two, provided the health factor is preserved in the source protocol. This flexibility enables users to adapt migration to their individual strategies.

  • Debt-Free Migration

If the user has no active debt but holds collateral in the protocol (e.g., only aTokens in Aave), they can migrate just the collateral. In this case, no flash loan is needed, making the transaction significantly cheaper. The contract simply withdraws the collateral and deposits it into Compound III on behalf of the user.

  • Full Position Migration Support

Currently, only full migration of each selected position is supported β€” if a user selects a specific debt or collateral, the entire amount is migrated. Partial migration (e.g., 50% of a debt) will be added in future updates.

  • Customizable Target Collateral Format in Compound III

After migration, users can choose the format for depositing into Compound III:

  • The base token of the market (e.g., USDC or USDS)
  • One of the supported collateral tokens

This allows them to preserve or simplify their portfolio structure.

  • USDS Migration via DAI Proxy Mechanism

If a user interacts with Compound markets that use USDS as the base token, but USDS liquidity is low, the contract uses DAI as an intermediary token:

  • Flash loans, swaps, and deposits are executed in DAI.
  • The contract automatically converts DAI ⇄ USDS when needed.
  • Switching to direct USDS usage is possible in the future without redeployment.

πŸš€ How Migration Works

  1. Debt Repayment:
    • The contract receives a flash loan from Uniswap V3 (in the base token of the Compound market, or in DAI if the market uses USDS, based on migrator contract configuration).
    • The funds are used to repay the user’s debt in the source protocol (Aave, Spark, or Morpho).
  2. Collateral Migration:
    • The contract withdraws the user’s collateral from the source protocol.
    • If needed, it performs a swap via Uniswap V3 or a conversion (e.g., DAI ⇄ USDS).
    • Users can choose which collaterals to migrate or migrate all β€” fully or partially.
  3. Deposit into Compound III:
    • The collateral is deposited on behalf of the user into the selected Compound III market.
    • The user can choose:
    • The market’s base token β€” e.g., USDC or USDS
    • Supported collateral tokens β€” e.g., WBTC, WETH, etc.

πŸ’‘ Flash Loan Repayment Logic

  • If all collaterals are migrated into the market as base tokens, a portion of those funds will be automatically used to repay the flash loan plus the fee. After migration, the user’s balance will reflect the total collateral minus flash loan repayment costs.
  • If collaterals are migrated as supported tokens (not base asset):
  • The contract attempts to cover the flash loan by withdrawing the required base token amount from the user’s Compound III deposit.
  • If only collateral tokens are deposited (no base asset), the contract withdraws funds from the user’s Compound balance (via withdrawFrom) to repay the flash loan.
  • In case of a mixed migration (some collaterals as base token, others as collateral tokens):
  • If the deposited base token is insufficient to fully repay the flash loan, a debt is created for the difference, which is either deducted from the base token or withdrawn from the collateral in Compound.

⚠️ Important: The frontend must calculate the flash loan amount so that the user maintains a safe Health Factor in the new Compound market after migration. It must account for all variables: remaining base token balance, deposited collaterals, and potential flash loan repayment costs. An incorrectly calculated flash loan could leave the user at risk of immediate liquidation after migration.

  • The collateral is deposited into the selected Compound III market.
  • If the target market uses USDS as the base token, the DAI proxy mechanism is used.

πŸ“– Building Transactions for migrate()

πŸ” Example 1: Swapping debt and collateral via Uniswap V3

const fee3000 = ethers.utils.hexZeroPad(ethers.utils.hexlify(3000), 3); // 0x0BB8

const position = {
  borrows: [
    {
      debtToken: varDebtDaiTokenAddress,
      amount: MaxUint256,
      swapParams: {
        path: ethers.utils.concat([
          ethers.utils.hexZeroPad(daiTokenAddress, 20),
          fee3000,
          ethers.utils.hexZeroPad(usdcTokenAddress, 20)
        ]),
        amountInMaximum: parseUnits("80", 6)
      }
    }
  ],
  collaterals: [
    {
      aToken: aWbtcTokenAddress,
      amount: MaxUint256,
      swapParams: {
        path: ethers.utils.concat([
          ethers.utils.hexZeroPad(wbtcTokenAddress, 20),
          fee3000,
          ethers.utils.hexZeroPad(usdcTokenAddress, 20)
        ]),
        amountOutMinimum: parseUnits("100", 6)
      }
    }
  ]
};

const abi = [
  "tuple(address debtToken, uint256 amount, tuple(bytes path, uint256 amountInMaximum) swapParams)[]",
  "tuple(address aToken, uint256 amount, tuple(bytes path, uint256 amountOutMinimum) swapParams)[]"
];

const migrationData = ethers.utils.defaultAbiCoder.encode([
  "tuple(" + abi.join(",") + ")"
], [[position.borrows, position.collaterals]]);

πŸ” Example 2: Migrating only collateral (no debt)

const fee3000 = ethers.utils.hexZeroPad(ethers.utils.hexlify(3000), 3);

const position = {
  borrows: [],
  collaterals: [
    {
      aToken: aWbtcTokenAddress,
      amount: MaxUint256,
      swapParams: {
        path: ethers.utils.concat([
          ethers.utils.hexZeroPad(wbtcTokenAddress, 20),
          fee3000,
          ethers.utils.hexZeroPad(usdcTokenAddress, 20)
        ]),
        amountOutMinimum: parseUnits("100", 6)
      }
    }
  ]
};

const abi = [...]; // same as above
const migrationData = ethers.utils.defaultAbiCoder.encode([
  "tuple(" + abi.join(",") + ")"
], [[position.borrows, position.collaterals]]);

⚑ Example 3: No swaps (direct transfer)

const position = {
  borrows: [
    {
      debtToken: varDebtDaiTokenAddress,
      amount: MaxUint256,
      swapParams: {
        path: "0x",
        amountInMaximum: 0
      }
    }
  ],
  collaterals: [
    {
      aToken: aWbtcTokenAddress,
      amount: MaxUint256,
      swapParams: {
        path: "0x",
        amountOutMinimum: 0
      }
    }
  ]
};

πŸ”„ Example 4: DAI <-> USDS Conversion

const position = {
  borrows: [
    {
      debtToken: varDebtDaiTokenAddress,
      amount: MaxUint256,
      swapParams: {
        path: ethers.utils.concat([
          ethers.utils.hexZeroPad(usdsTokenAddress, 20),
          ethers.utils.hexZeroPad(daiTokenAddress, 20)
        ]),
        amountOutMinimum: parseUnits("120", 18)
      }
    }
  ],
  collaterals: [...]
};

🚧 Migration Prerequisites

  1. Grant permissions to the migrator contract:
  • For Aave and Spark protocols:
await aToken.approve(migrator.address, ethers.utils.parseUnits("0.1", 8));
await comet.allow(migrator.address, true);
  • For Morpho protocol:
await morphoPool.connect(user).setAuthorization(migrator.address, true);
  1. Frontend Responsibilities:
  • Provide accurate swap routes and all migrationData parameters.
  • Ensure approval of all involved tokens (debts and collaterals).
  • Account for the DAI ⇄ USDS proxy conversion logic.

🧭 Optimal Swap Routing β€” UniswapV3PathFinder

The UniswapV3PathFinder contract assists the frontend in building the most efficient swap paths through Uniswap V3. It selects the best route for both single-hop and multi-hop swaps, optimizing either the received token amount or minimizing input costs for a given amountOut.

πŸ” Key Features

  • Supports both exactInput and exactOutput modes.
  • Automatically finds the best pool (from fee tiers: 0.01%, 0.05%, 0.3%, 1%).
  • Supports excluding specific pools (excludedPool) β€” to avoid pool reuse within the same transaction.
  • Special handling for DAI ⇄ USDS β€” returns direct path without querying.
  • maxGasEstimate parameter allows setting a gas limit during QuoterV2 queries.

✳️ Use Cases

  1. πŸ” Single-Hop Swap β€” getBestSingleSwapPath
function getBestSingleSwapPath(SingleSwapParams memory params)
    external
    returns (bytes memory path, uint256 estimatedAmount, uint256 gasEstimate);

Input Parameters:

  • tokenIn, tokenOut β€” token addresses
  • amountIn or amountOut β€” only one must be provided
  • excludedPool β€” optional pool to exclude
  • maxGasEstimate β€” required

  1. πŸ”€ Multi-Hop Swap β€” getBestMultiSwapPath
function getBestMultiSwapPath(MultiSwapParams memory params)
    external
    returns (bytes memory path, uint256 estimatedAmount, uint256 gasEstimate);

Input Parameters:

  • tokenIn, tokenOut β€” token addresses
  • connectors β€” required list of possible intermediate tokens
  • amountIn or amountOut β€” only one must be set
  • excludedPool, maxGasEstimate

πŸ“Œ Error if both amountIn and amountOut are set or both are zero.


βš™οΈ Frontend Example

const params = {
  tokenIn: DAI,
  tokenOut: USDC,
  amountIn: parseUnits("100", 18),
  amountOut: 0,
  excludedPool: ZERO_ADDRESS,
  maxGasEstimate: 1_000_000
};

const { path, estimatedAmount, gasEstimate } = await pathFinder.getBestSingleSwapPath(params);

πŸ“Ž Built-in USDS ⇄ DAI Support

When tokenIn and tokenOut are DAI and USDS (or vice versa), the route is returned directly via abi.encodePacked(DAI, USDS) without querying the Quoter.


πŸ›‘οΈ Error Handling

Returns errors for:

  • InvalidZeroAddress
  • OnlyOneAmountMustBeSet
  • MustBeSetAmountInOrAmountOut
  • MustBeSetMaxGasEstimate
  • SwapPoolsNotFound
  • MustBeAtLeastOneConnector (for multi-hop only)

πŸ”§Β The contract is used only for off-chain swap path computation


πŸ“ƒ Administrative Functions (Owner Only)

  • setAdapter(address) β€” add a new adapter
  • removeAdapter(address) β€” remove an adapter
  • setFlashData(address comet, FlashData flashData) β€” set flash loan configuration
  • removeFlashData(address comet) β€” remove flash loan data for a market

πŸš€ Future Plans

  • Enable partial position migration (by amount).
  • Transition from DAI proxy to direct USDS use (as liquidity allows).

🌐 Deployed Contracts

Arbitrum


Base


Ethereum


Polygon


Optimism


πŸ”– License

The project is licensed under BUSL-1.1 License.

About

Migrator is a tool for seamless migration of lend or borrow position from AAVE v3, Morpho, or Spark to Compound v3.

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published