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.
Collateral:
- ERC20: Approve vault, then call
operatewithvalue: 0. - Native ETH: Send the collateral as
value; no approval needed.
- ERC20: Approve vault, then call
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.
- Transaction:typescript
{ to: vaultAddress, data: encodeFunctionData({ abi: vaultT3Abi, functionName: 'operatePerfect', args: [ nftId, newCol, perfectDebtShares, debtToken0MinMax, debtToken1MinMax, accountAddress ] }), value: isColNative ? newCol : 0n }
Operate ABI
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()
/**
* 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()
/**
* 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);
});
