package alp import ( "context" "fmt" "log" "math/big" "strings" "time" "github.com/ethereum/go-ethereum" "github.com/ethereum/go-ethereum/accounts/abi" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/ethclient" db "github.com/gothinkster/golang-gin-realworld-example-app/common" "github.com/gothinkster/golang-gin-realworld-example-app/config" "github.com/gothinkster/golang-gin-realworld-example-app/models" ) const ( ytPoolManagerAddress = "0xb11824eAA659F8A4648711709dA60720d5Cdabd2" usdyAddress = "0x29774970556407fAE16BC07e87704fE0E9559BC4" alpSnapshotInterval = 1 * time.Hour ) const poolManagerABIJSON = `[ { "inputs":[{"internalType":"bool","name":"_maximise","type":"bool"}], "name":"getAumInUsdy", "outputs":[{"internalType":"uint256","name":"","type":"uint256"}], "stateMutability":"view","type":"function" }, { "inputs":[{"internalType":"bool","name":"_maximise","type":"bool"}], "name":"getPrice", "outputs":[{"internalType":"uint256","name":"","type":"uint256"}], "stateMutability":"view","type":"function" } ]` const erc20TotalSupplyABIJSON = `[{ "inputs":[], "name":"totalSupply", "outputs":[{"internalType":"uint256","name":"","type":"uint256"}], "stateMutability":"view","type":"function" }]` // StartALPSnapshot starts the ALP pool snapshot service (interval: 1h). func StartALPSnapshot(cfg *config.Config) { log.Println("=== ALP Snapshot Service Started (interval: 1h) ===") runALPSnapshot(cfg) ticker := time.NewTicker(alpSnapshotInterval) defer ticker.Stop() for range ticker.C { runALPSnapshot(cfg) } } func runALPSnapshot(cfg *config.Config) { start := time.Now() log.Printf("[ALPSnapshot] Starting at %s", start.Format("2006-01-02 15:04:05")) client, err := ethclient.Dial(cfg.BSCTestnetRPC) if err != nil { log.Printf("[ALPSnapshot] RPC connect error: %v", err) return } defer client.Close() poolValue, alpPrice, err := fetchPoolStats(client) if err != nil { log.Printf("[ALPSnapshot] fetchPoolStats error: %v", err) return } usdySupply, err := fetchUSDYSupply(client) if err != nil { log.Printf("[ALPSnapshot] fetchUSDYSupply error: %v", err) return } feeSurplus := poolValue - usdySupply snap := models.ALPSnapshot{ PoolValue: poolValue, UsdySupply: usdySupply, FeeSurplus: feeSurplus, ALPPrice: alpPrice, SnapshotTime: time.Now().UTC(), } if err := db.GetDB().Create(&snap).Error; err != nil { log.Printf("[ALPSnapshot] DB save error: %v", err) return } log.Printf("[ALPSnapshot] poolValue=%.6f USDY, usdySupply=%.6f, feeSurplus=%.6f, alpPrice=%.8f | done in %v", poolValue, usdySupply, feeSurplus, alpPrice, time.Since(start)) } func fetchPoolStats(client *ethclient.Client) (poolValue float64, alpPrice float64, err error) { pmABI, err := abi.JSON(strings.NewReader(poolManagerABIJSON)) if err != nil { return 0, 0, fmt.Errorf("parse poolManager ABI: %w", err) } pmAddr := common.HexToAddress(ytPoolManagerAddress) ctx := context.Background() // getAumInUsdy(true) callData, err := pmABI.Pack("getAumInUsdy", true) if err != nil { return 0, 0, fmt.Errorf("pack getAumInUsdy: %w", err) } result, err := client.CallContract(ctx, ethereum.CallMsg{To: &pmAddr, Data: callData}, nil) if err != nil { return 0, 0, fmt.Errorf("call getAumInUsdy: %w", err) } decoded, err := pmABI.Unpack("getAumInUsdy", result) if err != nil || len(decoded) == 0 { return 0, 0, fmt.Errorf("unpack getAumInUsdy: %w", err) } poolValue = bigToFloat(decoded[0].(*big.Int), 18) // getPrice(false) callData, err = pmABI.Pack("getPrice", false) if err != nil { return 0, 0, fmt.Errorf("pack getPrice: %w", err) } result, err = client.CallContract(ctx, ethereum.CallMsg{To: &pmAddr, Data: callData}, nil) if err != nil { return 0, 0, fmt.Errorf("call getPrice: %w", err) } decoded, err = pmABI.Unpack("getPrice", result) if err != nil || len(decoded) == 0 { return 0, 0, fmt.Errorf("unpack getPrice: %w", err) } alpPrice = bigToFloat(decoded[0].(*big.Int), 18) return poolValue, alpPrice, nil } func fetchUSDYSupply(client *ethclient.Client) (float64, error) { supplyABI, err := abi.JSON(strings.NewReader(erc20TotalSupplyABIJSON)) if err != nil { return 0, fmt.Errorf("parse totalSupply ABI: %w", err) } usdyAddr := common.HexToAddress(usdyAddress) callData, err := supplyABI.Pack("totalSupply") if err != nil { return 0, fmt.Errorf("pack totalSupply: %w", err) } result, err := client.CallContract(context.Background(), ethereum.CallMsg{To: &usdyAddr, Data: callData}, nil) if err != nil { return 0, fmt.Errorf("call totalSupply: %w", err) } decoded, err := supplyABI.Unpack("totalSupply", result) if err != nil || len(decoded) == 0 { return 0, fmt.Errorf("unpack totalSupply: %w", err) } return bigToFloat(decoded[0].(*big.Int), 18), nil } func bigToFloat(n *big.Int, decimals int64) float64 { divisor := new(big.Int).Exp(big.NewInt(10), big.NewInt(decimals), nil) f, _ := new(big.Float).SetPrec(256).Quo( new(big.Float).SetPrec(256).SetInt(n), new(big.Float).SetPrec(256).SetInt(divisor), ).Float64() return f }