package alp import ( "net/http" "time" "github.com/gin-gonic/gin" db "github.com/gothinkster/golang-gin-realworld-example-app/common" "github.com/gothinkster/golang-gin-realworld-example-app/models" ) // GetALPHistory returns historical APR and price data from alp_snapshots. // Each point is computed from consecutive snapshot pairs. // Query param: days=30 (default 30, max 90) func GetALPHistory(c *gin.Context) { database := db.GetDB() days := 30 if d := c.Query("days"); d != "" { if v := parseInt(d, 30); v > 0 && v <= 90 { days = v } } since := time.Now().UTC().Add(-time.Duration(days) * 24 * time.Hour) var snaps []models.ALPSnapshot if err := database. Where("snapshot_time >= ?", since). Order("snapshot_time ASC"). Find(&snaps).Error; err != nil { c.JSON(http.StatusInternalServerError, gin.H{"success": false, "error": err.Error()}) return } type Point struct { Time string `json:"time"` TS int64 `json:"ts"` PoolAPR float64 `json:"poolAPR"` ALPPrice float64 `json:"alpPrice"` FeeSurplus float64 `json:"feeSurplus"` PoolValue float64 `json:"poolValue"` } // Deduplicate: keep the latest snapshot per calendar day (UTC) dayMap := make(map[string]models.ALPSnapshot) dayOrder := make([]string, 0) for _, snap := range snaps { key := snap.SnapshotTime.UTC().Format("2006-01-02") if _, exists := dayMap[key]; !exists { dayOrder = append(dayOrder, key) } dayMap[key] = snap // overwrite keeps the latest of the day } daily := make([]models.ALPSnapshot, 0, len(dayOrder)) for _, key := range dayOrder { daily = append(daily, dayMap[key]) } points := make([]Point, 0, len(daily)) for i, snap := range daily { apr := 0.0 if i > 0 { prev := daily[i-1] d := snap.SnapshotTime.Sub(prev.SnapshotTime).Hours() / 24 if d > 0 && snap.PoolValue > 0 && snap.FeeSurplus > prev.FeeSurplus { apr = (snap.FeeSurplus-prev.FeeSurplus) / snap.PoolValue / d * 365 * 100 } } points = append(points, Point{ Time: snap.SnapshotTime.Format("01/02"), TS: snap.SnapshotTime.Unix(), PoolAPR: apr, ALPPrice: snap.ALPPrice, FeeSurplus: snap.FeeSurplus, PoolValue: snap.PoolValue, }) } c.JSON(http.StatusOK, gin.H{"success": true, "data": points}) } func parseInt(s string, def int) int { if s == "" { return def } v := 0 for _, c := range s { if c < '0' || c > '9' { return def } v = v*10 + int(c-'0') } return v } // GetALPStats returns current ALP pool APR calculated from snapshots. // TVL and ALP price are read directly from chain on the frontend; // this endpoint only provides the APR which requires historical data. func GetALPStats(c *gin.Context) { database := db.GetDB() // Latest snapshot var latest models.ALPSnapshot if err := database.Order("snapshot_time DESC").First(&latest).Error; err != nil { c.JSON(http.StatusOK, gin.H{ "success": true, "data": gin.H{"poolAPR": 0.0, "rewardAPR": 0.0}, }) return } // Use the oldest available snapshot as reference (maximises data coverage) var past models.ALPSnapshot database.Order("snapshot_time ASC").First(&past) found := past.ID != 0 && past.ID != latest.ID poolAPR := 0.0 if found && latest.PoolValue > 0 && past.FeeSurplus < latest.FeeSurplus { days := latest.SnapshotTime.Sub(past.SnapshotTime).Hours() / 24 if days > 0 { surplusDelta := latest.FeeSurplus - past.FeeSurplus poolAPR = surplusDelta / latest.PoolValue / days * 365 * 100 // annualized % } } c.JSON(http.StatusOK, gin.H{ "success": true, "data": gin.H{ "poolAPR": poolAPR, "rewardAPR": 0.0, // placeholder until reward contract is connected }, }) }