Skip to content

Repay & Withdraw (T2 Vault)

T2 vaults use LP-style collateral (e.g. weETH/ETH). Use the NFT API (fetchNft) to get position and vault data. Supports both operate() (token-amount based) and operatePerfect() (share-based).

Helper: calculateColSharesMinMax (withdraw)

For withdraw: based on your initial deposit, pass one token amount if you want to withdraw entirely in that token, or pass both amounts in any desired split. Use depositAndBorrow: false.

Helper: calculatePerfectColAmounts (withdraw)

Pass supply0AmountWei (or supply1AmountWei) only; the other amount is auto-computed from pool proportion. Use depositAndBorrow: false, paybackAndWithdraw: true.

Repay Debt

  • Approve the vault to spend the borrow token (e.g. wstETH). Use a 1% buffer for accrued interest:
    typescript
    const approveAmount = (repayAmountWei * 101n) / 100n;

Exact amounts: operate()

For exact token amounts, use calculateColSharesMinMax with depositAndBorrow: false to get colSharesMinMax, then pass negative token amounts and debt:

typescript
const nft = await fetchNft(chainId, nftId);
const vault = nft.vault;
const colResult = calculateColSharesMinMax({
  vault,
  supply0AmountInWei: withdrawCol0Wei.toString(),
  supply1AmountInWei: withdrawCol1Wei.toString(),
  // ...
  depositAndBorrow: false,
});

// operate() with negative values for repay-and-withdraw
args: [
  nftId,
  -withdrawCol0Wei,
  -withdrawCol1Wei,
  BigInt(colResult.sharesWithSlippage),
  -repayAmountWei,
  accountAddress
]

Exact amounts: operatePerfect()

Use calculateColSharesMinMax for perfectColShares, and negative token min/max for slippage:

typescript
const perfectColShares = BigInt(colResult.shares);
const colToken0MinMax = -(withdrawCol0Wei * 99n) / 100n;
const colToken1MinMax = -(withdrawCol1Wei * 99n) / 100n;
const newDebt = -repayAmountWei;

args: [nftId, perfectColShares, colToken0MinMax, colToken1MinMax, newDebt, accountAddress]

Max (close position): operatePerfect()

For full close with operatePerfect(), use minInt for shares and debt:

  • perfectColShares = minInt
  • colToken0MinMax = -withdrawResult.token0AmtWithSlippage
  • colToken1MinMax = -withdrawResult.token1AmtWithSlippage
  • newDebt = minInt

Examples

Exact amounts with operate():

ts
/**
 * Example: Repay borrowed wstETH and withdraw weETH + ETH (exact amounts) using operate().
 *
 * Flow:
 *  (1) Fetch NFT, compute collateral shares for withdraw via calculateColSharesMinMax.
 *  (2) Approve vault to spend wstETH for repay.
 *  (3) Call operate() with negative token amounts, colSharesMinMax, and newDebt.
 * Set PRIVATE_KEY and RPC_URL in .env.
 *
 * Run: pnpm run borrow:t2-withdraw-repay -- --network arbitrum
 */

import { encodeFunctionData, erc20Abi, formatUnits, parseUnits } from "viem";
import { vaultT2Abi } from "../shared/abis/vaultT2Abi.js";
import {
  account,
  chain,
  publicClient,
  walletClient,
} from "../shared/config.js";
import { fetchNft } from "../shared/utils/utils.js";
import { calculateColSharesMinMax } from "../shared/utils/dex/shares.js";

const SLIPPAGE = 0.01;

// Set your NFT position ID here
const NFT_ID = "";

// Exact amounts to withdraw (18 decimals each)
const withdrawCol0Wei = parseUnits("0.00074419588105", 18); // weETH
const withdrawCol1Wei = parseUnits("0.0005709487514072", 18); // ETH
const repayAmountWei = parseUnits("0.001000000001628682", 18); // wstETH

