Skip to content

Deposit & Borrow (T3 Vault)

T3 vaults use single-token collateral (e.g. ETH) and dual-token debt (e.g. USDC/USDT). Supports both operate() (token-amount based) and operatePerfect() (share-based).

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(): 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 calculateDebtSharesMinMax to compute debtSharesMinMax for the desired borrow amounts.

  1. Collateral:

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

    typescript
    {
      to: vaultAddress,
      data: encodeFunctionData({
        abi: vaultT3Abi,
        functionName: 'operate',
        args: [
           nftId,           // 0 for new position
           newCol,          // Positive: deposit collateral (e.g. ETH)
           newDebtToken0,   // Positive: borrow token0 (e.g. USDC)
           newDebtToken1,   // Positive: borrow token1 (e.g. USDT)
           debtSharesMinMax,// Max shares to mint (from calculateDebtSharesMinMax)
           accountAddress
        ]
      }),
      value: isColNative ? newCol : 0n
    }

Function: operatePerfect()

Deposits collateral and borrows debt using share-based amounts. Use calculatePerfectDebtAmounts for perfectDebtShares and token min/max for slippage.

  1. Transaction:
    typescript
    {
      to: vaultAddress,
      data: encodeFunctionData({
        abi: vaultT3Abi,
        functionName: 'operatePerfect',
        args: [
           nftId,
           newCol,
           perfectDebtShares,
           debtToken0MinMax,
           debtToken1MinMax,
           accountAddress
        ]
      }),
      value: isColNative ? newCol : 0n
    }

Operate ABI

