Skip to content

Repay & Withdraw (T1 Vault)

Function: operate()

Repays borrowed debt and withdraws collateral from a T1 Fluid vault in a single transaction.

  1. Repay Debt:

    • ERC20 Tokens: Prior to repaying, you must approve the vault to spend the debt token. Always approve slightly more than the exact debt amount — interest accrues every block, so by the time your transaction is mined the debt will be marginally higher than when you read it. A 1% buffer is the standard practice:
      typescript
      const approveAmount = (debtAmount * 101n) / 100n; // 1% buffer for accrued interest
      await walletClient.writeContract({
        address: debtTokenAddress,
        abi: erc20Abi,
        functionName: 'approve',
        args: [vaultAddress, approveAmount],
      });
      If you approve exactly debtAmount and interest accrues before your operate call lands, the vault's transferFrom will revert because the approval is insufficient.
    • Native Tokens (ETH): If the debt is in native ETH, send the repay amount as the value of the operate transaction. No approval is needed.
  2. Withdraw Collateral:

    • Collateral is withdrawn to the to_ address specified in the operate call.
    • If the collateral is a native token, it will be sent as native ETH to the recipient.
  3. Repay and Withdraw Action:

    • Use negative values for newCol_ to withdraw collateral and negative values for newDebt_ to repay debt.
    • Transaction data:
      typescript
      {
        to: vaultAddress,
        data: encodeFunctionData({
          abi: vaultT1Abi,
          functionName: 'operate',
          args: [
             nftId,                  // The specific NFT ID for your position
             -withdrawCollateralWei, // Negative value to withdraw collateral
             -repayAmountWei,        // Negative value to repay debt
             accountAddress          // Address to receive withdrawn collateral
          ]
        }),
        value: isNativeDebt ? repayAmountWei : 0n // Send value if repaying native ETH
      }
  4. Repay & Withdraw Max (Close Position):

    • To fully repay debt and withdraw all collateral (closing the position), use the minimum possible int256 value (-57896044618658097711785492504343953926634992332820282019728792003956564819968).
    • Using this value ensures all remaining amounts are processed regardless of small interest accruals.
      typescript
      const minInt256 = -57896044618658097711785492504343953926634992332820282019728792003956564819968n;
      
      // In operate call
      args: [nftId, minInt256, minInt256, accountAddress]
  5. Operate ABI:

    typescript
    export const vaultT1Abi = [
      {
        name: "operate",
        type: "function",
        stateMutability: "payable",
        inputs: [
          { name: "nftId_", type: "uint256" },
          { name: "newCol_", type: "int256" },
          { name: "newDebt_", type: "int256" },
          { name: "to_", type: "address" },
        ],
        outputs: [
          { name: "nftId", type: "uint256" },
          { name: "supplyAmt", type: "int256" },
          { name: "borrowAmt", type: "int256" },
        ],
      },
    ];

Flow:

User → approve(debtToken, debt * 1.01) → operate(nftId, -col, -debt, ...) → Debt Repaid & Collateral Withdrawn

Full repay & withdraw example

ts
/**
 * Example: Repay borrowed USDC and withdraw WETH collateral (exact amounts).
 *
 * Flow: (1) Approve vault to spend USDC, (2) operate(nftId, -withdrawCol, -repayDebt, to).
 * Set PRIVATE_KEY, RPC_URL in .env.
 *
 * Run: pnpm run borrow:t1-repay-withdraw -- --network polygon
 */

import vaultT1Abi from "../shared/abis/vaultT1Abi";
import { formatUnits, parseUnits, encodeFunctionData, erc20Abi } from "viem";
import {
  account,
  chain,
  publicClient,
  walletClient,
} from "../shared/config.js";

const VAULT_WETH_USDC = "0xeAbBfca72F8a8bf14C4ac59e69ECB2eB69F0811C";
const POLYGON_USDC = "0x3c499c542cef5e3811e1192ce70d8cc03d5c3359";

// 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 repayAmountWei = parseUnits("1.000458", 6); // USDC - 6 decimals
  const withdrawCollateralWei = parseUnits("0.001000028414875511", 18); // wEth 18 decimals

  // Approve 1.01 times the repay amount to add a slight buffer for rounding or fees
  const approveAmount = (repayAmountWei * 101n) / 100n;

  console.log("Repay & withdraw (exact amounts, WETH/USDC on Polygon)");
  console.log("  Vault:", VAULT_WETH_USDC);
  console.log("  NFT ID:", nftId.toString());
  console.log("  Repay:", formatUnits(repayAmountWei, 6), "USDC");
  console.log(
    "  Withdraw collateral:",
    formatUnits(withdrawCollateralWei, 18),
    "WETH",
  );

  // Step 1 - Approve the vault to spend USDC
  const approveData = encodeFunctionData({
    abi: erc20Abi,
    functionName: "approve",
    args: [VAULT_WETH_USDC, approveAmount],
  });
  const approveHash = await walletClient.sendTransaction({
    to: POLYGON_USDC,
    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 debt and withdraw collateral
  const operateData = encodeFunctionData({
    abi: vaultT1Abi,
    functionName: "operate",
    args: [nftId, -withdrawCollateralWei, -repayAmountWei, account.address],
  });

  const hash = await walletClient.sendTransaction({
    to: VAULT_WETH_USDC,
    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. USDC repaid, WETH withdrawn.");
}

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

Close position (repay & withdraw max) example

ts
/**
 * Example: Repay all borrowed USDC and withdraw all WETH collateral (close position fully).
 *
 * Flow: (1) Approve vault a large USDC amount, (2) operate(nftId, INT256_MIN, INT256_MIN, to).
 * Set PRIVATE_KEY, RPC_URL in .env.
 *
 * Run: pnpm run borrow:t1-repay-withdraw-max -- --network polygon
 */

import vaultT1Abi from "../shared/abis/vaultT1Abi";
import { encodeFunctionData, erc20Abi, parseUnits } from "viem";
import { minInt } from "../shared/utils/constants";
import {
  account,
  chain,
  publicClient,
  walletClient,
} from "../shared/config.js";

const VAULT_WETH_USDC = "0xeAbBfca72F8a8bf14C4ac59e69ECB2eB69F0811C";
const POLYGON_USDC = "0x3c499c542cef5e3811e1192ce70d8cc03d5c3359";

// Set your NFT_ID here, e.g. const nftId = "123";
const NFT_ID = "";

async function main() {
  if (!NFT_ID.trim()) {
    console.error("Set NFT_ID in .env");
    process.exit(1);
  }
  const nftId = BigInt(NFT_ID.trim());
  const actualAmountWei = parseUnits("1.00085", 6);
  const approveAmount = (actualAmountWei * 101n) / 100n;

  console.log("Repay & withdraw MAX (close position, WETH/USDC on Polygon)");
  console.log("  Vault:", VAULT_WETH_USDC);
  console.log("  NFT ID:", nftId.toString());

  const approveData = encodeFunctionData({
    abi: erc20Abi,
    functionName: "approve",
    args: [VAULT_WETH_USDC, approveAmount],
  });

  // Step 1 - Approve the vault to spend USDC
  const approveHash = await walletClient.sendTransaction({
    to: POLYGON_USDC,
    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 max debt and withdraw max collateral
  const operateData = encodeFunctionData({
    abi: vaultT1Abi,
    functionName: "operate",
    args: [nftId, BigInt(minInt), BigInt(minInt), account.address],
  });

  const hash = await walletClient.sendTransaction({
    to: VAULT_WETH_USDC,
    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 closed. USDC repaid, WETH withdrawn (max).");
}

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