move all defi functionality to page and separate controller

This commit is contained in:
Timur Gordon
2025-05-01 02:55:41 +02:00
parent 457f3c8268
commit 6b7b2542ab
21 changed files with 2198 additions and 1505 deletions

View File

@@ -0,0 +1,369 @@
use actix_web::{web, HttpResponse, Result};
use actix_web::HttpRequest;
use tera::{Context, Tera};
use chrono::{Utc, Duration};
use serde::Deserialize;
use uuid::Uuid;
use crate::models::asset::{Asset, AssetType, AssetStatus};
use crate::models::defi::{DefiPosition, DefiPositionType, DefiPositionStatus, LendingPosition, BorrowingPosition, DEFI_DB};
use crate::utils::render_template;
// Form structs for DeFi operations
#[derive(Debug, Deserialize)]
pub struct LendingForm {
pub asset_id: String,
pub amount: f64,
pub duration: i32,
}
#[derive(Debug, Deserialize)]
pub struct BorrowingForm {
pub collateral_asset_id: String,
pub collateral_amount: f64,
pub amount: f64,
pub duration: i32,
}
#[derive(Debug, Deserialize)]
pub struct LiquidityForm {
pub first_token: String,
pub first_amount: f64,
pub second_token: String,
pub second_amount: f64,
pub pool_fee: f64,
}
#[derive(Debug, Deserialize)]
pub struct StakingForm {
pub asset_id: String,
pub amount: f64,
pub staking_period: i32,
}
#[derive(Debug, Deserialize)]
pub struct SwapForm {
pub from_token: String,
pub from_amount: f64,
pub to_token: String,
}
#[derive(Debug, Deserialize)]
pub struct CollateralForm {
pub asset_id: String,
pub amount: f64,
pub purpose: String,
pub loan_amount: Option<f64>,
pub loan_term: Option<i32>,
}
pub struct DefiController;
impl DefiController {
// Display the DeFi dashboard
pub async fn index(tmpl: web::Data<Tera>, req: HttpRequest) -> Result<HttpResponse> {
let mut context = Context::new();
println!("DEBUG: Starting DeFi dashboard rendering");
// Get mock assets for the dropdown selectors
let assets = Self::get_mock_assets();
println!("DEBUG: Generated {} mock assets", assets.len());
// Add active_page for navigation highlighting
context.insert("active_page", &"defi");
// Add DeFi stats
let defi_stats = Self::get_defi_stats();
context.insert("defi_stats", &serde_json::to_value(defi_stats).unwrap());
// Add recent assets for selection in forms
let recent_assets: Vec<serde_json::Map<String, serde_json::Value>> = assets
.iter()
.take(5)
.map(|a| Self::asset_to_json(a))
.collect();
context.insert("recent_assets", &recent_assets);
// Get user's lending positions
let db = DEFI_DB.lock().unwrap();
let lending_positions = db.get_user_lending_positions("user123");
let lending_positions_json: Vec<serde_json::Value> = lending_positions
.iter()
.map(|p| serde_json::to_value(p).unwrap())
.collect();
context.insert("lending_positions", &lending_positions_json);
// Get user's borrowing positions
let borrowing_positions = db.get_user_borrowing_positions("user123");
let borrowing_positions_json: Vec<serde_json::Value> = borrowing_positions
.iter()
.map(|p| serde_json::to_value(p).unwrap())
.collect();
context.insert("borrowing_positions", &borrowing_positions_json);
// Add success message if present in query params
if let Some(success) = req.query_string().strip_prefix("success=") {
let decoded = urlencoding::decode(success).unwrap_or_else(|_| success.into());
context.insert("success_message", &decoded);
}
println!("DEBUG: Rendering DeFi dashboard template");
let response = render_template(&tmpl, "defi/index.html", &context);
println!("DEBUG: Finished rendering DeFi dashboard template");
response
}
// Process lending request
pub async fn create_lending(_tmpl: web::Data<Tera>, form: web::Form<LendingForm>) -> Result<HttpResponse> {
println!("DEBUG: Processing lending request: {:?}", form);
// Get the asset details (in a real app, this would come from a database)
let assets = Self::get_mock_assets();
let asset = assets.iter().find(|a| a.id == form.asset_id);
if let Some(asset) = asset {
// Calculate interest and return amount
let apy = match form.duration {
7 => 2.5,
30 => 4.2,
90 => 6.8,
180 => 8.5,
365 => 12.0,
_ => 4.2, // Default to 30 days rate
};
let interest = form.amount * (apy / 100.0) * (form.duration as f64 / 365.0);
let return_amount = form.amount + interest;
// Create a new lending position
let lending_position = LendingPosition {
base: DefiPosition {
id: Uuid::new_v4().to_string(),
position_type: DefiPositionType::Lending,
status: DefiPositionStatus::Active,
asset_id: asset.id.clone(),
asset_name: asset.name.clone(),
asset_symbol: asset.asset_type.as_str().to_string(), // Using asset_type as symbol for now
amount: form.amount,
value_usd: form.amount * asset.latest_valuation().map_or(0.5, |v| v.value), // Assuming 0.5 USD per token if no valuation
apy,
created_at: Utc::now(),
expires_at: Some(Utc::now() + Duration::days(form.duration as i64)),
user_id: "user123".to_string(), // Hardcoded user ID for now
},
duration_days: form.duration,
interest_earned: interest,
return_amount,
};
// Add the position to the database
{
let mut db = DEFI_DB.lock().unwrap();
db.add_lending_position(lending_position);
}
// Redirect with success message
let success_message = format!("Successfully lent {} {} at {}% APY", form.amount, asset.name, apy);
Ok(HttpResponse::SeeOther()
.append_header(("Location", format!("/defi?success={}", urlencoding::encode(&success_message))))
.finish())
} else {
// Asset not found, redirect with error
Ok(HttpResponse::SeeOther()
.append_header(("Location", "/defi?error=Asset not found"))
.finish())
}
}
// Process borrowing request
pub async fn create_borrowing(_tmpl: web::Data<Tera>, form: web::Form<BorrowingForm>) -> Result<HttpResponse> {
println!("DEBUG: Processing borrowing request: {:?}", form);
// Get the asset details (in a real app, this would come from a database)
let assets = Self::get_mock_assets();
let collateral_asset = assets.iter().find(|a| a.id == form.collateral_asset_id);
if let Some(collateral_asset) = collateral_asset {
// Calculate interest rate based on duration
let interest_rate = match form.duration {
7 => 3.5,
30 => 5.0,
90 => 6.5,
180 => 8.0,
365 => 10.0,
_ => 5.0, // Default to 30 days rate
};
// Calculate interest and total to repay
let interest = form.amount * (interest_rate / 100.0) * (form.duration as f64 / 365.0);
let total_to_repay = form.amount + interest;
// Calculate collateral value and ratio
let collateral_value = form.collateral_amount * collateral_asset.latest_valuation().map_or(0.5, |v| v.value);
let collateral_ratio = (collateral_value / form.amount) * 100.0;
// Create a new borrowing position
let borrowing_position = BorrowingPosition {
base: DefiPosition {
id: Uuid::new_v4().to_string(),
position_type: DefiPositionType::Borrowing,
status: DefiPositionStatus::Active,
asset_id: "ZAZ".to_string(), // Hardcoded for now, in a real app this would be a parameter
asset_name: "Zanzibar Token".to_string(),
asset_symbol: "ZAZ".to_string(),
amount: form.amount,
value_usd: form.amount * 0.5, // Assuming 0.5 USD per ZAZ
apy: interest_rate,
created_at: Utc::now(),
expires_at: Some(Utc::now() + Duration::days(form.duration as i64)),
user_id: "user123".to_string(), // Hardcoded user ID for now
},
collateral_asset_id: collateral_asset.id.clone(),
collateral_asset_name: collateral_asset.name.clone(),
collateral_asset_symbol: collateral_asset.asset_type.as_str().to_string(),
collateral_amount: form.collateral_amount,
collateral_value_usd: collateral_value,
duration_days: form.duration,
interest_rate,
interest_owed: interest,
total_to_repay,
collateral_ratio,
};
// Add the position to the database
{
let mut db = DEFI_DB.lock().unwrap();
db.add_borrowing_position(borrowing_position);
}
// Redirect with success message
let success_message = format!("Successfully borrowed {} ZAZ using {} {} as collateral",
form.amount, form.collateral_amount, collateral_asset.name);
Ok(HttpResponse::SeeOther()
.append_header(("Location", format!("/defi?success={}", urlencoding::encode(&success_message))))
.finish())
} else {
// Asset not found, redirect with error
Ok(HttpResponse::SeeOther()
.append_header(("Location", "/defi?error=Collateral asset not found"))
.finish())
}
}
// Process liquidity provision
pub async fn add_liquidity(_tmpl: web::Data<Tera>, form: web::Form<LiquidityForm>) -> Result<HttpResponse> {
println!("DEBUG: Processing liquidity provision: {:?}", form);
// In a real application, this would add liquidity to a pool in the database
// For now, we'll just redirect back to the DeFi dashboard with a success message
let success_message = format!("Successfully added liquidity: {} {} and {} {}",
form.first_amount, form.first_token, form.second_amount, form.second_token);
Ok(HttpResponse::SeeOther()
.append_header(("Location", format!("/defi?success={}", urlencoding::encode(&success_message))))
.finish())
}
// Process staking request
pub async fn create_staking(_tmpl: web::Data<Tera>, form: web::Form<StakingForm>) -> Result<HttpResponse> {
println!("DEBUG: Processing staking request: {:?}", form);
// In a real application, this would create a staking position in the database
// For now, we'll just redirect back to the DeFi dashboard with a success message
let success_message = format!("Successfully staked {} {}", form.amount, form.asset_id);
Ok(HttpResponse::SeeOther()
.append_header(("Location", format!("/defi?success={}", urlencoding::encode(&success_message))))
.finish())
}
// Process token swap
pub async fn swap_tokens(_tmpl: web::Data<Tera>, form: web::Form<SwapForm>) -> Result<HttpResponse> {
println!("DEBUG: Processing token swap: {:?}", form);
// In a real application, this would perform a token swap in the database
// For now, we'll just redirect back to the DeFi dashboard with a success message
let success_message = format!("Successfully swapped {} {} to {}",
form.from_amount, form.from_token, form.to_token);
Ok(HttpResponse::SeeOther()
.append_header(("Location", format!("/defi?success={}", urlencoding::encode(&success_message))))
.finish())
}
// Process collateral position creation
pub async fn create_collateral(_tmpl: web::Data<Tera>, form: web::Form<CollateralForm>) -> Result<HttpResponse> {
println!("DEBUG: Processing collateral creation: {:?}", form);
// In a real application, this would create a collateral position in the database
// For now, we'll just redirect back to the DeFi dashboard with a success message
let purpose_str = match form.purpose.as_str() {
"loan" => "secure a loan",
"synthetic" => "generate synthetic assets",
"leverage" => "leverage trading",
_ => "collateralization",
};
let success_message = format!("Successfully collateralized {} {} for {}",
form.amount, form.asset_id, purpose_str);
Ok(HttpResponse::SeeOther()
.append_header(("Location", format!("/defi?success={}", urlencoding::encode(&success_message))))
.finish())
}
// Helper method to get DeFi statistics
fn get_defi_stats() -> serde_json::Map<String, serde_json::Value> {
let mut stats = serde_json::Map::new();
// Handle Option<Number> by unwrapping with expect
stats.insert("total_value_locked".to_string(), serde_json::Value::Number(serde_json::Number::from_f64(1250000.0).expect("Valid float")));
stats.insert("lending_volume".to_string(), serde_json::Value::Number(serde_json::Number::from_f64(450000.0).expect("Valid float")));
stats.insert("borrowing_volume".to_string(), serde_json::Value::Number(serde_json::Number::from_f64(320000.0).expect("Valid float")));
stats.insert("liquidity_pools_count".to_string(), serde_json::Value::Number(serde_json::Number::from(12)));
stats.insert("active_stakers".to_string(), serde_json::Value::Number(serde_json::Number::from(156)));
stats.insert("total_swap_volume".to_string(), serde_json::Value::Number(serde_json::Number::from_f64(780000.0).expect("Valid float")));
stats
}
// Helper method to convert Asset to a JSON object for templates
fn asset_to_json(asset: &Asset) -> serde_json::Map<String, serde_json::Value> {
let mut map = serde_json::Map::new();
map.insert("id".to_string(), serde_json::Value::String(asset.id.clone()));
map.insert("name".to_string(), serde_json::Value::String(asset.name.clone()));
map.insert("description".to_string(), serde_json::Value::String(asset.description.clone()));
map.insert("asset_type".to_string(), serde_json::Value::String(asset.asset_type.as_str().to_string()));
map.insert("status".to_string(), serde_json::Value::String(asset.status.as_str().to_string()));
// Add current valuation
if let Some(latest) = asset.latest_valuation() {
if let Some(num) = serde_json::Number::from_f64(latest.value) {
map.insert("current_valuation".to_string(), serde_json::Value::Number(num));
} else {
map.insert("current_valuation".to_string(), serde_json::Value::Number(serde_json::Number::from(0)));
}
map.insert("valuation_currency".to_string(), serde_json::Value::String(latest.currency.clone()));
map.insert("valuation_date".to_string(), serde_json::Value::String(latest.date.format("%Y-%m-%d").to_string()));
} else {
map.insert("current_valuation".to_string(), serde_json::Value::Number(serde_json::Number::from(0)));
map.insert("valuation_currency".to_string(), serde_json::Value::String("USD".to_string()));
map.insert("valuation_date".to_string(), serde_json::Value::String("N/A".to_string()));
}
map
}
// Generate mock assets for testing
fn get_mock_assets() -> Vec<Asset> {
// Reuse the asset controller's mock data function
crate::controllers::asset::AssetController::get_mock_assets()
}
}

View File

@@ -7,6 +7,7 @@ pub mod governance;
pub mod flow;
pub mod contract;
pub mod asset;
pub mod defi;
pub mod marketplace;
// Re-export controllers for easier imports