typescript
export const vaultT3Abi = [
  {
    name: "operate",
    type: "function",
    stateMutability: "payable",
    inputs: [
      { name: "nftId_", type: "uint256" },
      { name: "newCol_", 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: "newCol_", 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 ETH, borrow USDC + USDT (T3 vault) using operate().
 *
 * Flow:
 *  (1) Approve ERC20 collateral if needed; native ETH sent as value.
 *  (2) Call operate() to deposit collateral and borrow.
 * Set PRIVATE_KEY and RPC_URL in .env.
 *
 * Run: pnpm run borrow:t3-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 { calculateDebtSharesMinMax } from "../shared/utils/dex/shares.js";
import { vaultT3Abi } from "../shared/abis/vaultT3Abi.js";

const SLIPPAGE = 0.01;
const vaultAddress =
  "0xaD439b9D61b25af1ca4Cd211E3eCb9AfBaAEd84a" as `0x${string}`; // ETH - USDC/USDT T3 vault

const supply0Wei = parseUnits("0.001", 18); // Eth (native)
const borrow0Wei = parseUnits("0.1", 6); // USDC
const borrow1Wei = parseUnits("0.1", 6); // USDT
const isToken0Native = true; // Flag to indicate if supply is native or not.

async function main() {
  const vault = await fetchVaultApi(chain.id, vaultAddress);
  if (!vault.borrowToken.token1)
    throw new Error("T3 vault must have borrowToken.token1");

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

  const debtSharesMinMax = BigInt(debtResult.sharesWithSlippage);

  console.log("T3 operate: deposit collateral, borrow (Arbitrum)");
  console.log("  Vault:", vaultAddress);
  console.log("  debtSharesMinMax:", debtSharesMinMax.toString());
  console.log(
    "Borrow:",
    formatUnits(borrow0Wei, vault.borrowToken.token0.decimals),
    vault.borrowToken.token0.symbol,
    ",",
    formatUnits(borrow1Wei, vault.borrowToken.token1.decimals),
    vault.borrowToken.token1.symbol,
  );

  // Step 1 - Approve ERC20 collateral (weETH). Token1 is native ETH; send as value.
  if (!isToken0Native) {
    const data = encodeFunctionData({
      abi: erc20Abi,
      functionName: "approve",
      args: [vaultAddress, supply0Wei],
    });
    const hash = await walletClient.sendTransaction({
      to: vault.supplyToken.token0.address,
      data,
      account,
    });
    await publicClient.waitForTransactionReceipt({ hash });
    console.log("  Approve tx:", hash);
  }

  const operateData = encodeFunctionData({
    abi: vaultT3Abi,
    functionName: "operate",
    args: [
      0n,
      supply0Wei,
      borrow0Wei,
      borrow1Wei,
      debtSharesMinMax,
      account.address,
    ],
  });

  const hash = await walletClient.sendTransaction({
    to: vaultAddress,
    data: operateData,
    // Send native ETH for the ETH collateral leg; zero otherwise.
    value: isToken0Native ? supply0Wei : 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 ETH, borrow USDC + USDT (T3 vault) using operatePerfect().
 *
 * Flow:
 *  (1) Approve ERC20 collateral if needed; native ETH sent as value.
 *  (2) Call operatePerfect() to deposit collateral and borrow.
 * Set PRIVATE_KEY and RPC_URL in .env.
 *
 * Run: pnpm run borrow:t3-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 { calculatePerfectDebtAmounts } from "../shared/utils/dex/perfectAmounts.js";
import { vaultT3Abi } from "../shared/abis/vaultT3Abi.js";

const SLIPPAGE = 0.01;
const vaultAddress =
  "0xaD439b9D61b25af1ca4Cd211E3eCb9AfBaAEd84a" as `0x${string}`; // ETH - USDC/USDT T3 vault

const supply0Wei = parseUnits("0.1", 18); // Eth (native)
const borrow0Wei = parseUnits("1", 6); // USDC
const borrow1Wei = parseUnits("1", 6); // USDT
const isToken0Native = true; // Flag to indicate if supply is native or not.

async function main() {
  const vault = await fetchVaultApi(chain.id, vaultAddress);
  if (!vault.borrowToken.token1)
    throw new Error("T3 vault must have borrowToken.token1");

  // calculatePerfectDebtAmounts: pass only borrow0; borrow1 is auto-computed from pool proportion
  const perfectDebtAmounts = calculatePerfectDebtAmounts({
    vault,
    borrowSlippage: SLIPPAGE,
    paybackAndWithdraw: false,
    proportion: "fixed",
    borrow0AmountWei: borrow0Wei.toString(),
    borrow1AmountWei: "",
  });

  if (!perfectDebtAmounts)
    throw new Error("calculatePerfectDebtAmounts failed");

  const debtToken0MinMax = perfectDebtAmounts.token0AmtWithSlippage;
  const debtToken1MinMax = perfectDebtAmounts.token1AmtWithSlippage;
  const perfectDebtShares = BigInt(perfectDebtAmounts.shares);

  console.log("T3 operatePerfect: deposit collateral, borrow (Arbitrum)");
  console.log("  Vault:", vaultAddress);
  console.log("  perfectDebtShares:", perfectDebtShares.toString());
  console.log("  debtToken0MinMax:", debtToken0MinMax.toString());
  console.log("  debtToken1MinMax:", debtToken1MinMax.toString());
  console.log(
    "Borrow:",
    formatUnits(borrow0Wei, vault.borrowToken.token0.decimals),
    vault.borrowToken.token0.symbol,
    ",",
    formatUnits(borrow1Wei, vault.borrowToken.token1.decimals),
    vault.borrowToken.token1.symbol,
  );

  // Step 1 - Approve ERC20 collateral (weETH). Token1 is native ETH; send as value.
  if (!isToken0Native) {
    const data = encodeFunctionData({
      abi: erc20Abi,
      functionName: "approve",
      args: [vaultAddress, supply0Wei],
    });
    const hash = await walletClient.sendTransaction({
      to: vault.supplyToken.token0.address,
      data,
      account,
    });
    await publicClient.waitForTransactionReceipt({ hash });
    console.log("  Approve tx:", hash);
  }

  const operateData = encodeFunctionData({
    abi: vaultT3Abi,
    functionName: "operatePerfect",
    args: [
      0n,
      supply0Wei,
      perfectDebtShares,
      debtToken0MinMax,
      debtToken1MinMax,
      account.address,
    ],
  });

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