Merge branch 'development_timur' of https://git.ourworld.tf/herocode/hostbasket into development_timur
This commit is contained in:
369
actix_mvc_app/src/controllers/defi.rs
Normal file
369
actix_mvc_app/src/controllers/defi.rs
Normal 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()
|
||||
}
|
||||
}
|
@@ -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
|
||||
|
Reference in New Issue
Block a user