Skip to content

Deposit & Borrow (T2 Vault)

T2 vaults use LP-style collateral (e.g. weETH/ETH) and support both operate() (token-amount based) and operatePerfect() (share-based).

Helper: calculateColSharesMinMax

For deposit: pass both token amounts you wish to deposit (supply0AmountInWei, supply1AmountInWei). The function returns min/max shares for operate().

Helper: calculatePerfectColAmounts

For operatePerfect(): you only need to pass supply0AmountWei (or supply1AmountWei). The other amount is automatically computed from the pool proportion.

Function: operate()

Deposits collateral and borrows debt using explicit token amounts. Use calculateColSharesMinMax to compute colSharesMinMax for the desired token amounts.

  1. Collateral:

    • ERC20: Approve vault, then call operate with value: 0.
    • Native ETH: Send the collateral leg as value; no approval needed for that leg.
  2. Transaction:

    typescript
    {
      to: vaultAddress,
      data: encodeFunctionData({
        abi: vaultT2Abi,
        functionName: 'operate',
        args: [
           nftId,           // 0 for new position
           colToken0,       // Positive: deposit token0 (e.g. weETH)
           colToken1,       // Positive: deposit token1 (e.g. ETH)
           colSharesMinMax, // Max shares to mint (from calculateColSharesMinMax)
           borrowAmount,    // Positive: borrow debt
           accountAddress
        ]
      }),
      value: isToken0Native ? colToken0 : isToken1Native ? colToken1 : 0n
    }

Function: operatePerfect()

Deposits collateral and borrows debt using share-based amounts. Use calculatePerfectColAmounts for perfectColShares and token min/max; it auto-computes the second token from pool proportion.

  1. Transaction:
    typescript
    {
      to: vaultAddress,
      data: encodeFunctionData({
        abi: vaultT2Abi,
        functionName: 'operatePerfect',
        args: [
           nftId,
           perfectColShares,
           colToken0MinMax, // Token amounts with buffer (e.g. * 101/100)
           colToken1MinMax,
           borrowAmount,
           accountAddress
        ]
      }),
      value: isToken0Native ? colToken0 : isToken1Native ? colToken1 : 0n
    }

Operate ABI

typescript
export const vaultT2Abi = [
  {
    name: "operate",
    type: "function",
    stateMutability: "payable",
    inputs: [
      { name: "nftId_", type: "uint256" },
      { name: "newColToken0_", type: "int256" },
      { name: "newColToken1_", type: "int256" },
      { name: "colSharesMinMax_", type: "int256" },
      { name: "newDebt_", type: "int256" },
      { name: "to_", type: "address" },
    ],
    outputs: [
      { name: "nftId", type: "uint256" },
      { name: "supplyAmt", type: "int256" },
      { name: "borrowAmt", type: "int256" },
    ],
  },
  {
    name: "operatePerfect",
    type: "function",
    stateMutability: "payable",
    inputs: [
      { name: "nftId_", type: "uint256" },
      { name: "perfectColShares_", type: "int256" },
      { name: "colToken0MinMax_", type: "int256" },
      { name: "colToken1MinMax_", type: "int256" },
      { name: "newDebt_", type: "int256" },
      { name: "to_", type: "address" },
    ],
    outputs: [
      { name: "nftId", type: "uint256" },
      { name: "r_", type: "int256[]" },
    ],
  },
];

Example: operate()

