Skip to main content

Migrate position (cross-lender)

GET 

/v1/actions/loop/migrate

Move a whole debt + collateral position from one lender to another in a single flash-loan transaction, optionally converting ONE leg (collateral OR debt) via an aggregator swap.

Flow (no conversion): flash the debt → repay the source → withdraw the source collateral → deposit it to the target → borrow the debt from the target → repay the flash. Same-lender / different-market moves (e.g. Morpho market A→B, Aave V4 reserve→reserve) are supported.

Supported sources & targets: Aave V2/V3, Aave V4, Compound V3, Morpho Blue / Lista, Fluid (an existing position via NFT-custody, OR a brand-new position — see below), Gearbox V3 (existing credit account as source; a fresh credit account is opened in-callback as target), Euler V2 (accountId 0; collateral-share transfer through the call forwarder). Not supported: Dolomite (either side), Euler as a TARGET, non-Venus Compound V2 as a target, both legs converting at once, Fluid↔Fluid.

Fluid target — existing vs fresh: pass the position's NFT id in accountId to migrate INTO an existing Fluid position (delivered via the VaultFactory NFT-custody flow). Omit it (or pass 0) to open a brand-new Fluid position: the migrate emits a single dual-axis Fluid operate that supplies the withdrawn collateral, borrows the debt, and mints the new position NFT straight to you — no NFT custody, no target-side consent, and the transaction targets the composer (deltaCompose) normally.

Native ↔ wrapped-native (wrap conversion): a native debt — e.g. a Venus vBNB borrow — migrates into a target lender's wrapped-native debt market (WBNB). On-behalf borrowing cannot be delegated for the native asset, so the target MUST be the wrapped form; a native TARGET debt is rejected. The builder flashes the wrapped form, unwraps it to repay the native source, and borrows the wrapped form on the target — no aggregator needed. Compound V2 / Venus can be a migration source but never a target (no borrow delegation). The collateral leg wraps BOTH ways — a native source collateral (e.g. Fluid's ETH slot) is wrapped to WETH before the target deposit, and a wrapped source collateral is unwrapped for a native-collateral target — again with no aggregator. Native collateral is only reachable as such a wrap pair; native → an unrelated ERC20 target is rejected.

Lista DAO markets: two per-market extras decide the shape. A brokered market (non-zero broker) routes its debt leg through the fixed-term broker — pass loanId when it is the SOURCE (which loan to repay) and termId when it is the TARGET (which term to open); the target has no default because the broker's flexible borrow has no on-behalf variant. A market with a collateralProvider (e.g. the slisBNB markets) routes its collateral leg through that provider, which exposes no position() getter — so a provider-gated SOURCE cannot use the withdraw-all sentinel and is sized from your live collateral balance instead (read server-side; pass collateralAmount to override).

Asset conversion (swap leg): ONE leg may change asset via an aggregator swap — either the collateral (marketUidTargetCollateral uses a different underlying) or the debt (marketUidTargetDebt), not both. The route fetches the aggregator quote server-side and bakes the trade into the flash callback. A collateral swap is EXACT_INPUT of the withdrawn collateral → provide a concrete collateralAmount (or collateralAmountHint) so the swap input lines up. A debt swap is EXACT_OUTPUT (buy exactly the source debt to repay, selling the flashed target debt). Tune the swap tolerance with slippage. Euler as a source still needs an off-chain eVault share-balance read via the prepareMigrate SDK.

Omit account for quote-only (returns data.quotes with price deltas). Include account to build full transaction calldata (populates actions with alternatives, transactions, and permissions).

Setup transactions (actions.permissions): returned only for the consents actually missing, to be executed BEFORE the migrate. These vary by lender pair — e.g. the source collateral withdrawal approval; Aave V4 Giver/Taker/Config Position-Manager authorizations + per-reserve borrow allowance + collateral-enable grant; Morpho setAuthorization; a Gearbox V3 SOURCE's setBotPermissions grant on the credit account; Euler collateral eVault share approval to the composer. Fluid legs need no separate permission (NFT custody — or, for a fresh open, the composer opening on your behalf — IS the authorization).

Delivery: the migrate transaction targets the composer (deltaCompose) normally, OR the Fluid VaultFactory.safeTransferFrom when a Fluid leg requires NFT custody — actions.transactions[0].to reflects this.

data.result (resulting position): a summary for the UI — from/to lenders with their collateral + debt assets (address/symbol/decimals/logo), the new position amounts (to.collateral.amount/amountUsd, to.debt.amount/amountUsd), netUsd, leverage, apr: { deposit, borrow, net } and healthFactor. The rates, liquidation threshold, decimals and prices behind those are resolved server-side from the target markets — the corresponding query params are overrides only. USD/symbol fields are best-effort (omitted when price/token metadata is unavailable). to.collateral.amount is present when the withdraw was sized server-side (Aave source) or passed explicitly.

