Files
assetx/webapp-back/lending/handlers.go

343 lines
8.6 KiB
Go
Raw Normal View History

package lending
import (
"net/http"
"time"
"github.com/gin-gonic/gin"
"github.com/gothinkster/golang-gin-realworld-example-app/common"
"github.com/gothinkster/golang-gin-realworld-example-app/models"
)
// GetUserPosition returns user's lending position
// GET /api/lending/position/:address
func GetUserPosition(c *gin.Context) {
address := c.Param("address")
if address == "" {
c.JSON(http.StatusBadRequest, gin.H{
"success": false,
"error": "wallet address is required",
})
return
}
config := GetLendingMarketConfig()
position := &UserPosition{
UserAddress: address,
WalletAddress: address,
SuppliedBalance: "0",
SuppliedBalanceUSD: 0,
BorrowedBalance: "0",
BorrowedBalanceUSD: 0,
CollateralBalances: map[string]CollateralInfo{},
HealthFactor: 0,
LTV: 0,
SupplyAPY: config["base_supply_apy"].(float64),
BorrowAPY: config["base_borrow_apy"].(float64),
}
c.JSON(http.StatusOK, gin.H{
"success": true,
"data": position,
})
}
// GetLendingStats returns lending market statistics
// GET /api/lending/stats
func GetLendingStats(c *gin.Context) {
config := GetLendingMarketConfig()
stats := &LendingStats{
TotalSuppliedUSD: 0,
TotalBorrowedUSD: 0,
TotalCollateralUSD: 0,
UtilizationRate: 0,
AvgSupplyAPY: config["base_supply_apy"].(float64),
AvgBorrowAPY: config["base_borrow_apy"].(float64),
TotalUsers: 0,
ActiveBorrowers: 0,
TotalTVL: 0,
}
c.JSON(http.StatusOK, gin.H{
"success": true,
"data": stats,
})
}
// GetLendingMarkets returns lending market configuration.
// Contract addresses come from system_contracts; static config from GetLendingMarketConfig.
// GET /api/lending/markets
func GetLendingMarkets(c *gin.Context) {
db := common.GetDB()
var lc struct {
Address string `gorm:"column:address"`
ChainID int `gorm:"column:chain_id"`
}
db.Table("system_contracts").
Where("name = ? AND is_active = ?", "lendingProxy", true).
Select("address, chain_id").
First(&lc)
config := GetLendingMarketConfig()
config["contract_address"] = lc.Address
config["chain_id"] = lc.ChainID
c.JSON(http.StatusOK, gin.H{
"success": true,
"data": []interface{}{config},
})
}
// SupplyUSDC handles USDC supply (deposit) transaction
// POST /api/lending/supply
func SupplyUSDC(c *gin.Context) {
var req SupplyRequest
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, gin.H{
"success": false,
"error": err.Error(),
})
return
}
// TODO: Validate transaction on blockchain
// TODO: Update user position
// TODO: Record transaction in database
c.JSON(http.StatusOK, SupplyResponse{
Success: true,
Message: "USDC supplied successfully",
})
}
// WithdrawUSDC handles USDC withdrawal transaction
// POST /api/lending/withdraw
func WithdrawUSDC(c *gin.Context) {
var req WithdrawRequest
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, gin.H{
"success": false,
"error": err.Error(),
})
return
}
// TODO: Validate transaction on blockchain
// TODO: Update user position
// TODO: Record transaction in database
c.JSON(http.StatusOK, WithdrawResponse{
Success: true,
Message: "USDC withdrawn successfully",
})
}
// SupplyCollateral handles collateral supply transaction
// POST /api/lending/supply-collateral
func SupplyCollateral(c *gin.Context) {
var req SupplyCollateralRequest
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, gin.H{
"success": false,
"error": err.Error(),
})
return
}
// Validate asset type - check against assets table
if !ValidateYTToken(req.Asset) {
c.JSON(http.StatusBadRequest, gin.H{
"success": false,
"error": "invalid collateral asset, must be a valid YT token (YT-A, YT-B, YT-C)",
})
return
}
// Get token info from database
tokenInfo, err := GetYTTokenInfo(req.Asset, 0)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{
"success": false,
"error": "failed to fetch token information",
})
return
}
_ = tokenInfo // Will be used for blockchain validation
// TODO: Validate transaction on blockchain using tokenInfo.ContractAddress
// TODO: Update user collateral position
// TODO: Record transaction in database
c.JSON(http.StatusOK, SupplyCollateralResponse{
Success: true,
Message: "Collateral supplied successfully",
})
}
// WithdrawCollateral handles collateral withdrawal transaction
// POST /api/lending/withdraw-collateral
func WithdrawCollateral(c *gin.Context) {
var req WithdrawCollateralRequest
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, gin.H{
"success": false,
"error": err.Error(),
})
return
}
// Validate asset type - check against assets table
if !ValidateYTToken(req.Asset) {
c.JSON(http.StatusBadRequest, gin.H{
"success": false,
"error": "invalid collateral asset, must be a valid YT token (YT-A, YT-B, YT-C)",
})
return
}
// Get token info from database
tokenInfo, err := GetYTTokenInfo(req.Asset, 0)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{
"success": false,
"error": "failed to fetch token information",
})
return
}
_ = tokenInfo // Will be used for blockchain validation
// TODO: Validate transaction on blockchain using tokenInfo.ContractAddress
// TODO: Check if withdrawal is safe (health factor)
// TODO: Update user collateral position
// TODO: Record transaction in database
c.JSON(http.StatusOK, WithdrawCollateralResponse{
Success: true,
Message: "Collateral withdrawn successfully",
})
}
// BorrowUSDC handles USDC borrow transaction
// POST /api/lending/borrow
func BorrowUSDC(c *gin.Context) {
var req BorrowRequest
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, gin.H{
"success": false,
"error": err.Error(),
})
return
}
// TODO: Validate transaction on blockchain
// TODO: Check collateral sufficiency
// TODO: Update user borrow position
// TODO: Record transaction in database
c.JSON(http.StatusOK, BorrowResponse{
Success: true,
Message: "USDC borrowed successfully",
})
}
// RepayUSDC handles USDC repayment transaction
// POST /api/lending/repay
func RepayUSDC(c *gin.Context) {
var req RepayRequest
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, gin.H{
"success": false,
"error": err.Error(),
})
return
}
// TODO: Validate transaction on blockchain
// TODO: Update user borrow position
// TODO: Record transaction in database
c.JSON(http.StatusOK, RepayResponse{
Success: true,
Message: "USDC repaid successfully",
})
}
// GetLendingAPYHistory returns historical supply/borrow APY snapshots
// GET /api/lending/apy-history?period=1W&chain_id=97
func GetLendingAPYHistory(c *gin.Context) {
period := c.DefaultQuery("period", "1W")
chainId := parseChainID(c)
database := common.GetDB()
var usdcAsset models.Asset
if err := database.Where("asset_code = ?", "USDC").First(&usdcAsset).Error; err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"success": false, "error": "USDC asset not configured"})
return
}
now := time.Now()
var since time.Time
switch period {
case "1M":
since = now.AddDate(0, -1, 0)
case "1Y":
since = now.AddDate(-1, 0, 0)
default: // 1W
since = now.AddDate(0, 0, -7)
}
query := database.Where("asset_id = ? AND snapshot_time >= ?", usdcAsset.ID, since).
Order("snapshot_time ASC")
if chainId != 0 {
query = query.Where("chain_id = ?", chainId)
}
var snapshots []models.APYSnapshot
if err := query.Find(&snapshots).Error; err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"success": false, "error": "failed to fetch APY history"})
return
}
type DataPoint struct {
Time string `json:"time"`
SupplyAPY float64 `json:"supply_apy"`
BorrowAPY float64 `json:"borrow_apy"`
}
points := make([]DataPoint, 0, len(snapshots))
for _, s := range snapshots {
points = append(points, DataPoint{
Time: s.SnapshotTime.UTC().Format(time.RFC3339),
SupplyAPY: s.SupplyAPY,
BorrowAPY: s.BorrowAPY,
})
}
var currentSupplyAPY, currentBorrowAPY float64
if len(snapshots) > 0 {
last := snapshots[len(snapshots)-1]
currentSupplyAPY = last.SupplyAPY
currentBorrowAPY = last.BorrowAPY
}
// APY change vs first point in period
var apyChange float64
if len(snapshots) > 1 {
apyChange = snapshots[len(snapshots)-1].SupplyAPY - snapshots[0].SupplyAPY
}
c.JSON(http.StatusOK, gin.H{
"success": true,
"data": gin.H{
"history": points,
"current_supply_apy": currentSupplyAPY,
"current_borrow_apy": currentBorrowAPY,
"apy_change": apyChange,
"period": period,
},
})
}