package holders import ( "fmt" "log" "time" "github.com/gothinkster/golang-gin-realworld-example-app/common" ) // Asset represents a product/asset in the database type Asset struct { ID int64 `gorm:"column:id"` AssetCode string `gorm:"column:asset_code"` Name string `gorm:"column:name"` TokenRole string `gorm:"column:token_role"` ChainID int `gorm:"column:chain_id"` ContractAddress string `gorm:"column:contract_address"` DeployBlock *uint64 `gorm:"column:deploy_block"` } const zeroAddress = "0x0000000000000000000000000000000000000000" // LoadConfigFromDB loads contract addresses and deploy blocks from database based on chain ID func LoadConfigFromDB(chainID int64) (Config, error) { db := common.GetDB() log.Printf("📚 [Scanner] 从数据库加载配置 - Chain ID: %d", chainID) switch chainID { case 97, 421614: // supported default: return Config{}, fmt.Errorf("unsupported chain ID: %d", chainID) } // Load YT assets by token_role filtered by chain_id var assets []Asset if err := db.Table("assets"). Where("token_role = ? AND chain_id = ? AND is_active = ?", "yt_token", chainID, true). Find(&assets).Error; err != nil { return Config{}, fmt.Errorf("failed to load assets: %w", err) } ytVaults := make([]VaultConfig, 0, len(assets)) for _, asset := range assets { if asset.ContractAddress == "" || asset.ContractAddress == zeroAddress { log.Printf("⚠️ [Scanner] 跳过 %s (地址未配置)", asset.AssetCode) continue } if asset.DeployBlock == nil || *asset.DeployBlock == 0 { log.Printf("⚠️ [Scanner] 跳过 %s (deploy_block 未配置)", asset.AssetCode) continue } ytVaults = append(ytVaults, VaultConfig{ Name: asset.AssetCode, Address: asset.ContractAddress, DeployBlock: *asset.DeployBlock, }) log.Printf(" ✓ %s: %s (部署区块: %d)", asset.AssetCode, asset.ContractAddress, *asset.DeployBlock) } log.Printf("✅ [Scanner] 加载了 %d 个 YT Vault", len(ytVaults)) // Load YTLPToken address from system_contracts var ytLPContract struct { Address string `gorm:"column:address"` DeployBlock *uint64 `gorm:"column:deploy_block"` } ytLPAddress := "" var ytLPDeployBlock uint64 err := db.Table("system_contracts"). Where("name = ? AND chain_id = ? AND is_active = ?", "YTLPToken", chainID, 1). Select("address, deploy_block"). First(&ytLPContract).Error if err != nil { log.Printf("⚠️ [Scanner] 未找到 YTLPToken 配置") } else if ytLPContract.Address == "" || ytLPContract.Address == zeroAddress { log.Printf("⚠️ [Scanner] 跳过 ytLP (地址未配置)") } else if ytLPContract.DeployBlock == nil || *ytLPContract.DeployBlock == 0 { log.Printf("⚠️ [Scanner] 跳过 ytLP (deploy_block 未配置)") } else { ytLPAddress = ytLPContract.Address ytLPDeployBlock = *ytLPContract.DeployBlock log.Printf("✅ [Scanner] ytLP: %s (部署区块: %d)", ytLPAddress, ytLPDeployBlock) } rpcURL := getRPCURLForChain(chainID) config := Config{ ChainID: int(chainID), RPCURL: rpcURL, YTVaults: ytVaults, YTLPAddress: ytLPAddress, DeploymentBlocks: DeploymentBlocks{ YTLP: ytLPDeployBlock, }, PollInterval: 30 * time.Second, BatchSize: 9999, } log.Printf("📊 [Scanner] 配置加载完成: YT Vaults=%d, ytLP=%s, RPC=%s", len(config.YTVaults), config.YTLPAddress, config.RPCURL) return config, nil } // getRPCURLForChain returns the RPC URL for the given chain ID func getRPCURLForChain(chainID int64) string { switch chainID { case 421614: return "https://api.zan.top/node/v1/arb/sepolia/baf84c429d284bb5b676cb8c9ca21c07" case 97: return "https://api.zan.top/node/v1/bsc/testnet/baf84c429d284bb5b676cb8c9ca21c07" default: return "" } } // TableName sets the table name for GORM func (Asset) TableName() string { return "assets" }