Repay & Withdraw (T3 Vault)
T3 vaults use single-token collateral (e.g. ETH) and dual-token debt (e.g. USDC/USDT). Use the NFT API (fetchNft) to get position and vault data. Supports both operate() (token-amount based) and operatePerfect() (share-based).
Helper: calculateDebtSharesMinMax (payback)
For payback: based on your initial borrow, pass one token amount if you want to repay entirely in that token, or pass both amounts in any desired split. Use depositAndBorrow: false.
Helper: calculatePerfectDebtAmounts (payback)
Pass borrow0AmountWei (or borrow1AmountWei) only; the other amount is auto-computed from pool proportion. Use paybackAndWithdraw: true.
Repay Debt
- Approve the vault to spend both borrow tokens (e.g. USDC, USDT). Use a 1% buffer for accrued interest:typescript
const approveAmt0 = (repay0Wei * 101n) / 100n; const approveAmt1 = (repay1Wei * 101n) / 100n;
Exact amounts: operate()
For exact token amounts, use calculateDebtSharesMinMax with depositAndBorrow: false to get debtSharesMinMax, then pass negative amounts:
const nft = await fetchNft(chainId, nftId);
const debtResult = calculateDebtSharesMinMax({
vault: nft.vault,
borrow0AmountInWei: repay0Wei.toString(),
borrow1AmountInWei: repay1Wei.toString(),
borrowSlippage: SLIPPAGE,
depositAndBorrow: false,
});
// operate() with negative values for repay-and-withdraw
args: [
nftId,
-withdrawColWei,
-repay0Wei,
-repay1Wei,
BigInt(debtResult.sharesWithSlippage),
accountAddress
]Exact amounts: operatePerfect()
Use calculatePerfectDebtAmounts for perfectDebtShares, and negative token min/max for slippage:
const debtResult = calculatePerfectDebtAmounts({
vault: nft.vault,
borrowSlippage: SLIPPAGE,
paybackAndWithdraw: true,
proportion: "fixed",
borrow0AmountWei: repay0Wei.toString(),
borrow1AmountWei: "", // auto-computed from pool proportion
});
const perfectDebtShares = -BigInt(debtResult.shares);
const debtToken0MinMax = -BigInt(debtResult.token0AmtWithSlippage);
const debtToken1MinMax = -BigInt(debtResult.token1AmtWithSlippage);
args: [nftId, -withdrawColWei, perfectDebtShares, debtToken0MinMax, debtToken1MinMax, accountAddress]Max (close position): operatePerfect()
For full close with operatePerfect(), use minInt for collateral and debt shares:
newCol = minIntperfectDebtShares = minIntdebtToken0MinMax = -paybackResult.token0AmtWithSlippagedebtToken1MinMax = -paybackResult.token1AmtWithSlippage
Examples
Exact amounts with operate():
/**
* Example: Repay borrowed USDC + USDT and withdraw ETH collateral (exact amounts) using operate().
*
* Flow:
* (1) Fetch NFT, compute debt shares for repay via calculateDebtSharesMinMax.
* (2) Approve vault to spend USDC and USDT for repay.
* (3) Call operate() with negative newCol, newDebtToken0, newDebtToken1, debtSharesMinMax.
* Set PRIVATE_KEY and RPC_URL in .env.
*
* Run: pnpm run borrow:t3-withdraw-repay -- --network arbitrum
*/
import { encodeFunctionData, erc20Abi, formatUnits, parseUnits } from "viem";
import { vaultT3Abi } from "../shared/abis/vaultT3Abi.js";
import {
account,
chain,
publicClient,
walletClient,
} from "../shared/config.js";
import { fetchNft } from "../shared/utils/utils.js";
import { calculateDebtSharesMinMax } from "../shared/utils/dex/shares.js";
const SLIPPAGE = 0.1;
// Set your NFT position ID here
const NFT_ID = "";
// Exact amounts
const withdrawColWei = parseUnits("0.000100086894324", 18); // ETH
const repay0Wei = parseUnits("0.2", 6); // USDC
const repay1Wei = parseUnits("0.01", 6); // USDT
async function main() {
if (!NFT_ID.trim()) {
console.error("Set NFT_ID in this file");
process.exit(1);
}
const nftId = BigInt(NFT_ID.trim());
const nft = await fetchNft(chain.id, NFT_ID.trim());
const vault = nft.vault;
const vaultAddr = vault.address as `0x${string}`;
if (!vault.borrowToken.token1)
throw new Error("T3 vault must have borrowToken.token1");
// calculateDebtSharesMinMax (payback): pass one token if repaying all from it, or both in any split
const debtResult = calculateDebtSharesMinMax({
vault,
supply0AmountInWei: "",
supply1AmountInWei: "",
borrow0AmountInWei: repay0Wei.toString(),
borrow1AmountInWei: "",
supplySlippage: 0,
borrowSlippage: SLIPPAGE,
depositAndBorrow: false,
});
if (!debtResult) throw new Error("calculateDebtSharesMinMax failed");
const newCol = -withdrawColWei;
const newDebtToken0 = -repay0Wei;
const newDebtToken1 = -repay1Wei;
const debtSharesMinMax = BigInt(debtResult.sharesWithSlippage);
console.log("T3 operate: repay & withdraw (exact amounts, Arbitrum)");
console.log(" Vault:", vaultAddr);
console.log(" NFT ID:", nftId.toString());
console.log(
" Repay:",
formatUnits(repay0Wei, vault.borrowToken.token0.decimals),
vault.borrowToken.token0.symbol,
"+",
formatUnits(repay1Wei, vault.borrowToken.token1.decimals),
vault.borrowToken.token1.symbol,
);
console.log(
" Withdraw col:",
formatUnits(withdrawColWei, vault.supplyToken.token0.decimals),
vault.supplyToken.token0.symbol,
);
const approveAmt0 = (repay0Wei * 101n) / 100n;
const approveAmt1 = (repay1Wei * 101n) / 100n;
const approve = async (token: `0x${string}`, amount: bigint) => {
const data = encodeFunctionData({
abi: erc20Abi,
functionName: "approve",
args: [vaultAddr, amount],
});
const hash = await walletClient.sendTransaction({
to: token,
data,
account,
});
await publicClient.waitForTransactionReceipt({ hash });
console.log(" Approve tx:", hash);
};
await approve(vault.borrowToken.token0.address as `0x${string}`, approveAmt0);
await approve(
vault.borrowToken.token1!.address as `0x${string}`,
approveAmt1,
);
const operateData = encodeFunctionData({
abi: vaultT3Abi,
functionName: "operate",
args: [
nftId,
newCol,
newDebtToken0,
newDebtToken1,
debtSharesMinMax,
account.address,
],
});
const hash = await walletClient.sendTransaction({
to: vaultAddr,
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 updated. USDC + USDT repaid, ETH withdrawn.");
}
main().catch((e) => {
console.error(e);
process.exit(1);
});Exact amounts with operatePerfect():
/**
* Example: Repay borrowed USDC + USDT and withdraw ETH collateral (exact amounts) using operatePerfect().
*
* Flow:
* (1) Fetch NFT, compute debt amounts for repay via calculatePerfectDebtAmounts.
* (2) Approve vault to spend USDC and USDT for repay.
* (3) Call operatePerfect() with negative newCol, perfectDebtShares, debtToken0MinMax, debtToken1MinMax.
* Set PRIVATE_KEY and RPC_URL in .env.
*
* Run: pnpm run borrow:t3-withdraw-repay-perfect -- --network arbitrum
*/
import { encodeFunctionData, erc20Abi, formatUnits, parseUnits } from "viem";
import { vaultT3Abi } from "../shared/abis/vaultT3Abi.js";
import {
account,
chain,
publicClient,
walletClient,
} from "../shared/config.js";
import { fetchNft } from "../shared/utils/utils.js";
import { calculatePerfectDebtAmounts } from "../shared/utils/dex/perfectAmounts.js";
const SLIPPAGE = 0.1;
// Set your NFT position ID here
const NFT_ID = "";
// Exact amounts
const withdrawColWei = parseUnits("0.001", 18); // ETH
const repay0Wei = parseUnits("0.001034", 6); // USDC
const repay1Wei = parseUnits("0.001007", 6); // USDT
async function main() {
if (!NFT_ID.trim()) {
console.error("Set NFT_ID in this file");
process.exit(1);
}
const nftId = BigInt(NFT_ID.trim());
const nft = await fetchNft(chain.id, NFT_ID.trim());
const vault = nft.vault;
const vaultAddr = vault.address as `0x${string}`;
if (!vault.borrowToken.token1)
throw new Error("T3 vault must have borrowToken.token1");
// calculatePerfectDebtAmounts: pass only borrow0; borrow1 auto-computed from pool proportion
const debtResult = calculatePerfectDebtAmounts({
vault,
borrowSlippage: SLIPPAGE,
paybackAndWithdraw: true,
proportion: "fixed",
borrow0AmountWei: repay0Wei.toString(),
borrow1AmountWei: "",
});
if (!debtResult) throw new Error("calculatePerfectDebtAmounts failed");
const newCol = -withdrawColWei;
const perfectDebtShares = -BigInt(debtResult.shares);
const debtToken0MinMax = -BigInt(debtResult.token0AmtWithSlippage);
const debtToken1MinMax = -BigInt(debtResult.token1AmtWithSlippage);
console.log("T3 operatePerfect: repay & withdraw (exact amounts, Arbitrum)");
console.log(" Vault:", vaultAddr);
console.log(" NFT ID:", nftId.toString());
console.log(
" Repay:",
formatUnits(repay0Wei, vault.borrowToken.token0.decimals),
vault.borrowToken.token0.symbol,
"+",
formatUnits(repay1Wei, vault.borrowToken.token1.decimals),
vault.borrowToken.token1.symbol,
);
console.log(
" Withdraw col:",
formatUnits(withdrawColWei, vault.supplyToken.token0.decimals),
vault.supplyToken.token0.symbol,
);
const approveAmt0 = (repay0Wei * 101n) / 100n;
const approveAmt1 = (repay1Wei * 101n) / 100n;
const approve = async (token: `0x${string}`, amount: bigint) => {
const data = encodeFunctionData({
abi: erc20Abi,
functionName: "approve",
args: [vaultAddr, amount],
});
const hash = await walletClient.sendTransaction({
to: token,
data,
account,
});
await publicClient.waitForTransactionReceipt({ hash });
console.log(" Approve tx:", hash);
};
await approve(vault.borrowToken.token0.address as `0x${string}`, approveAmt0);
await approve(
vault.borrowToken.token1!.address as `0x${string}`,
approveAmt1,
);
const operateData = encodeFunctionData({
abi: vaultT3Abi,
functionName: "operatePerfect",
args: [
nftId,
newCol,
perfectDebtShares,
debtToken0MinMax,
debtToken1MinMax,
account.address,
],
});
const hash = await walletClient.sendTransaction({
to: vaultAddr,
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 updated. USDC + USDT repaid, ETH withdrawn.");
}
main().catch((e) => {
console.error(e);
process.exit(1);
});Close position (operatePerfect):
/**
* Example: Repay all borrowed USDC + USDT and withdraw all ETH collateral (close position) using operatePerfect().
*
* Flow:
* (1) Fetch NFT, compute max payback amounts via calculatePerfectMaxPaybackAmounts.
* (2) Approve vault to spend USDC and USDT for max repay.
* (3) Call operatePerfect() with minInt for newCol and perfectDebtShares.
* Set PRIVATE_KEY and RPC_URL in .env.
*
* Run: pnpm run borrow:t3-withdraw-repay-max-perfect -- --network arbitrum
*/
import { encodeFunctionData, erc20Abi, formatUnits } from "viem";
import { vaultT3Abi } from "../shared/abis/vaultT3Abi.js";
import {
account,
chain,
publicClient,
walletClient,
} from "../shared/config.js";
import { fetchNft } from "../shared/utils/utils.js";
import { calculatePerfectMaxPaybackAmounts } from "../shared/utils/dex/perfectAmounts.js";
import { minInt } from "../shared/utils/constants.js";
const SLIPPAGE = 0.1;
// Set your NFT position ID here
const NFT_ID = "";
async function main() {
if (!NFT_ID.trim()) {
console.error("Set NFT_ID in this file");
process.exit(1);
}
const nftId = BigInt(NFT_ID.trim());
const nft = await fetchNft(chain.id, NFT_ID.trim());
const vault = nft.vault;
const vaultAddr = vault.address as `0x${string}`;
if (!vault.borrowToken.token1)
throw new Error("T3 vault must have borrowToken.token1");
// calculatePerfectMaxPaybackAmounts: computes max repay amounts for full close
const paybackResult = calculatePerfectMaxPaybackAmounts({
nft,
borrowSlippage: SLIPPAGE,
});
if (!paybackResult)
throw new Error("calculatePerfectMaxPaybackAmounts failed");
const repay0Wei = BigInt(paybackResult.token0AmtWithSlippage);
const repay1Wei = BigInt(paybackResult.token1AmtWithSlippage);
const newCol = BigInt(minInt);
const perfectDebtShares = BigInt(minInt);
const debtToken0MinMax = -repay0Wei;
const debtToken1MinMax = -repay1Wei;
const approveAmt0 = (repay0Wei * 101n) / 100n;
const approveAmt1 = (repay1Wei * 101n) / 100n;
console.log("T3 operatePerfect: repay max & withdraw max (Arbitrum)");
console.log(" Vault:", vaultAddr);
console.log(" NFT ID:", nftId.toString());
console.log(
" Repay: ~",
formatUnits(repay0Wei, vault.borrowToken.token0.decimals),
vault.borrowToken.token0.symbol,
"+",
formatUnits(repay1Wei, vault.borrowToken.token1.decimals),
vault.borrowToken.token1.symbol,
);
const approve = async (token: `0x${string}`, amount: bigint) => {
const data = encodeFunctionData({
abi: erc20Abi,
functionName: "approve",
args: [vaultAddr, amount],
});
const hash = await walletClient.sendTransaction({
to: token,
data,
account,
});
await publicClient.waitForTransactionReceipt({ hash });
console.log(" Approve tx:", hash);
};
await approve(vault.borrowToken.token0.address as `0x${string}`, approveAmt0);
await approve(
vault.borrowToken.token1!.address as `0x${string}`,
approveAmt1,
);
const operateData = encodeFunctionData({
abi: vaultT3Abi,
functionName: "operatePerfect",
args: [
nftId,
newCol,
perfectDebtShares,
debtToken0MinMax,
debtToken1MinMax,
account.address,
],
});
const hash = await walletClient.sendTransaction({
to: vaultAddr,
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 closed. USDC + USDT repaid, ETH withdrawn (max).");
}
main().catch((e) => {
console.error(e);
process.exit(1);
});
