包含 webapp(Next.js 用户端)、webapp-back(Go 后端)、 antdesign(管理后台)、landingpage(营销落地页)、 数据库 SQL 和配置文件。
172 lines
5.0 KiB
Go
172 lines
5.0 KiB
Go
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
|
|
}
|