init: 初始化 AssetX 项目仓库
包含 webapp(Next.js 用户端)、webapp-back(Go 后端)、 antdesign(管理后台)、landingpage(营销落地页)、 数据库 SQL 和配置文件。
This commit is contained in:
342
webapp-back/lending/handlers.go
Normal file
342
webapp-back/lending/handlers.go
Normal file
@@ -0,0 +1,342 @@
|
||||
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,
|
||||
},
|
||||
})
|
||||
}
|
||||
Reference in New Issue
Block a user