async function main() {
  if (!NFT_ID.trim()) {
    console.error("Set NFT_ID in this file");
    process.exit(1);
  }
  const nftId = BigInt(NFT_ID.trim());

  const nft = await fetchNft(chain.id, NFT_ID.trim());
  const vault = nft.vault;
  const vaultAddress = vault.address as `0x${string}`;
  const supplyToken0 = vault.supplyToken.token0;
  const supplyToken1 = vault.supplyToken.token1;
  const borrowToken0 = vault.borrowToken.token0;
  if (!supplyToken1) throw new Error("T2 vault must have supplyToken.token1");

  // calculateColSharesMinMax (withdraw): pass one token if paying all from it, or both in any split
  const colResult = calculateColSharesMinMax({
    vault,
    supply0AmountInWei: withdrawCol0Wei.toString(),
    supply1AmountInWei: withdrawCol1Wei.toString(),
    borrow0AmountInWei: "",
    borrow1AmountInWei: "",
    supplySlippage: SLIPPAGE,
    borrowSlippage: 0,
    depositAndBorrow: false,
  });
  if (!colResult) throw new Error("calculateColSharesMinMax failed");

  // operate() expects negative values for repay-and-withdraw
  const newColToken0 = -withdrawCol0Wei;
  const newColToken1 = -withdrawCol1Wei;
  const colSharesMinMax = BigInt(colResult.sharesWithSlippage); // max shares we allow to burn (slippage-adjusted)
  const newDebt = -repayAmountWei;

  const borrowTokenAddr = borrowToken0.address as `0x${string}`;

  console.log("T2 operate: repay & withdraw (exact amounts, Arbitrum)");
  console.log("  Vault:", vaultAddress);
  console.log("  NFT ID:", nftId.toString());
  console.log(
    "  Repay:",
    formatUnits(repayAmountWei, borrowToken0.decimals),
    borrowToken0.symbol,
  );
  console.log(
    "  Withdraw col:",
    formatUnits(withdrawCol0Wei, supplyToken0.decimals),
    supplyToken0.symbol,
    "+",
    formatUnits(withdrawCol1Wei, supplyToken1.decimals),
    supplyToken1.symbol,
  );

  const approveAmt = (repayAmountWei * 101n) / 100n;

  // Step 1 - Approve vault to spend wstETH for repay
  const approveData = encodeFunctionData({
    abi: erc20Abi,
    functionName: "approve",
    args: [vaultAddress, approveAmt],
  });
  const approveHash = await walletClient.sendTransaction({
    to: borrowTokenAddr,
    data: approveData,
    account,
  });
  console.log("  Approve tx:", approveHash);
  console.log(
    "Explorer:",
    `${chain.blockExplorers?.default?.url}/tx/${approveHash}`,
  );

  const approveReceipt = await publicClient.waitForTransactionReceipt({
    hash: approveHash,
  });
  if (approveReceipt.status !== "success") {
    console.error("Approve tx reverted");
    process.exit(1);
  }

  // Step 2 - Call operate() to repay and withdraw
  const operateData = encodeFunctionData({
    abi: vaultT2Abi,
    functionName: "operate",
    args: [
      nftId,
      newColToken0,
      newColToken1,
      colSharesMinMax,
      newDebt,
      account.address,
    ],
  });

  const hash = await walletClient.sendTransaction({
    to: vaultAddress,
    data: operateData,
    value: 0n,
    account,
  });

  console.log("  Operate tx:", hash);
  console.log("Explorer:", `${chain.blockExplorers?.default?.url}/tx/${hash}`);

  const receipt = await publicClient.waitForTransactionReceipt({ hash });
  if (receipt.status !== "success") {
    console.error("Operate tx reverted");
    process.exit(1);
  }
  console.log("Position updated. wstETH repaid, weETH + ETH withdrawn.");
}

main().catch((e) => {
  console.error(e);
  process.exit(1);
});

Exact amounts with operatePerfect():

ts
/**
 * Example: Repay borrowed wstETH and withdraw weETH + ETH (exact amounts) using operatePerfect().
 *
 * Flow:
 *  (1) Fetch NFT, compute collateral shares for withdraw via calculateColSharesMinMax.
 *  (2) Approve vault to spend wstETH for repay.
 *  (3) Call operatePerfect() with perfectColShares, colToken0MinMax, colToken1MinMax, newDebt.
 * Set PRIVATE_KEY and RPC_URL in .env.
 *
 * Run: pnpm run borrow:t2-withdraw-repay-perfect -- --network arbitrum
 */

import { encodeFunctionData, erc20Abi, formatUnits, parseUnits } from "viem";
import { vaultT2Abi } from "../shared/abis/vaultT2Abi.js";
import {
  account,
  chain,
  publicClient,
  walletClient,
} from "../shared/config.js";
import { fetchNft } from "../shared/utils/utils.js";
import { calculatePerfectColAmounts } from "../shared/utils/dex/perfectAmounts.js";

const SLIPPAGE = 0.01;

// Set your NFT position ID here
const NFT_ID = "";

// Exact amounts to withdraw (18 decimals each)
const withdrawCol0Wei = parseUnits("0.000000149195155", 18); // weETH
const withdrawCol1Wei = parseUnits("0.000000114462103", 18); // ETH
const repayAmountWei = parseUnits("0.00000010000000032", 18); // wstETH

