Skip to content

Deposit & Borrow (T4 Vault)

T4 vaults use LP-style collateral (e.g. USDai/USDC) and dual-token debt (e.g. USDC/USDT). Supports 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() collateral: you only need to pass supply0AmountWei (or supply1AmountWei). The other amount is automatically computed from the pool proportion.

Helper: calculateDebtSharesMinMax

For borrow: pass both token amounts you wish to borrow (borrow0AmountInWei, borrow1AmountInWei). The function returns min/max shares for operate().

Helper: calculatePerfectDebtAmounts

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

Function: operate()

Deposits collateral and borrows debt using explicit token amounts. Use calculateColSharesMinMax for colSharesMinMax and calculateDebtSharesMinMax for debtSharesMinMax.

  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: vaultT4Abi,
        functionName: 'operate',
        args: [
           nftId,           // 0 for new position
           colToken0,       // Positive: deposit token0
           colToken1,       // Positive: deposit token1
           colSharesMinMax, // Max shares to mint (from calculateColSharesMinMax)
           debtToken0,      // Positive: borrow token0
           debtToken1,      // Positive: borrow token1
           debtSharesMinMax,// Max shares to mint (from calculateDebtSharesMinMax)
           accountAddress
        ]
      }),
      value: isToken0Native ? colToken0 : isToken1Native ? colToken1 : 0n
    }

Function: operatePerfect()

Deposits collateral and borrows debt using share-based amounts. Use calculatePerfectColAmounts for collateral and calculatePerfectDebtAmounts for debt; each auto-computes the second token from pool proportion.

  1. Transaction:
    typescript
    {
      to: vaultAddress,
      data: encodeFunctionData({
        abi: vaultT4Abi,
        functionName: 'operatePerfect',
        args: [
           nftId,
           perfectColShares,
           colToken0MinMax,
           colToken1MinMax,
           perfectDebtShares,
           debtToken0MinMax,
           debtToken1MinMax,
           accountAddress
        ]
      }),
      value: isToken0Native ? colToken0MinMax : isToken1Native ? colToken1MinMax : 0n
    }

Operate ABI

typescript
export const vaultT4Abi = [
  {
    name: "operate",
    type: "function",
    stateMutability: "payable",
    inputs: [
      { name: "nftId_", type: "uint256" },
      { name: "newColToken0_", type: "int256" },
      { name: "newColToken1_", type: "int256" },
      { name: "colSharesMinMax_", type: "int256" },
      { name: "newDebtToken0_", type: "int256" },
      { name: "newDebtToken1_", type: "int256" },
      { name: "debtSharesMinMax_", 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: "perfectDebtShares_", type: "int256" },
      { name: "debtToken0MinMax_", type: "int256" },
      { name: "debtToken1MinMax_", type: "int256" },
      { name: "to_", type: "address" },
    ],
    outputs: [
      { name: "nftId", type: "uint256" },
      { name: "r_", type: "int256[]" },
    ],
  },
];

Example: operate()

ts
/**
 * Example: Deposit USDai + USDC (LP), borrow USDC + USDT (T4 vault) using operate().
 *
 * Flow:
 *  (1) Approve both supply tokens; native ETH sent as value if applicable.
 *  (2) Call operate() to deposit collateral and borrow.
 * Set PRIVATE_KEY and RPC_URL in .env.
 *
 * Run: pnpm run borrow:t4-deposit-borrow -- --network arbitrum
 */

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

const SLIPPAGE = 0.01;
const vaultAddress =
  "0x528CF7DBBff878e02e48E83De5097F8071af768D" as `0x${string}`; // USDai,USDC/USDC,USDT T4 vault

