hostbasket/actix_mvc_app/static/js/defi.js
Timur Gordon 19f8700b78 feat: Implement comprehensive DeFi platform in Digital Assets dashboard
Add a complete DeFi platform with the following features:
- Tabbed interface for different DeFi functionalities
- Lending & Borrowing system with APY calculations
- Liquidity Pools with LP token rewards
- Staking options for tokens and digital assets
- Token Swap interface with real-time exchange rates
- Collateralization system for loans and synthetic assets
- Interactive JavaScript functionality for real-time calculations

This enhancement provides users with a complete suite of DeFi tools
directly integrated into the Digital Assets dashboard.
2025-04-29 01:11:51 +02:00

572 lines
28 KiB
JavaScript

// DeFi Platform JavaScript Functionality
document.addEventListener('DOMContentLoaded', function() {
// Initialize tooltips
var tooltipTriggerList = [].slice.call(document.querySelectorAll('[data-bs-toggle="tooltip"]'));
var tooltipList = tooltipTriggerList.map(function (tooltipTriggerEl) {
return new bootstrap.Tooltip(tooltipTriggerEl);
});
// =============== LENDING & BORROWING TAB ===============
// Lending form calculations
const lendingAmountInput = document.getElementById('lendingAmount');
const lendingAssetSelect = document.getElementById('lendingAsset');
const lendingTermSelect = document.getElementById('lendingTerm');
const estimatedReturnsElement = document.getElementById('estimatedReturns');
const totalReturnElement = document.getElementById('totalReturn');
if (lendingAmountInput && lendingAssetSelect && lendingTermSelect) {
const calculateLendingReturns = () => {
const amount = parseFloat(lendingAmountInput.value) || 0;
const asset = lendingAssetSelect.value;
const termDays = parseInt(lendingTermSelect.value) || 30;
// Get APY from the selected option's text
const selectedOption = lendingTermSelect.options[lendingTermSelect.selectedIndex];
const apyMatch = selectedOption.text.match(/\((\d+\.\d+)%\)/);
const apy = apyMatch ? parseFloat(apyMatch[1]) / 100 : 0.05; // Default to 5% if not found
// Calculate returns (simple interest for demonstration)
const returns = amount * apy * (termDays / 365);
const total = amount + returns;
if (estimatedReturnsElement) {
estimatedReturnsElement.textContent = returns.toFixed(2) + ' ' + asset;
}
if (totalReturnElement) {
totalReturnElement.textContent = total.toFixed(2) + ' ' + asset;
}
};
lendingAmountInput.addEventListener('input', calculateLendingReturns);
lendingAssetSelect.addEventListener('change', calculateLendingReturns);
lendingTermSelect.addEventListener('change', calculateLendingReturns);
}
// Borrowing form calculations
const borrowingAmountInput = document.getElementById('borrowingAmount');
const borrowingAssetSelect = document.getElementById('borrowingAsset');
const borrowingTermSelect = document.getElementById('borrowingTerm');
const borrowingCollateralSelect = document.getElementById('collateralAsset');
const borrowingCollateralAmountInput = document.getElementById('collateralAmount');
const interestDueElement = document.getElementById('interestDue');
const totalRepaymentElement = document.getElementById('totalRepayment');
const borrowingCollateralRatioElement = document.getElementById('collateralRatio');
if (borrowingAmountInput && borrowingAssetSelect && borrowingCollateralSelect && borrowingCollateralAmountInput) {
const calculateBorrowingDetails = () => {
const amount = parseFloat(borrowingAmountInput.value) || 0;
const asset = borrowingAssetSelect.value;
const termDays = parseInt(borrowingTermSelect.value) || 30;
// Get APR from the selected option's text
const selectedOption = borrowingTermSelect.options[borrowingTermSelect.selectedIndex];
const aprMatch = selectedOption.text.match(/\((\d+\.\d+)%\)/);
const apr = aprMatch ? parseFloat(aprMatch[1]) / 100 : 0.08; // Default to 8% if not found
// Calculate interest and total repayment
const interest = amount * apr * (termDays / 365);
const total = amount + interest;
if (interestDueElement) {
interestDueElement.textContent = interest.toFixed(2) + ' ' + asset;
}
if (totalRepaymentElement) {
totalRepaymentElement.textContent = total.toFixed(2) + ' ' + asset;
}
// Calculate collateral ratio
const collateralAmount = parseFloat(borrowingCollateralAmountInput.value) || 0;
const collateralAsset = borrowingCollateralSelect.value;
let collateralValue = 0;
// Mock prices for demonstration
const assetPrices = {
'TFT': 0.5,
'ZAZ': 0.5,
'USDT': 1.0
};
if (collateralAsset in assetPrices) {
collateralValue = collateralAmount * assetPrices[collateralAsset];
} else {
// For other assets, assume the value is the amount (simplified)
collateralValue = collateralAmount;
}
const borrowValue = amount * (asset === 'USDT' ? 1 : assetPrices[asset] || 0.5);
const ratio = borrowValue > 0 ? (collateralValue / borrowValue * 100) : 0;
if (borrowingCollateralRatioElement) {
borrowingCollateralRatioElement.textContent = ratio.toFixed(0) + '%';
// Update color based on ratio
if (ratio >= 200) {
borrowingCollateralRatioElement.className = 'text-success';
} else if (ratio >= 150) {
borrowingCollateralRatioElement.className = 'text-warning';
} else {
borrowingCollateralRatioElement.className = 'text-danger';
}
}
};
borrowingAmountInput.addEventListener('input', calculateBorrowingDetails);
borrowingAssetSelect.addEventListener('change', calculateBorrowingDetails);
borrowingTermSelect.addEventListener('change', calculateBorrowingDetails);
borrowingCollateralSelect.addEventListener('change', calculateBorrowingDetails);
borrowingCollateralAmountInput.addEventListener('input', calculateBorrowingDetails);
}
// =============== LIQUIDITY POOLS TAB ===============
// Add Liquidity form calculations
const poolSelect = document.getElementById('liquidityPool');
const token1AmountInput = document.getElementById('token1Amount');
const token2AmountInput = document.getElementById('token2Amount');
const lpTokensElement = document.getElementById('lpTokensReceived');
const poolShareElement = document.getElementById('poolShare');
if (poolSelect && token1AmountInput && token2AmountInput) {
const calculateLiquidityDetails = () => {
const token1Amount = parseFloat(token1AmountInput.value) || 0;
const token2Amount = parseFloat(token2AmountInput.value) || 0;
// Mock calculations for demonstration
const lpTokens = Math.sqrt(token1Amount * token2Amount);
const poolShare = token1Amount > 0 ? (lpTokens / (lpTokens + 1000) * 100) : 0;
if (lpTokensElement) {
lpTokensElement.textContent = lpTokens.toFixed(2);
}
if (poolShareElement) {
poolShareElement.textContent = poolShare.toFixed(2) + '%';
}
};
token1AmountInput.addEventListener('input', calculateLiquidityDetails);
token2AmountInput.addEventListener('input', calculateLiquidityDetails);
// Handle pool selection to update token labels
poolSelect.addEventListener('change', function() {
const selectedOption = poolSelect.options[poolSelect.selectedIndex];
const token1Label = document.getElementById('token1Label');
const token2Label = document.getElementById('token2Label');
if (selectedOption.value === 'tft-zaz') {
if (token1Label) token1Label.textContent = 'TFT';
if (token2Label) token2Label.textContent = 'ZAZ';
} else if (selectedOption.value === 'zaz-usdt') {
if (token1Label) token1Label.textContent = 'ZAZ';
if (token2Label) token2Label.textContent = 'USDT';
}
calculateLiquidityDetails();
});
}
// =============== STAKING TAB ===============
// TFT Staking calculations
const tftStakeAmountInput = document.getElementById('tftStakeAmount');
const tftStakingPeriodSelect = document.getElementById('tftStakingPeriod');
const tftEstimatedRewardsElement = document.getElementById('tftEstimatedRewards');
if (tftStakeAmountInput && tftStakingPeriodSelect && tftEstimatedRewardsElement) {
const calculateTftStakingRewards = () => {
const amount = parseFloat(tftStakeAmountInput.value) || 0;
const termDays = parseInt(tftStakingPeriodSelect.value) || 30;
// Get APY from the selected option's text
const selectedOption = tftStakingPeriodSelect.options[tftStakingPeriodSelect.selectedIndex];
const apyMatch = selectedOption.text.match(/\((\d+\.\d+)%\)/);
const apy = apyMatch ? parseFloat(apyMatch[1]) / 100 : 0.085; // Default to 8.5% if not found
// Calculate rewards (simple interest for demonstration)
const rewards = amount * apy * (termDays / 365);
tftEstimatedRewardsElement.textContent = rewards.toFixed(2) + ' TFT';
};
tftStakeAmountInput.addEventListener('input', calculateTftStakingRewards);
tftStakingPeriodSelect.addEventListener('change', calculateTftStakingRewards);
}
// ZAZ Staking calculations
const zazStakeAmountInput = document.getElementById('zazStakeAmount');
const zazStakingPeriodSelect = document.getElementById('zazStakingPeriod');
const zazEstimatedRewardsElement = document.getElementById('zazEstimatedRewards');
if (zazStakeAmountInput && zazStakingPeriodSelect && zazEstimatedRewardsElement) {
const calculateZazStakingRewards = () => {
const amount = parseFloat(zazStakeAmountInput.value) || 0;
const termDays = parseInt(zazStakingPeriodSelect.value) || 30;
// Get APY from the selected option's text
const selectedOption = zazStakingPeriodSelect.options[zazStakingPeriodSelect.selectedIndex];
const apyMatch = selectedOption.text.match(/\((\d+\.\d+)%\)/);
const apy = apyMatch ? parseFloat(apyMatch[1]) / 100 : 0.12; // Default to 12% if not found
// Calculate rewards (simple interest for demonstration)
const rewards = amount * apy * (termDays / 365);
zazEstimatedRewardsElement.textContent = rewards.toFixed(2) + ' ZAZ';
};
zazStakeAmountInput.addEventListener('input', calculateZazStakingRewards);
zazStakingPeriodSelect.addEventListener('change', calculateZazStakingRewards);
}
// Asset Staking calculations
const assetStakingSelect = document.getElementById('assetStaking');
const assetStakingPeriodSelect = document.getElementById('assetStakingPeriod');
const assetEstimatedRewardsElement = document.getElementById('assetEstimatedRewards');
if (assetStakingSelect && assetStakingPeriodSelect && assetEstimatedRewardsElement) {
const calculateAssetStakingRewards = () => {
const selectedOption = assetStakingSelect.options[assetStakingSelect.selectedIndex];
if (selectedOption.value === '') return;
const assetValue = parseFloat(selectedOption.dataset.value) || 0;
const termDays = parseInt(assetStakingPeriodSelect.value) || 30;
// Get APY from the selected option's text
const periodOption = assetStakingPeriodSelect.options[assetStakingPeriodSelect.selectedIndex];
const apyMatch = periodOption.text.match(/\((\d+\.\d+)%\)/);
const apy = apyMatch ? parseFloat(apyMatch[1]) / 100 : 0.035; // Default to 3.5% if not found
// Calculate rewards in USD (simple interest for demonstration)
const rewards = assetValue * apy * (termDays / 365);
assetEstimatedRewardsElement.textContent = '$' + rewards.toFixed(2);
};
assetStakingSelect.addEventListener('change', calculateAssetStakingRewards);
assetStakingPeriodSelect.addEventListener('change', calculateAssetStakingRewards);
}
// =============== SWAP TAB ===============
// Token swap calculations
const swapFromAmountInput = document.getElementById('swapFromAmount');
const swapToAmountElement = document.getElementById('swapToAmount');
const fromTokenDropdown = document.getElementById('fromTokenDropdown');
const toTokenDropdown = document.getElementById('toTokenDropdown');
const exchangeRateElement = document.getElementById('exchangeRate');
const minimumReceivedElement = document.getElementById('minimumReceived');
const priceImpactElement = document.getElementById('priceImpact');
const swapDirectionButton = document.getElementById('swapDirectionButton');
const maxFromButton = document.getElementById('maxFromButton');
const fromTokenSymbolElement = document.getElementById('fromTokenSymbol');
const toTokenSymbolElement = document.getElementById('toTokenSymbol');
const fromTokenImgElement = document.getElementById('fromTokenImg');
const toTokenImgElement = document.getElementById('toTokenImg');
const fromTokenBalanceElement = document.getElementById('fromTokenBalance');
const toTokenBalanceElement = document.getElementById('toTokenBalance');
// Mock token data
const tokenData = {
'TFT': { price: 0.5, balance: '10,000 TFT', usdValue: '5,000.00' },
'ZAZ': { price: 0.5, balance: '5,000 ZAZ', usdValue: '2,500.00' },
'USDT': { price: 1.0, balance: '2,500 USDT', usdValue: '2,500.00' }
};
if (swapFromAmountInput && swapToAmountElement) {
let fromToken = 'TFT';
let toToken = 'ZAZ';
const calculateSwap = () => {
const fromAmount = parseFloat(swapFromAmountInput.value) || 0;
// Calculate exchange rate
const fromPrice = tokenData[fromToken].price;
const toPrice = tokenData[toToken].price;
const rate = fromPrice / toPrice;
// Calculate to amount
const toAmount = fromAmount * rate;
// Update UI
swapToAmountElement.value = toAmount.toFixed(2);
if (exchangeRateElement) {
exchangeRateElement.textContent = `1 ${fromToken} = ${rate.toFixed(4)} ${toToken}`;
}
if (minimumReceivedElement) {
// 0.5% slippage for demonstration
const minReceived = toAmount * 0.995;
minimumReceivedElement.textContent = `${minReceived.toFixed(2)} ${toToken}`;
}
if (priceImpactElement) {
// Mock price impact calculation
const impact = fromAmount > 1000 ? '0.5%' : '< 0.1%';
priceImpactElement.textContent = impact;
priceImpactElement.className = fromAmount > 1000 ? 'text-warning' : 'text-success';
}
};
// Initialize from token dropdown items
const fromTokenItems = document.querySelectorAll('[aria-labelledby="fromTokenDropdown"] .dropdown-item');
fromTokenItems.forEach(item => {
item.addEventListener('click', function(e) {
e.preventDefault();
fromToken = this.dataset.token;
fromTokenSymbolElement.textContent = fromToken;
fromTokenImgElement.src = this.dataset.img;
fromTokenBalanceElement.textContent = tokenData[fromToken].balance;
calculateSwap();
});
});
// Initialize to token dropdown items
const toTokenItems = document.querySelectorAll('[aria-labelledby="toTokenDropdown"] .dropdown-item');
toTokenItems.forEach(item => {
item.addEventListener('click', function(e) {
e.preventDefault();
toToken = this.dataset.token;
toTokenSymbolElement.textContent = toToken;
toTokenImgElement.src = this.dataset.img;
toTokenBalanceElement.textContent = tokenData[toToken].balance;
calculateSwap();
});
});
// Swap direction button
if (swapDirectionButton) {
swapDirectionButton.addEventListener('click', function() {
// Swap tokens
const tempToken = fromToken;
fromToken = toToken;
toToken = tempToken;
// Update UI
fromTokenSymbolElement.textContent = fromToken;
toTokenSymbolElement.textContent = toToken;
const tempImg = fromTokenImgElement.src;
fromTokenImgElement.src = toTokenImgElement.src;
toTokenImgElement.src = tempImg;
fromTokenBalanceElement.textContent = tokenData[fromToken].balance;
toTokenBalanceElement.textContent = tokenData[toToken].balance;
// Swap amounts
const tempAmount = swapFromAmountInput.value;
swapFromAmountInput.value = swapToAmountElement.value;
calculateSwap();
});
}
// Max button
if (maxFromButton) {
maxFromButton.addEventListener('click', function() {
// Set max amount based on token balance
const balance = parseInt(tokenData[fromToken].balance.split(' ')[0].replace(/,/g, ''));
swapFromAmountInput.value = balance;
calculateSwap();
});
}
swapFromAmountInput.addEventListener('input', calculateSwap);
// Initial calculation
calculateSwap();
}
// =============== COLLATERAL TAB ===============
// Collateral form calculations
const collateralAssetSelect = document.getElementById('collateralAsset');
const collateralAmountInput = document.getElementById('collateralAmount');
const collateralValueElement = document.getElementById('collateralValue');
const collateralUnitElement = document.getElementById('collateralUnit');
const collateralAvailableElement = document.getElementById('collateralAvailable');
const collateralAvailableUSDElement = document.getElementById('collateralAvailableUSD');
const collateralPurposeSelect = document.getElementById('collateralPurpose');
const loanTermGroup = document.getElementById('loanTermGroup');
const loanAmountGroup = document.getElementById('loanAmountGroup');
const syntheticAssetGroup = document.getElementById('syntheticAssetGroup');
const syntheticAmountGroup = document.getElementById('syntheticAmountGroup');
const loanAmountInput = document.getElementById('loanAmount');
const maxLoanAmountElement = document.getElementById('maxLoanAmount');
const syntheticAmountInput = document.getElementById('syntheticAmount');
const maxSyntheticAmountElement = document.getElementById('maxSyntheticAmount');
const collateralRatioElement = document.getElementById('collateralRatio');
const liquidationPriceElement = document.getElementById('liquidationPrice');
const liquidationUnitElement = document.getElementById('liquidationUnit');
if (collateralAssetSelect && collateralAmountInput) {
const calculateCollateralDetails = () => {
if (collateralAssetSelect.selectedIndex === 0) return;
const selectedOption = collateralAssetSelect.options[collateralAssetSelect.selectedIndex];
const assetType = selectedOption.dataset.type;
const assetValue = parseFloat(selectedOption.dataset.value) || 0;
const assetAmount = parseFloat(selectedOption.dataset.amount) || 0;
const assetUnit = selectedOption.dataset.unit || '';
// Update UI with asset details
if (collateralUnitElement) collateralUnitElement.textContent = assetUnit;
if (collateralAvailableElement) collateralAvailableElement.textContent = assetAmount.toLocaleString() + ' ' + assetUnit;
if (collateralAvailableUSDElement) collateralAvailableUSDElement.textContent = '$' + assetValue.toLocaleString();
if (liquidationUnitElement) liquidationUnitElement.textContent = assetUnit;
// Calculate collateral value
const amount = parseFloat(collateralAmountInput.value) || 0;
let collateralValue = 0;
if (assetType === 'token') {
// For tokens, calculate based on token price
const tokenPrice = assetValue / assetAmount;
collateralValue = amount * tokenPrice;
} else {
// For NFTs and other assets, use the full value if amount is 1
collateralValue = amount === 1 ? assetValue : 0;
}
if (collateralValueElement) collateralValueElement.value = collateralValue.toFixed(2);
// Calculate max loan amount (75% of collateral value)
const maxLoanAmount = collateralValue * 0.75;
if (maxLoanAmountElement) maxLoanAmountElement.textContent = maxLoanAmount.toFixed(2);
// Calculate max synthetic amount (50% of collateral value)
const maxSyntheticAmount = collateralValue * 0.5;
if (maxSyntheticAmountElement) maxSyntheticAmountElement.textContent = maxSyntheticAmount.toFixed(2);
// Calculate collateral ratio and liquidation price
updateCollateralRatio();
};
const updateCollateralRatio = () => {
const collateralValue = parseFloat(collateralValueElement.value) || 0;
const purpose = collateralPurposeSelect.value;
let borrowedValue = 0;
if (purpose === 'loan') {
borrowedValue = parseFloat(loanAmountInput.value) || 0;
} else if (purpose === 'synthetic') {
borrowedValue = parseFloat(syntheticAmountInput.value) || 0;
} else {
// For leverage trading, assume 2x leverage
borrowedValue = collateralValue;
}
// Calculate ratio
const ratio = borrowedValue > 0 ? (collateralValue / borrowedValue * 100) : 0;
if (collateralRatioElement) {
collateralRatioElement.value = ratio.toFixed(0) + '%';
}
// Calculate liquidation price
if (liquidationPriceElement) {
const selectedOption = collateralAssetSelect.options[collateralAssetSelect.selectedIndex];
if (selectedOption.selectedIndex === 0) return;
const assetType = selectedOption.dataset.type;
const assetValue = parseFloat(selectedOption.dataset.value) || 0;
const assetAmount = parseFloat(selectedOption.dataset.amount) || 0;
const collateralAmount = parseFloat(collateralAmountInput.value) || 0;
if (assetType === 'token' && collateralAmount > 0) {
const currentPrice = assetValue / assetAmount;
const liquidationThreshold = purpose === 'loan' ? 1.2 : 1.5; // 120% for loans, 150% for synthetic
const liquidationPrice = (borrowedValue / collateralAmount) * liquidationThreshold;
liquidationPriceElement.value = liquidationPrice.toFixed(4);
} else {
liquidationPriceElement.value = (borrowedValue * 1.2).toFixed(2);
}
}
};
// Handle collateral asset selection
collateralAssetSelect.addEventListener('change', function() {
collateralAmountInput.value = '';
calculateCollateralDetails();
});
// Handle collateral amount input
collateralAmountInput.addEventListener('input', calculateCollateralDetails);
// Handle purpose selection
collateralPurposeSelect.addEventListener('change', function() {
const purpose = collateralPurposeSelect.value;
// Show/hide relevant form groups
if (loanTermGroup) loanTermGroup.style.display = purpose === 'loan' ? 'block' : 'none';
if (loanAmountGroup) loanAmountGroup.style.display = purpose === 'loan' ? 'block' : 'none';
if (syntheticAssetGroup) syntheticAssetGroup.style.display = purpose === 'synthetic' ? 'block' : 'none';
if (syntheticAmountGroup) syntheticAmountGroup.style.display = purpose === 'synthetic' ? 'block' : 'none';
updateCollateralRatio();
});
// Handle loan amount input
if (loanAmountInput) {
loanAmountInput.addEventListener('input', updateCollateralRatio);
// Max loan button
const maxLoanButton = document.getElementById('maxLoanButton');
if (maxLoanButton) {
maxLoanButton.addEventListener('click', function() {
const maxLoan = parseFloat(maxLoanAmountElement.textContent) || 0;
loanAmountInput.value = maxLoan.toFixed(2);
updateCollateralRatio();
});
}
}
// Handle synthetic amount input
if (syntheticAmountInput) {
syntheticAmountInput.addEventListener('input', updateCollateralRatio);
// Max synthetic button
const maxSyntheticButton = document.getElementById('maxSyntheticButton');
if (maxSyntheticButton) {
maxSyntheticButton.addEventListener('click', function() {
const maxSynthetic = parseFloat(maxSyntheticAmountElement.textContent) || 0;
syntheticAmountInput.value = maxSynthetic.toFixed(2);
updateCollateralRatio();
});
}
}
// Handle synthetic asset selection
const syntheticAssetSelect = document.getElementById('syntheticAsset');
const syntheticUnitElement = document.getElementById('syntheticUnit');
const maxSyntheticUnitElement = document.getElementById('maxSyntheticUnit');
if (syntheticAssetSelect && syntheticUnitElement && maxSyntheticUnitElement) {
syntheticAssetSelect.addEventListener('change', function() {
const asset = syntheticAssetSelect.value;
syntheticUnitElement.textContent = asset;
maxSyntheticUnitElement.textContent = asset;
});
}
}
// Initialize tab functionality if not already handled by Bootstrap
const tabLinks = document.querySelectorAll('.nav-link[data-bs-toggle="tab"]');
tabLinks.forEach(tabLink => {
tabLink.addEventListener('click', function(e) {
e.preventDefault();
const targetId = this.getAttribute('href');
const targetTab = document.querySelector(targetId);
// Hide all tabs
document.querySelectorAll('.tab-pane').forEach(tab => {
tab.classList.remove('show', 'active');
});
// Show the target tab
if (targetTab) {
targetTab.classList.add('show', 'active');
}
// Update active state on nav links
tabLinks.forEach(link => link.classList.remove('active'));
this.classList.add('active');
});
});
});