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.
Collateral:
- ERC20: Approve vault, then call
operatewithvalue: 0. - Native ETH: Send the collateral leg as
value; no approval needed for that leg.
- ERC20: Approve vault, then call
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.
- 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
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()
/**
* 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()
/**
* 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);
});