ts
/**
 * Example: Deposit weETH + ETH, borrow wstETH (T2 vault) using operate().
 *
 * Flow:
 *  (1) Approve ERC20 collateral (weETH); native ETH sent as value for the ETH leg.
 *  (2) Call operate() to deposit collateral and borrow.
 * Set PRIVATE_KEY and RPC_URL in .env.
 *
 * Run: pnpm run borrow:t2-deposit-borrow -- --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 { fetchVaultApi } from "../shared/utils/utils.js";
import { calculateColSharesMinMax } from "../shared/utils/dex/shares.js";

const SLIPPAGE = 0.01;
const vaultAddress =
  "0x3996464c0fCCa8183e13ea5E5e74375e2c8744Dd" as `0x${string}`; // weETH/ETH - wstETH T2 vault
const supply0Wei = parseUnits("0.000724696012379057102", 18); // weETH
const supply1Wei = parseUnits("0.0005928029528832", 18); // ETH (native)
const borrowAmount = parseUnits("0.0001", 18); // 0.0001 wstETH
// Flags to indicate which legs are native ETH for this example
const isToken0Native = false;
const isToken1Native = true;

async function main() {
  const vault = await fetchVaultApi(chain.id, vaultAddress);
  const supplyToken0 = vault.supplyToken.token0;
  const supplyToken1 = vault.supplyToken.token1;
  if (!supplyToken1) throw new Error("T2 vault must have supplyToken.token1");

  // calculateColSharesMinMax: pass both tokens you wish to deposit; returns colSharesMinMax for operate()
  const colResult = calculateColSharesMinMax({
    vault,
    supply0AmountInWei: supply0Wei.toString(),
    supply1AmountInWei: supply1Wei.toString(),
    borrow0AmountInWei: "",
    borrow1AmountInWei: "",
    supplySlippage: SLIPPAGE,
    borrowSlippage: 0,
    depositAndBorrow: true,
  });
  if (!colResult) throw new Error("calculateColSharesMinMax failed");

  const colSharesMinMax = BigInt(colResult.sharesWithSlippage);
  // Raw collateral amounts used for operate()
  const colToken0 = BigInt(supply0Wei);
  const colToken1 = BigInt(supply1Wei);
  const token0Addr = supplyToken0.address as `0x${string}`;
  const token1Addr = supplyToken1.address as `0x${string}`;

  console.log("T2 operate: deposit collateral, borrow (Arbitrum)");
  console.log("  Vault:", vaultAddress);
  console.log("  colToken0:", colToken0.toString());
  console.log("  colToken1:", colToken1.toString());
  console.log("  colSharesMinMax:", colSharesMinMax.toString());
  console.log(
    "  Borrow:",
    formatUnits(borrowAmount, vault.borrowToken.token0.decimals),
    vault.borrowToken.token0.symbol,
  );

  // Helper: send ERC20 approve transaction
  const approve = async (token: `0x${string}`, amount: bigint) => {
    const data = encodeFunctionData({
      abi: erc20Abi,
      functionName: "approve",
      args: [vaultAddress, amount],
    });
    const hash = await walletClient.sendTransaction({
      to: token,
      data,
      account,
    });
    await publicClient.waitForTransactionReceipt({ hash });
    console.log("  Approve tx:", hash);
  };

  // Step 1 - Approve ERC20 collateral (weETH). Token1 is native ETH; send as value.
  if (!isToken0Native) await approve(token0Addr, colToken0);
  if (!isToken1Native) await approve(token1Addr, colToken1);

  // Step 2 - Call operate() to deposit collateral and borrow
  const operateData = encodeFunctionData({
    abi: vaultT2Abi,
    functionName: "operate",
    args: [
      0n,
      colToken0,
      colToken1,
      colSharesMinMax,
      borrowAmount,
      account.address,
    ],
  });

  const hash = await walletClient.sendTransaction({
    to: vaultAddress,
    data: operateData,
    // Send native ETH for the ETH collateral leg; zero otherwise.
    value: isToken0Native ? colToken0 : isToken1Native ? colToken1 : 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 opened.");
}

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

Example: operatePerfect()

ts
/**
 * Example: Deposit weETH + ETH, borrow wstETH (T2 vault) using operatePerfect().
 *
 * Flow:
 *  (1) Approve ERC20 collateral (weETH); native ETH sent as value for the ETH leg.
 *  (2) Call operatePerfect() to deposit collateral and borrow.
 * Set PRIVATE_KEY and RPC_URL in .env.
 *
 * Run: pnpm run borrow:t2-deposit-borrow-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 { fetchVaultApi } from "../shared/utils/utils.js";
import { calculatePerfectColAmounts } from "../shared/utils/dex/perfectAmounts.js";

const SLIPPAGE = 0.01;
const vaultAddress =
  "0x3996464c0fCCa8183e13ea5E5e74375e2c8744Dd" as `0x${string}`; // weETH/ETH - wstETH T2 vault
const supply0Wei = parseUnits("0.0007444939633859551022", 18); // weETH
const supply1Wei = parseUnits("0.0005713045660656", 18); // ETH (native)
const borrowAmount = parseUnits("0.00001", 18); // 0.00001 wstETH
// Flags to indicate which legs are native ETH for this example
const isToken0Native = false;
const isToken1Native = true;

async function main() {
  const vault = await fetchVaultApi(chain.id, vaultAddress);
  const supplyToken0 = vault.supplyToken.token0;
  const supplyToken1 = vault.supplyToken.token1;
  if (!supplyToken1) throw new Error("T2 vault must have supplyToken.token1");

  // calculatePerfectColAmounts: pass only supply0; supply1 is auto-computed from pool proportion
  const perfectCol = calculatePerfectColAmounts({
    vault,
    supplySlippage: SLIPPAGE,
    depositAndBorrow: true,
    paybackAndWithdraw: false,
    proportion: "fixed",
    supply0AmountWei: supply0Wei.toString(),
    supply1AmountWei: "",
  });

  if (!perfectCol) {
    throw new Error(
      "calculatePerfectColAmounts failed: perfectCol is undefined",
    );
  }
  if (!supplyToken0 || !supplyToken1) {
    throw new Error("Missing supplyToken0 or supplyToken1");
  }
  const colToken0MinMax = BigInt(perfectCol.token0AmtWithSlippage);
  const colToken1MinMax = BigInt(perfectCol.token1AmtWithSlippage);
  const perfectColShares = BigInt(perfectCol.shares);

  const token0Addr = supplyToken0.address as `0x${string}`;
  const token1Addr = supplyToken1.address as `0x${string}`;

  console.log("T2 operatePerfect: deposit collateral, borrow (Arbitrum)");
  console.log("  Vault:", vaultAddress);
  console.log("  perfectColShares:", perfectColShares.toString());
  console.log("  colToken0MinMax:", colToken0MinMax.toString());
  console.log("  colToken1MinMax:", colToken1MinMax.toString());
  console.log(
    "  Borrow:",
    formatUnits(borrowAmount, vault.borrowToken.token0.decimals),
    vault.borrowToken.token0.symbol,
  );

  // Helper: send ERC20 approve transaction
  const approve = async (token: `0x${string}`, amount: bigint) => {
    const data = encodeFunctionData({
      abi: erc20Abi,
      functionName: "approve",
      args: [vaultAddress, amount],
    });
    const hash = await walletClient.sendTransaction({
      to: token,
      data,
      account,
    });
    await publicClient.waitForTransactionReceipt({ hash });
    console.log("  Approve tx:", hash);
  };

  // Step 1 - Approve ERC20 collateral (weETH). Token1 is native ETH; send as value.
  if (!isToken0Native) await approve(token0Addr, colToken0MinMax);
  if (!isToken1Native) await approve(token1Addr, colToken1MinMax);

  // Step 2 - Call operatePerfect() to deposit collateral and borrow
  const operateData = encodeFunctionData({
    abi: vaultT2Abi,
    functionName: "operatePerfect",
    args: [
      0n,
      perfectColShares,
      colToken0MinMax,
      colToken1MinMax,
      borrowAmount,
      account.address,
    ],
  });

  const hash = await walletClient.sendTransaction({
    to: vaultAddress,
    data: operateData,
    // Send native ETH for the ETH collateral leg; zero otherwise.
    value: isToken0Native
      ? colToken0MinMax
      : isToken1Native
        ? colToken1MinMax
        : 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 opened.");
}

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