const supply0Wei = parseUnits("1.5", 18); // USDai
const supply1Wei = parseUnits("1.5", 6); // USDC
const borrow0Wei = parseUnits("0.5", 6); // USDC
const borrow1Wei = parseUnits("0.5", 6); // USDT

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("T4 vault must have supplyToken.token1");
  if (!vault.borrowToken.token1)
    throw new Error("T4 vault must have borrowToken.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");

  // calculateDebtSharesMinMax: pass both tokens you wish to borrow; returns debtSharesMinMax for operate()
  const debtResult = calculateDebtSharesMinMax({
    vault,
    supply0AmountInWei: "",
    supply1AmountInWei: "",
    borrow0AmountInWei: borrow0Wei.toString(),
    borrow1AmountInWei: borrow1Wei.toString(),
    supplySlippage: 0,
    borrowSlippage: SLIPPAGE,
    depositAndBorrow: true,
  });
  if (!debtResult) throw new Error("calculateDebtSharesMinMax failed");

  const colSharesMinMax = BigInt(colResult.sharesWithSlippage);
  const debtSharesMinMax = BigInt(debtResult.sharesWithSlippage);
  const colToken0 = supply0Wei;
  const colToken1 = supply1Wei;
  const token0Addr = supplyToken0.address as `0x${string}`;
  const token1Addr = supplyToken1.address as `0x${string}`;

  console.log("T4 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(borrow0Wei, vault.borrowToken.token0.decimals),
    vault.borrowToken.token0.symbol,
    ",",
    formatUnits(borrow1Wei, vault.borrowToken.token1.decimals),
    vault.borrowToken.token1.symbol,
  );
  console.log("  debtSharesMinMax:", debtSharesMinMax.toString());

  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 both supply tokens (or send value for native)
  await approve(token0Addr, colToken0);
  await approve(token1Addr, colToken1);

  // Step 2 - Call operate() to deposit collateral and borrow
  const operateData = encodeFunctionData({
    abi: vaultT4Abi,
    functionName: "operate",
    args: [
      0n,
      colToken0,
      colToken1,
      colSharesMinMax,
      borrow0Wei,
      borrow1Wei,
      debtSharesMinMax,
      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 opened.");
}

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

Example: operatePerfect()

ts
/**
 * Example: Deposit USDai + USDC (LP), borrow USDC + USDT (T4 vault) using operatePerfect().
 *
 * Flow:
 *  (1) Approve both supply tokens (USDai, USDC); no native ETH in this vault.
 *  (2) Call operatePerfect() to deposit collateral and borrow.
 * Set PRIVATE_KEY and RPC_URL in .env.
 *
 * Run: pnpm run borrow:t4-deposit-borrow-perfect -- --network arbitrum
 */

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

const SLIPPAGE = 0.01;
const vaultAddress =
  "0x528CF7DBBff878e02e48E83De5097F8071af768D" as `0x${string}`; // USDai,USDC/USDC,USDT T4 vault

// Pass only supply0; supply1 is auto-computed from pool proportion
const supply0Wei = parseUnits("1.5", 18); // USDai
// Pass only borrow0; borrow1 is auto-computed from pool proportion
const borrow0Wei = parseUnits("0.5", 6); // USDC

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("T4 vault must have supplyToken.token1");
  if (!vault.borrowToken.token1)
    throw new Error("T4 vault must have borrowToken.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");

  // calculatePerfectDebtAmounts: pass only borrow0; borrow1 is auto-computed from pool proportion
  const perfectDebt = calculatePerfectDebtAmounts({
    vault,
    borrowSlippage: SLIPPAGE,
    paybackAndWithdraw: false,
    proportion: "fixed",
    borrow0AmountWei: borrow0Wei.toString(),
    borrow1AmountWei: "",
  });
  if (!perfectDebt)
    throw new Error(
      "calculatePerfectDebtAmounts failed: perfectDebt is undefined",
    );

  const perfectColShares = BigInt(perfectCol.shares);
  const colToken0MinMax = BigInt(perfectCol.token0AmtWithSlippage);
  const colToken1MinMax = BigInt(perfectCol.token1AmtWithSlippage);
  const perfectDebtShares = BigInt(perfectDebt.shares);
  const debtToken0MinMax = BigInt(perfectDebt.token0AmtWithSlippage);
  const debtToken1MinMax = BigInt(perfectDebt.token1AmtWithSlippage);

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

  console.log("T4 operatePerfect: deposit collateral, borrow (Arbitrum)");
  console.log("  Vault:", vaultAddress);
  console.log(
    "  Deposit:",
    formatUnits(colToken0MinMax, supplyToken0.decimals),
    supplyToken0.symbol,
    "+",
    formatUnits(colToken1MinMax, supplyToken1.decimals),
    supplyToken1.symbol,
  );
  console.log(
    "  Borrow:",
    formatUnits(debtToken0MinMax, vault.borrowToken.token0.decimals),
    vault.borrowToken.token0.symbol,
    "+",
    formatUnits(debtToken1MinMax, vault.borrowToken.token1.decimals),
    vault.borrowToken.token1.symbol,
  );
  console.log("  perfectColShares:", perfectColShares.toString());
  console.log("  perfectDebtShares:", perfectDebtShares.toString());

  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 both supply tokens
  await approve(token0Addr, colToken0MinMax);
  await approve(token1Addr, colToken1MinMax);

  // Step 2 - Call operatePerfect() to deposit collateral and borrow
  const operateData = encodeFunctionData({
    abi: vaultT4Abi,
    functionName: "operatePerfect",
    args: [
      0n,
      perfectColShares,
      colToken0MinMax,
      colToken1MinMax,
      perfectDebtShares,
      debtToken0MinMax,
      debtToken1MinMax,
      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 opened.");
}

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