async function main() {
  if (!NFT_ID.trim()) {
    console.error("Set NFT_ID in this file");
    process.exit(1);
  }
  const nftId = BigInt(NFT_ID.trim());

  const nft = await fetchNft(chain.id, NFT_ID.trim());
  const vault = nft.vault;
  const vaultAddress = vault.address as `0x${string}`;
  const supplyToken0 = vault.supplyToken.token0;
  const supplyToken1 = vault.supplyToken.token1;
  const borrowToken0 = vault.borrowToken.token0;
  if (!supplyToken1) throw new Error("T2 vault must have supplyToken.token1");

  // calculatePerfectColAmounts: pass only supply0; supply1 auto-computed from pool proportion
  const colResult = calculatePerfectColAmounts({
    vault: nft.vault,
    supplySlippage: SLIPPAGE,
    depositAndBorrow: false,
    paybackAndWithdraw: true,
    proportion: "fixed",
    supply0AmountWei: withdrawCol0Wei.toString(),
    supply1AmountWei: "",
  });
  if (!colResult) throw new Error("calculateColSharesMinMax failed");

  // operatePerfect expects negative values for repay-and-withdraw.
  const perfectColShares = BigInt(colResult.shares);
  const colToken0MinMax = -BigInt(colResult.token0AmtWithSlippage);
  const colToken1MinMax = -BigInt(colResult.token1AmtWithSlippage);
  const newDebt = -repayAmountWei;

  const borrowTokenAddr = borrowToken0.address as `0x${string}`;

  console.log("T2 operatePerfect: repay & withdraw (exact amounts, Arbitrum)");
  console.log("  Vault:", vaultAddress);
  console.log("  NFT ID:", nftId.toString());
  console.log(
    "  Repay:",
    formatUnits(repayAmountWei, borrowToken0.decimals),
    borrowToken0.symbol,
  );
  console.log(
    "  Withdraw col:",
    formatUnits(withdrawCol0Wei, supplyToken0.decimals),
    supplyToken0.symbol,
    "+",
    formatUnits(withdrawCol1Wei, supplyToken1.decimals),
    supplyToken1.symbol,
  );

  const approveAmt = (repayAmountWei * 101n) / 100n;

  // Step 1 - Approve vault to spend wstETH for repay
  const approveData = encodeFunctionData({
    abi: erc20Abi,
    functionName: "approve",
    args: [vaultAddress, approveAmt],
  });
  const approveHash = await walletClient.sendTransaction({
    to: borrowTokenAddr,
    data: approveData,
    account,
  });
  console.log("  Approve tx:", approveHash);
  console.log(
    "Explorer:",
    `${chain.blockExplorers?.default?.url}/tx/${approveHash}`,
  );

  const approveReceipt = await publicClient.waitForTransactionReceipt({
    hash: approveHash,
  });
  if (approveReceipt.status !== "success") {
    console.error("Approve tx reverted");
    process.exit(1);
  }

  // Step 2 - Call operatePerfect() to repay and withdraw
  const operateData = encodeFunctionData({
    abi: vaultT2Abi,
    functionName: "operatePerfect",
    args: [
      nftId,
      perfectColShares,
      colToken0MinMax,
      colToken1MinMax,
      newDebt,
      account.address,
    ],
  });

  const hash = await walletClient.sendTransaction({
    to: vaultAddress,
    data: operateData,
    value: 0n,
    account,
  });

  console.log("  OperatePerfect tx:", hash);
  console.log("Explorer:", `${chain.blockExplorers?.default?.url}/tx/${hash}`);

  const receipt = await publicClient.waitForTransactionReceipt({ hash });
  if (receipt.status !== "success") {
    console.error("OperatePerfect tx reverted");
    process.exit(1);
  }
  console.log("Position updated. wstETH repaid, weETH + ETH withdrawn.");
}

main().catch((e) => {
  console.error(e);
  process.exit(1);
});

Close position (operatePerfect):

ts
/**
 * Example: Repay all borrowed wstETH and withdraw all weETH + ETH (close position) using operatePerfect().
 *
 * Flow:
 *  (1) Fetch NFT position, compute max withdraw amounts via calculatePerfectMaxWithdrawAmounts,
 *  (2) Approve vault to spend wstETH for max repay,
 *  (3) Call operatePerfect() with minInt for shares/debt to repay max and withdraw max.
 * Set PRIVATE_KEY and RPC_URL in .env.
 *
 * Run: pnpm run borrow:t2-withdraw-repay-max-perfect -- --network arbitrum
 */