Finding a target: GET /v1/actions/loop/migrate/targets returns the ranked destinations this endpoint accepts for a given position — support matrix, native ⇄ wrapped equivalence, un-openable fixed-term books and borrow liquidity all applied server-side.

Plain-text reference — GET /v1/actions/loop/migrate

Parameters

ParameterInTypeRequiredDescription
marketUidSourceCollateralquerystringyesSource collateral market (lender:chainId:address). The position being moved FROM.
marketUidSourceDebtquerystringyesSource debt market (lender:chainId:address).
marketUidTargetCollateralquerystringyesTarget collateral market (lender:chainId:address). The position being moved TO. May be a different lender and/or a different asset (collateral conversion).
marketUidTargetDebtquerystringyesTarget debt market (lender:chainId:address). May be a different lender and/or a different asset (debt conversion).
debtAmountquerystringyesLive debt to migrate, in the debt asset’s wei. For a debt conversion this is the NEW-debt (flash/borrow) amount instead. The flash is sized a small buffer above this so the source repay clears in full (a residual would make the full collateral withdrawal revert).
accountquerystringnoAccount address. Include to build the transaction + permission setup; omit for a quote-only response.
isMaxInquerybooleannoWithdraw the full collateral balance from the source (default true for a same-asset move). Set false + collateralAmount for an exact-amount conversion so the swap input lines up.
collateralAmountquerystringnoExact source collateral to withdraw, in wei. Required (with isMaxIn=false) for a collateral conversion so the withdrawn amount matches the swap input.
accountIdquerystringnoPer-position id where the lender needs one: an existing Fluid NFT id (omit or 0 opens a BRAND-NEW Fluid position), Euler sub-account index (must be 0 — sub-accounts unsupported). Defaults to 0.
loanIdquerystringnoLista fixed-term broker SOURCE only: the loan posId to repay, or type(uint128).max for the flex/dynamic position. Required when the source debt market is brokered; ignored otherwise. The target borrow is keyed by term, not loanId.
termIdqueryintegernoLista fixed-term broker TARGET only: which term the migrated debt opens at. REQUIRED when the target debt market is brokered — the broker exposes no on-behalf flexible borrow, so there is no default; ignored otherwise. Mirrors the termId on /v1/actions/loop/open.
irModeFromquery0, 1, 2noAave interest mode of the source debt (repay). 2 = variable.
irModeToquery0, 1, 2noAave interest mode of the target debt (borrow). 2 = variable.
slippagequerystringnoSwap-leg slippage tolerance as a FRACTION (0.005 = 0.5%). Only used
when a leg is converted via an aggregator swap; ignored for same-asset /
wrap moves. Defaults to 0.5%.

This endpoint is the exception. Every other slippage in this API is in BASIS POINTS (50 = 0.5%) — migrate takes a fraction, which is 100× smaller for the same tolerance. Sending 50 here would ask for 5000% slippage. | | eModeTo | query | string | no | Risk-config category to read the TARGET collateral's liquidation threshold from (Aave-style e-modes publish one config per category). Defaults to 0 (no e-mode). Display only — it does not change the built transaction. | | collateralDecimals | query | integer | no | Override. Target collateral decimals. Resolved server-side; pass only to overrule the published metadata (e.g. a market whose token-list decimals are known-wrong, which would mis-scale the data.result USD values). Never affects the on-chain amounts. | | debtDecimals | query | integer | no | Override. TARGET debt decimals — the swap target’s when the debt converts, else the source’s. Resolved server-side; see collateralDecimals. | | sourceDebtDecimals | query | integer | no | Override. SOURCE debt decimals, for the from-leg display when the debt is converted. Resolved server-side; defaults to debtDecimals. | | depositApr | query | string | no | Override. Target collateral deposit APR as a FRACTION (0.05 = 5%), intrinsic yield folded in. Resolved server-side and reported as data.result.apr.deposit. | | borrowApr | query | string | no | Override. Target debt borrow APR as a FRACTION (0.05 = 5%), intrinsic yield folded in — for a fixed-term target this is the term rate, not the 0% variable rate. Resolved server-side. | | liqThreshold | query | string | no | Override. Target collateral liquidation threshold as a FRACTION (0.85 = 85%), driving data.result.healthFactor (= collateralUsd · liqThreshold / debtUsd; < 1 ⇒ liquidatable). Resolved server-side from the target market’s risk config — see eModeTo. | | collateralPriceUsd | query | string | no | Override. USD price of the TARGET collateral for the display conversion. Resolved server-side, preferring the market’s own ORACLE price (what the lender liquidates against). | | debtPriceUsd | query | string | no | Override. USD price of the TARGET debt for the display conversion. Resolved server-side; see collateralPriceUsd. |

