# Position Roller
Migrate your debt from one position to another using flash loans.
The Position Roller is a helper contract that atomically moves your debt from a source position to a target position. This is useful when you want to take advantage of better terms, extend your position's expiration, or consolidate.
Each MintingHub module ships with its own roller. A roller can only operate between two positions registered with the same MintingHub:
| Module | PositionRoller |
|---|---|
| V2 | 0x4CE0AB2FC21Bd27a47A64F594Fdf7654Ea57Dc79 (opens new window) |
| V3 | 0x5C22d5b752b2121faE7F6f0069252B03B2F7c5CD (opens new window) |
# Why Roll a Position?
| Scenario | Benefit |
|---|---|
| Better interest rate | New positions may have a lower risk premium or follow a lower Leadrate |
| Extend expiration | Avoid forced liquidation of expiring positions |
| Higher price tolerance | Clone a position with a better liquidation price |
| Different collateral type | Move to a more liquid or stable collateral (manual swap required) |
| Consolidation | Merge multiple small positions into a single one |
# How Rolling Works
The roller uses a flash loan to move debt and collateral in a single transaction:
┌─────────────────────────────────────────────────────────────┐
│ SINGLE TRANSACTION │
├─────────────────────────────────────────────────────────────┤
│ 1. Flash-loan dEURO ──► temporary dEURO │
│ 2. Repay source position ──► debt cleared │
│ 3. Withdraw collateral ──► collateral released │
│ 4. Deposit into target ──► collateral locked │
│ 5. Mint from target ──► new debt created │
│ 6. Repay flash loan ──► transaction complete │
└─────────────────────────────────────────────────────────────┘
All steps happen in the same transaction — either everything succeeds or nothing changes.
# Rolling Methods
# Automatic Rolling
function rollFully(IPosition source, IPosition target) external
function rollFullyWithExpiration(
IPosition source,
IPosition target,
uint40 expiration
) external
What happens:
- All collateral is moved.
- The maximum possible amount is minted in the target.
- If the target has less minting capacity than the source's debt, the remaining debt must be covered from your wallet.
# Manual Rolling
For precise control over the roll parameters:
function roll(
IPosition source, // your current position
uint256 repay, // amount to repay (principal + interest in V3)
uint256 collWithdraw, // collateral to withdraw from source
IPosition target, // position to roll into
uint256 mint, // amount to mint in target
uint256 collDeposit, // collateral to deposit in target
uint40 expiration // desired expiration for target
) external
# Native ETH Rolling (V3)
V3 positions can hold native ETH (wrapped to WETH internally). The roller exposes payable variants that handle the wrapping/unwrapping for you:
function rollNative(...) external payable
function rollFullyNative(IPosition source, IPosition target) external payable
function rollFullyNativeWithExpiration(
IPosition source,
IPosition target,
uint40 expiration
) external payable
Benefits of the native variants:
- No need to interact with WETH directly.
- Collateral flows through the roller in native form.
- Excess is returned as native ETH.
- You can top up the position with extra collateral via
msg.value.
V2 does not have native-ETH-aware roller functions. For V2 native-ETH positions, use the CoinLendingGateway to open the position; the roller then operates on the wrapped collateral.
# Prerequisites
For ERC-20 collateral. Approve the roller to move your collateral before rolling:
await collateralToken.approve(ROLLER_ADDRESS, collateralBalance);
For native ETH (V3). No approval needed — just send ETH with the transaction if you want to add collateral.
Position ownership. You must own the source position. The target can be any valid position in the same minting hub. If you don't own the target, a clone of it is created for you.
# What Happens During a Roll
Scenario 1 — rolling into your own position. If you already own the target and the expiration matches:
- Collateral is transferred directly to the target.
mint()is called on the existing target.- No cloning occurs.
Scenario 2 — rolling into someone else's position. If you do not own the target or want a different expiration:
- The target position is cloned for you.
- You become the owner of the new clone.
- The clone inherits the target's parameters.
- Your collateral goes into the clone.
Scenario 3 — partial roll. If the target does not have enough minting capacity:
- As much as possible is minted in the target.
- Remaining debt is covered from your wallet.
- Excess dEURO from the flash loan is returned to you.
# Frontend Code Preservation (V2)
When rolling through the V2 MintingHubGateway, your frontend code is preserved on the new position. The roller checks whether the source MintingHub supports the gateway interface, reads the frontend code attached to the source position, and re-applies it to the cloned target.
V3 has no FrontendGateway layer, so frontend codes are not tracked on V3 rolls.
# Example: Rolling to Better Terms
Current position:
- 10,000 dEURO debt
- 0.5 ETH collateral (V3, native ETH)
- 6% effective rate
- Expires in 30 days
Target position (cheaper terms):
- Same collateral (native ETH)
- 4% effective rate
- 12-month duration
Roll:
const roller = new ethers.Contract(ROLLER_V3, abi, signer);
// Native ETH variant — no approve, no WETH dance
await roller.rollFully(SOURCE_POSITION, TARGET_POSITION);
Result:
- Old position closed (debt = 0).
- New position created with your collateral.
- Lower interest rate locked in.
- Extended expiration.
# Error Handling
Common errors when rolling:
| Error | Cause | Solution |
|---|---|---|
NotOwner | You don't own the source position | Use your own position |
NotPosition | Invalid position address | Verify position addresses are registered in the same MintingHub |
NativeTransferFailed | ETH transfer failed | Check receiving address (V3 native rolls only) |
| Insufficient allowance | Collateral not approved | Approve the roller first |
# Events
event Roll(
address source, // source position address
uint256 collWithdraw, // collateral withdrawn
uint256 repay, // debt repaid
address target, // target position address
uint256 collDeposit, // collateral deposited
uint256 mint // amount minted
);
# Security Considerations
- Flash loan repayment. The flash loan must be repaid in the same transaction. If minting in the target fails, the entire transaction reverts and your source position is untouched.
- Slippage. If market conditions change between submission and execution, the roll may fail. Set parameters with this in mind.
- Position validation. The roller validates that both positions are registered with the dEURO system before proceeding.
- Collateral matching.
rollFully()requires that the source and target have the same collateral type. Cross-collateral rolls require closing manually and reopening.
# Advanced: Custom Roll Strategies
# Partial Debt Migration
Move only part of your debt:
await roller.roll(
source,
5000e18, // repay only 5000 dEURO
0.25e18, // withdraw only 0.25 ETH
target,
5000e18, // mint 5000 in target
0.25e18, // deposit 0.25 ETH
newExpiration
);
# Adding Collateral During a Roll
For native ETH (V3), send extra ETH with the call:
await roller.rollFullyNative(source, target, {
value: ethers.utils.parseEther("0.1") // add 0.1 ETH
});
# Changing Collateral Type
Rolling between different collaterals requires manual handling:
- Close the source position normally.
- Swap the collateral on a DEX.
- Open a new position with the new collateral.
The roller only handles same-collateral rolls within the same MintingHub.