import { encodeFunctionData, erc20Abi, formatUnits, parseUnits } from "viem";
import { vaultT2Abi } from "../shared/abis/vaultT2Abi.js";
import {
  account,
  chain,
  publicClient,
  walletClient,
} from "../shared/config.js";
import { fetchNft } from "../shared/utils/utils.js";
import { calculatePerfectMaxWithdrawAmounts } from "../shared/utils/dex/perfectAmounts.js";
import { minInt } from "../shared/utils/constants.js";

const SLIPPAGE = 0.01;

// Set your NFT position ID here
const NFT_ID = "";

async function main() {
  if (!NFT_ID.trim()) {
    console.error("Set NFT_ID in this file");
    process.exit(1);
  }
  const nftId = BigInt(NFT_ID.trim());

  const nft = await fetchNft(chain.id, NFT_ID.trim());
  const vault = nft.vault;
  const vaultAddress = vault.address as `0x${string}`;
  const supplyToken0 = vault.supplyToken.token0;
  const supplyToken1 = vault.supplyToken.token1;
  const borrowToken0 = vault.borrowToken.token0;
  if (!supplyToken1) throw new Error("T2 vault must have supplyToken.token1");

  // calculatePerfectMaxWithdrawAmounts: computes max withdraw amounts for full close
  const withdrawResult = calculatePerfectMaxWithdrawAmounts({
    nft,
    supplySlippage: SLIPPAGE,
  });
  if (!withdrawResult)
    throw new Error("calculatePerfectMaxWithdrawAmounts failed");

  // operatePerfect max: use minInt for shares and debt to withdraw/repay all
  const perfectColShares = BigInt(minInt);
  const colToken0MinMax = -BigInt(withdrawResult.token0AmtWithSlippage);
  const colToken1MinMax = -BigInt(withdrawResult.token1AmtWithSlippage);
  const newDebt = BigInt(minInt);

  const repayAmountWei = BigInt(minInt);
  const borrowTokenAddr = borrowToken0.address as `0x${string}`;

  console.log("T2 operatePerfect: repay max & withdraw max (Arbitrum)");
  console.log("  Vault:", vaultAddress);
  console.log("  NFT ID:", nftId.toString());
  console.log(
    "  Repay:",
    formatUnits(repayAmountWei, borrowToken0.decimals),
    borrowToken0.symbol,
  );
  console.log(
    "  Withdraw col: ~",
    formatUnits(BigInt(withdrawResult.token0Amt), supplyToken0.decimals),
    supplyToken0.symbol,
    "+",
    formatUnits(BigInt(withdrawResult.token1Amt), supplyToken1.decimals),
    supplyToken1.symbol,
  );

  // Approve wstETH with 1% buffer for max repay
  const approveAmt = (parseUnits("0.0001", 18) * 101n) / 100n;

  // Step 1 - Approve vault to spend wstETH for max repay
  const approveData = encodeFunctionData({
    abi: erc20Abi,
    functionName: "approve",
    args: [vaultAddress, approveAmt],
  });
  const approveHash = await walletClient.sendTransaction({
    to: borrowTokenAddr,
    data: approveData,
    account,
  });
  console.log("  Approve tx:", approveHash);
  console.log(
    "Explorer:",
    `${chain.blockExplorers?.default?.url}/tx/${approveHash}`,
  );

  const approveReceipt = await publicClient.waitForTransactionReceipt({
    hash: approveHash,
  });
  if (approveReceipt.status !== "success") {
    console.error("Approve tx reverted");
    process.exit(1);
  }

  // Step 2 - Call operatePerfect() to repay max and withdraw max
  const operateData = encodeFunctionData({
    abi: vaultT2Abi,
    functionName: "operatePerfect",
    args: [
      nftId,
      perfectColShares,
      colToken0MinMax,
      colToken1MinMax,
      newDebt,
      account.address,
    ],
  });

  const hash = await walletClient.sendTransaction({
    to: vaultAddress,
    data: operateData,
    value: 0n,
    account,
  });

  console.log("  OperatePerfect tx:", hash);
  console.log("Explorer:", `${chain.blockExplorers?.default?.url}/tx/${hash}`);

  const receipt = await publicClient.waitForTransactionReceipt({ hash });
  if (receipt.status !== "success") {
    console.error("OperatePerfect tx reverted");
    process.exit(1);
  }
  console.log("Position closed. wstETH repaid, weETH + ETH withdrawn (max).");
}

main().catch((e) => {
  console.error(e);
  process.exit(1);
});