Response 200

FieldTypeDescription
successTrue
dataobjectInformational data (quotes, simulation results, etc.)
data.lenderstringProtocol identifier
data.quotesobject[]Candidate routes, best output first. Execute exactly one.
data.quotes[].deltasobject
data.quotes[].deltas.aggregatorstringAggregator source
data.quotes[].deltas.tradeInputnumberTrade input amount
data.quotes[].deltas.tradeOutputnumberTrade output amount
data.quotes[].deltas.deltasobjectBalance deltas
data.quotes[].rateImpactobject[]Projected interest-rate impact per market for THIS quote (its own trade amounts). Omitted if IRM data is unavailable.
data.quotes[].rateImpact[].marketUidstringMarket identifier (format: lender:chainId:address)
data.quotes[].rateImpact[].utilizationobjectA current/projected pair for a single rate metric.
data.quotes[].rateImpact[].borrowRateobjectA current/projected pair for a single rate metric.
data.quotes[].rateImpact[].depositRateobjectA current/projected pair for a single rate metric.
data.rateImpactobject[]Projected interest-rate impact per market. Single-market actions produce 1 entry; loop actions produce 2. Null if IRM data is unavailable.
data.rateImpact[].marketUidstringMarket identifier (format: lender:chainId:address)
data.rateImpact[].utilizationobjectA current/projected pair for a single rate metric.
data.rateImpact[].utilization.currentnumberCurrent value
data.rateImpact[].utilization.projectednumberProjected value after the action
data.rateImpact[].borrowRateobjectA current/projected pair for a single rate metric.
data.rateImpact[].borrowRate.currentnumberCurrent value
data.rateImpact[].borrowRate.projectednumberProjected value after the action
data.rateImpact[].depositRateobjectA current/projected pair for a single rate metric.
data.rateImpact[].depositRate.currentnumberCurrent value
data.rateImpact[].depositRate.projectednumberProjected value after the action
data.lenderstringProtocol identifier
data.quotesobject[]Candidate routes, best output first. Execute exactly one.
data.quotes[].deltasobject
data.quotes[].deltas.aggregatorstringAggregator source
data.quotes[].deltas.tradeInputnumberTrade input amount
data.quotes[].deltas.tradeOutputnumberTrade output amount
data.quotes[].deltas.deltasobjectBalance deltas
data.quotes[].txobjectAn EVM transaction ready to sign and broadcast. Send to, data and value as-is; do not re-encode them.
data.quotes[].tx.tostringTarget contract address
data.quotes[].tx.datastringEncoded calldata
data.quotes[].tx.valuestringETH value to send with the transaction
data.quotes[].tx.descriptionstringHuman-readable label. For alternatives, this is the aggregator name (e.g. "Paraswap"). For transactions, describes the setup action (e.g. "Switch e-mode to 1").
data.quotes[].rateImpactobject[]Projected interest-rate impact per market for THIS quote (its own trade amounts). Omitted if IRM data is unavailable.
data.quotes[].rateImpact[].marketUidstringMarket identifier (format: lender:chainId:address)
data.quotes[].rateImpact[].utilizationobjectA current/projected pair for a single rate metric.

Example response

{
"success": true,
"data": {
"lender": "AAVE_V3",
"quotes": [
{
"deltas": {
"aggregator": "string",
"tradeInput": 1.0,
"tradeOutput": 1.0,
"deltas": {}
},
"rateImpact": [
{
"marketUid": "AAVE_V3:8453:0x4200000000000000000000000000000000000006",
"utilization": {
"current": 1.0,
"projected": 1.0
},
"borrowRate": {
"current": 1.0,
"projected": 1.0
},
"depositRate": {
"current": 1.0,
"projected": 1.0
}
}
]
}
],
"rateImpact": [
{
"marketUid": "AAVE_V3:8453:0x4200000000000000000000000000000000000006",
"utilization": {
"current": 1.0,
"projected": 1.0
},
"borrowRate": {
"current": 1.0,
"projected": 1.0
},
"depositRate": {
"current": 1.0,
"projected": 1.0
}
}
]
},
"actions": {
"transactions": [
{
"to": "0x87870Bca3F3fD6335C3F4ce8392D69350B4fA4E2",
"data": "0x617ba037000000000000000000000000c02aaa39b2",
"value": "0",
"description": "string"
}
],
"alternatives": [
{
"to": "0x87870Bca3F3fD6335C3F4ce8392D69350B4fA4E2",
"data": "0x617ba037000000000000000000000000c02aaa39b2",
"value": "0",
"description": "string"
}
],
"permissions": [
{
"to": "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2",
"data": "0x617ba037000000000000000000000000c02aaa39b2",
"value": "0",
"description": "string",
"spender": "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2"
}
]
}
}

Request

Responses

Quote or full build response