> For the complete documentation index, see [llms.txt](https://docs.curvance.com/app/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.curvance.com/app/developer-docs/quick-start-guides/leverage/leveraging.md).

# Leveraging

This page covers two concrete flows. Both extend the background in the Leveraging intro:

1. Deposit underlying and immediately leverage in a single transaction (building from scratch).
2. Leverage an existing Curvance position (add leverage without a fresh deposit).

This page assumes you already read Leverage, which covers `LeverageAction`, `SwapperLib.Swap`, swap calldata, auxiliary data, PositionManager fees, and position-health reads.

***

### 1. Initial Deposit and Leverage

The most common approach is to deposit and leverage in a single transaction. Ideal for users building a leveraged position from scratch.

Because `depositAndLeverage` applies the `preDeposit` modifier before `checkSlippage`, the initial deposit is posted before the PositionManager snapshots account status. The top-level `slippage` check is a net position-value sanity check for the leverage leg after the initial collateral is already in the account.

#### Preparing for Leverage

Before calling `depositAndLeverage`, prepare the market and route:

1. **Understand your target position:** Choose the isolated market, collateral cToken, debt cToken, and target leverage. Confirm the debt cToken is actually borrowable by checking `await marketManager.debtCaps(debtCToken) > 0n`. The market name is a grouping, not a role assignment.
2. **Build aggregator calldata** whose swap recipient is the PositionManager. Curvance calldata checkers validate the recipient against the PositionManager address.
3. **Set a slippage tolerance:** Set three slippage controls: the aggregator route's own min-out, `swapAction.slippage` for `SwapperLib._swapSafe`, and the top-level PositionManager `slippage` checked by `checkSlippage`.
4. **Approve the PositionManager:** your underlying collateral token must grant allowance to the PositionManager contract.

```js
import { ethers } from "ethers";

const ERC20_ABI = [
  "function allowance(address owner,address spender) view returns (uint256)",
  "function approve(address spender,uint256 amount) returns (bool)"
];

const POSITION_MANAGER = "0x...";
const COLLATERAL_ASSET_UNDERLYING = "0x...";
const COLLATERAL_DECIMALS = 18;

const underlying = new ethers.Contract(
  COLLATERAL_ASSET_UNDERLYING,
  ERC20_ABI,
  signer
);

const userAddress = await signer.getAddress();
const depositAmount = ethers.parseUnits("1000", COLLATERAL_DECIMALS);

const currentAllowance = await underlying.allowance(
  userAddress,
  POSITION_MANAGER
);

if (currentAllowance < depositAmount) {
  const approveTx = await underlying.approve(POSITION_MANAGER, depositAmount);
  await approveTx.wait();
}
```

#### Constructing the `LeverageAction`

`LeverageAction.borrowAssets` is the gross amount borrowed from the debt cToken. The internal swap usually uses the net amount after `centralRegistry.protocolLeverageFee()`, because the PositionManager deducts that fee before calling the manager-specific swap logic.

For a simple ERC20 PositionManager, `swapAction.inputAmount` should equal the net borrowed amount after the PositionManager fee, not always the gross `borrowAssets`.

```js
const BPS = 10_000n;
const WAD_SLIPPAGE_ONE_PERCENT = ethers.parseUnits("0.01", 18);

const MARKET_MANAGER_ABI = [
  "function debtCaps(address cToken) view returns (uint256)",
  "function isPositionManager(address positionManager) view returns (bool)",
  "function actionsPaused(address cToken) view returns (bool,bool,bool)"
];

const POSITION_MANAGER_ABI = [
  "function depositAndLeverage(uint256 assets,(address borrowableCToken,uint256 borrowAssets,address cToken,uint256 expectedShares,(address inputToken,uint256 inputAmount,address outputToken,address target,uint256 slippage,bytes call) swapAction,bytes auxData) action,uint256 slippage)",
  "function leverage((address borrowableCToken,uint256 borrowAssets,address cToken,uint256 expectedShares,(address inputToken,uint256 inputAmount,address outputToken,address target,uint256 slippage,bytes call) swapAction,bytes auxData) action,uint256 slippage)",
  "function leverageFor((address borrowableCToken,uint256 borrowAssets,address cToken,uint256 expectedShares,(address inputToken,uint256 inputAmount,address outputToken,address target,uint256 slippage,bytes call) swapAction,bytes auxData) action,address account,uint256 slippage)",
  "function centralRegistry() view returns (address)",
  "function setDelegateApproval(address delegate,bool isApproved)"
];

const CTOKEN_ABI = [
  "function asset() view returns (address)",
  "function previewDeposit(uint256 assets) view returns (uint256)",
  "function collateralPosted(address account) view returns (uint256)",
  "function balanceOf(address account) view returns (uint256)",
  "function postCollateral(uint256 shares)"
];

const CENTRAL_REGISTRY_ABI = [
  "function protocolLeverageFee() view returns (uint256)"
];

function mulDivUp(a, b, denominator) {
  return (a * b + denominator - 1n) / denominator;
}

async function netAfterPositionManagerFee(positionManager, grossAmount) {
  const centralRegistryAddress = await positionManager.centralRegistry();
  const centralRegistry = new ethers.Contract(
    centralRegistryAddress,
    CENTRAL_REGISTRY_ABI,
    signer
  );

  const feeBps = await centralRegistry.protocolLeverageFee();
  const fee = mulDivUp(grossAmount, feeBps, BPS);
  if (fee >= grossAmount) throw new Error("PositionManager fee exceeds amount");
  return grossAmount - fee;
}
```

```js
const MARKET_MANAGER = "0x...";
const BORROWABLE_CTOKEN = "0x...";
const COLLATERAL_CTOKEN = "0x...";
const AGGREGATOR_ROUTER_ADDRESS = "0x...";
const DEBT_DECIMALS = 6;
const ESTIMATED_COLLATERAL_OUT = ethers.parseUnits("0.49", 18);

const marketManager = new ethers.Contract(
  MARKET_MANAGER,
  MARKET_MANAGER_ABI,
  provider
);
const positionManager = new ethers.Contract(
  POSITION_MANAGER,
  POSITION_MANAGER_ABI,
  signer
);
const debtCToken = new ethers.Contract(BORROWABLE_CTOKEN, CTOKEN_ABI, provider);
const collateralCToken = new ethers.Contract(
  COLLATERAL_CTOKEN,
  CTOKEN_ABI,
  provider
);

const [isPositionManager, debtCap, [, , borrowPaused]] = await Promise.all([
  marketManager.isPositionManager(POSITION_MANAGER),
  marketManager.debtCaps(BORROWABLE_CTOKEN),
  marketManager.actionsPaused(BORROWABLE_CTOKEN)
]);

if (!isPositionManager) throw new Error("PositionManager is not enabled");
if (debtCap === 0n) throw new Error("Debt cToken is not borrowable");
if (borrowPaused) throw new Error("Borrowing is paused for this debt cToken");

const [debtAsset, collateralAsset] = await Promise.all([
  debtCToken.asset(),
  collateralCToken.asset()
]);

const borrowAssets = ethers.parseUnits("500", DEBT_DECIMALS);
const netSwapInput = await netAfterPositionManagerFee(
  positionManager,
  borrowAssets
);

const quotedShares = await collateralCToken.previewDeposit(
  ESTIMATED_COLLATERAL_OUT
);
const expectedShares = quotedShares * 9950n / 10_000n; // 0.50% share buffer.

// Aggregator calldata must send output to POSITION_MANAGER.
const swapCalldata = "0x...";

const swapAction = {
  inputToken: debtAsset,
  inputAmount: netSwapInput,
  outputToken: collateralAsset,
  target: AGGREGATOR_ROUTER_ADDRESS,
  slippage: WAD_SLIPPAGE_ONE_PERCENT,
  call: swapCalldata
};

const leverageAction = {
  borrowableCToken: BORROWABLE_CTOKEN,
  borrowAssets,
  cToken: COLLATERAL_CTOKEN,
  expectedShares,
  swapAction,
  auxData: "0x"
};
```

**Computing `expectedShares`:** the simplest approach is `await cToken.previewDeposit(estimatedSwapOutAmount)`, where `estimatedSwapOutAmount` is what you expect the aggregator to return. For aux-swap assets (Pendle PT, LP tokens), the input to `previewDeposit` is the post-aux-swap asset amount, not the raw borrowed amount. Slight staleness is acceptable here; the `checkSlippage` modifier is what ultimately enforces correctness.

{% hint style="info" %}
For the simple ERC20 path, `swapAction.outputToken` is `action.cToken.asset()`. For auxiliary managers such as Pendle or vault PositionManagers, `swapAction.outputToken` can be an intermediate protocol input, and `auxData` controls the final mint or enter step. Keep the action shape in sync with the specific PositionManager contract.
{% endhint %}

#### Executing the Leverage Operation

```js
// Slippage for the overall leverage action (WAD-scaled). 1e16 = 1%.
const slippage = ethers.parseUnits('0.01', 18);

// Execute deposit and leverage in one transaction
const tx = await positionManager.depositAndLeverage(
  depositAmount,
  leverageAction,
  slippage,
  { gasLimit: 2_000_000n } // optional overrides, not a positional arg
);

const receipt = await tx.wait();
console.log(`Leveraged position created. Tx hash: ${receipt.hash}`);
```

`expectedShares` is enforced after the swap output is deposited. If the deposit returns fewer shares than `action.expectedShares`, the PositionManager reverts with `BasePositionManager__InvalidSlippage()`.

***

### 2. Leveraging an Existing Position

If the account already has enough posted collateral in the same isolated market, call `leverage(LeverageAction action, uint256 slippage)` to add leverage without a fresh deposit.

```js
const postedOnTarget = await collateralCToken.collateralPosted(userAddress);
const targetBalance = await collateralCToken.balanceOf(userAddress);
const idleTargetShares = targetBalance - postedOnTarget;

if (postedOnTarget === 0n && idleTargetShares > 0n) {
  const postTx = await collateralCToken.postCollateral(idleTargetShares);
  await postTx.wait();
}
```

**Prerequisite:** the collateral must already be posted as collateral in the market (`collateralPosted[owner] > 0` on the collateral `cToken`). If you only deposited without posting, call `postCollateral(shares)` on the collateral cToken first, or use `depositAsCollateral` / `depositAndLeverage` in the initial flow.&#x20;

`postCollateral(shares)` takes cToken shares, not underlying assets. If the user only has underlying collateral tokens and no cToken shares, use `depositAndLeverage` for the initial flow, or deposit first and then post the received shares.

You can simulate the resulting health before submitting:

```js
const PROTOCOL_READER = "0x...";
const PROTOCOL_READER_ABI = [
  "function getPositionHealth(address mm,address account,address cToken,address borrowableCToken,bool isDeposit,uint256 collateralAssets,bool isRepayment,uint256 debtAssets,uint256 bufferTime) view returns (uint256,bool)"
];

const protocolReader = new ethers.Contract(
  PROTOCOL_READER,
  PROTOCOL_READER_ABI,
  provider
);

const [positionHealth, oracleError] =
  await protocolReader.getPositionHealth(
    MARKET_MANAGER,
    userAddress,
    COLLATERAL_CTOKEN,
    BORROWABLE_CTOKEN,
    true,
    ESTIMATED_COLLATERAL_OUT,
    false,
    borrowAssets,
    0
  );

if (oracleError) throw new Error("Oracle error while simulating position");
if (positionHealth <= ethers.parseUnits("1", 18)) {
  throw new Error("Projected position is at or below soft liquidation health");
}
```

Then execute:

```js
const tx = await positionManager.leverage(
  leverageAction,
  actionSlippage,
  { gasLimit: 2_000_000n }
);

const receipt = await tx.wait();
console.log(`Position further leveraged: ${receipt.hash}`);
```

***

### Hold-Period Interaction

Posting collateral and borrowing both reset the market's 20-minute `MIN_HOLD_PERIOD` for the account. `depositAndLeverage` posts collateral and borrows, so it starts or resets the cooldown. `leverage` borrows and deposits more collateral through the PositionManager, so it also starts or resets the cooldown.

During the cooldown, repayment, redemption / withdrawal, and cToken transfers for that account in the same isolated market revert with `MarketManager__MinimumHoldPeriod()`. Additional borrows and additional collateral postings are not directly blocked by the hold-period check, but they reset the cooldown again.

Deleveraging during the cooldown reverts. In the PositionManager deleverage path, `repayFor` checks the owner's hold period, and the final redemption review also checks transfer and redeem eligibility.

***

### Important Considerations

#### Position health and risk management

* Monitor the health factor before **and** after the operation using `ProtocolReader.getPositionHealth(...)`. See Monitoring Position Health for the WAD-scaled interpretation.
* **Max leverage ratio is not a single global parameter.** There is no single global max leverage ratio. The practical ceiling depends on per-market collateral config, borrow caps, available liquidity, prices, swap slippage, and PositionManager fees.
* For sizing leverage from a new deposit, `ProtocolReader.hypotheticalLeverageOf(...)` can estimate max borrow and leverage, but its own comments warn that AMM fees and slippage can make the executable amount lower.
* Liquidation penalties are configured per token through liquidation incentive and close-factor curves. The curve depends on how far the account is into liquidation and can also use dynamic auction configuration.

#### Swap data configuration

* The aggregator calldata (`swapAction.call`) must set its **recipient** to the PositionManager contract so the swapped collateral can be re-deposited in the same transaction.
* &#x20;`swapAction.inputAmount` must match the amount the specific PositionManager will actually swap. In the simple leverage path, that is the gross borrow amount minus `protocolLeverageFee()`.
* Typical aggregator slippage: 0.3-1% for stable pairs, 1-3% for volatile pairs. This is separate from the `slippage` arg passed to `depositAndLeverage` / `leverage`, which is the whole-action WAD-scaled tolerance checked by the `checkSlippage` modifier.
* The swap target must have a configured external calldata checker in the Central Registry. Unknown swap targets revert before execution.

#### Amount calculations

* Pre-compute `expectedShares` via `await cToken.previewDeposit(estimatedSwapOut)`; small staleness is fine since the outer slippage check enforces correctness.
* Include aggregator fees in your target leverage calc; for aux-swap assets (Pendle, LPs), include any additional fees from the underlying protocol's mint/swap.

#### Protocol-specific features

* Some managers require populated `auxData`, including Pendle and several vault or LP managers. Do not infer the tuple shape from another manager. Check the exact PositionManager contract or use the examples in Leverage.
* For simple ERC20 leverage, `estimatedCollateralInput` is the expected swap output of `action.cToken.asset()`. For aux-swap assets such as Pendle PT or LP positions, it is the expected final cToken underlying amount after the auxiliary enter step, not the raw borrowed amount.
* Apply a buffer to `expectedShares` so normal quote movement, protocol fees, swap fees, and cToken exchange-rate movement do not cause avoidable reverts.
* Do not default to `"0x"` for a manager that decodes `auxData`; that will revert during ABI decode.

#### Delegated leverage

For platforms leveraging on behalf of users, call `leverageFor(LeverageAction action, address account, uint256 slippage)`.

The account must approve the delegate on the PositionManager contract:

```js
const delegateAddress = "0x...";

const approvalTx = await positionManager.setDelegateApproval(
  delegateAddress,
  true
);
await approvalTx.wait();
```

{% hint style="info" %}
Users can mass-revoke delegates across Curvance contracts by calling `centralRegistry.incrementApprovalIndex()`. See [Integration Cookbook ](/app/developer-docs/quick-start-guides/integration-cookbook.md)for the full delegation model.
{% endhint %}

***
