package main import ( "fmt" "math/big" "os" "strconv" "strings" "time" ) type Config struct { ListenAddr string DBPath string AllowedOrigin string DeploymentClass string MemberPollIntervalSec int IntentTTL time.Duration QuoteTTL time.Duration WalletSessionTTL time.Duration RequireWalletSession bool InstallTokenTTL time.Duration LeaseTTL time.Duration OfflineRenewTTL time.Duration ChainID int64 DomainName string VerifyingContract string MembershipContract string EntitlementContract string MintCurrency string MintAmountAtomic string MintDecimals int ChainRPCURL string RequireOnchainTxVerify bool GovernanceRuntimeVersion string GovernancePackageURL string GovernancePackageHash string GovernancePackageSig string GovernanceSignerKeyID string GovernancePolicyHash string GovernanceRolloutChannel string } func loadConfig() Config { return Config{ ListenAddr: env("SECRET_API_LISTEN_ADDR", ":8080"), DBPath: env("SECRET_API_DB_PATH", "./secret.db"), AllowedOrigin: env("SECRET_API_ALLOWED_ORIGIN", "https://edut.ai"), DeploymentClass: normalizeDeploymentClass(env("SECRET_API_DEPLOYMENT_CLASS", "development")), MemberPollIntervalSec: envInt("SECRET_API_MEMBER_POLL_INTERVAL_SECONDS", 30), IntentTTL: time.Duration(envInt("SECRET_API_INTENT_TTL_SECONDS", 900)) * time.Second, QuoteTTL: time.Duration(envInt("SECRET_API_QUOTE_TTL_SECONDS", 900)) * time.Second, WalletSessionTTL: time.Duration(envInt("SECRET_API_WALLET_SESSION_TTL_SECONDS", 2592000)) * time.Second, RequireWalletSession: envBool("SECRET_API_REQUIRE_WALLET_SESSION", true), InstallTokenTTL: time.Duration(envInt("SECRET_API_INSTALL_TOKEN_TTL_SECONDS", 900)) * time.Second, LeaseTTL: time.Duration(envInt("SECRET_API_LEASE_TTL_SECONDS", 3600)) * time.Second, OfflineRenewTTL: time.Duration(envInt("SECRET_API_OFFLINE_RENEW_TTL_SECONDS", 2592000)) * time.Second, ChainID: int64(envInt("SECRET_API_CHAIN_ID", 84532)), DomainName: env("SECRET_API_DOMAIN_NAME", "EDUT Designation"), VerifyingContract: strings.ToLower(env("SECRET_API_VERIFYING_CONTRACT", "0x0000000000000000000000000000000000000000")), MembershipContract: strings.ToLower(env("SECRET_API_MEMBERSHIP_CONTRACT", "0x0000000000000000000000000000000000000000")), EntitlementContract: strings.ToLower(env("SECRET_API_ENTITLEMENT_CONTRACT", "0x0000000000000000000000000000000000000000")), MintCurrency: strings.ToUpper(env("SECRET_API_MINT_CURRENCY", "USDC")), MintAmountAtomic: env("SECRET_API_MINT_AMOUNT_ATOMIC", "100000000"), MintDecimals: envInt("SECRET_API_MINT_DECIMALS", 6), ChainRPCURL: env("SECRET_API_CHAIN_RPC_URL", ""), RequireOnchainTxVerify: loadRequireOnchainTxVerify(), GovernanceRuntimeVersion: env("SECRET_API_GOV_RUNTIME_VERSION", "0.1.0"), GovernancePackageURL: env("SECRET_API_GOV_PACKAGE_URL", "https://cdn.edut.ai/governance/edutd-0.1.0.tar.gz"), GovernancePackageHash: strings.ToLower(env("SECRET_API_GOV_PACKAGE_HASH", "sha256:pending")), GovernancePackageSig: env("SECRET_API_GOV_PACKAGE_SIGNATURE", "pending"), GovernanceSignerKeyID: env("SECRET_API_GOV_SIGNER_KEY_ID", "edut-signer-1"), GovernancePolicyHash: strings.ToLower(env("SECRET_API_GOV_POLICY_HASH", "sha256:pending")), GovernanceRolloutChannel: strings.ToLower(env("SECRET_API_GOV_ROLLOUT_CHANNEL", "stable")), } } func (c Config) Validate() error { if c.ChainID <= 0 { return fmt.Errorf("SECRET_API_CHAIN_ID must be positive") } currency := strings.ToUpper(strings.TrimSpace(c.MintCurrency)) switch currency { case "USDC": if c.MintDecimals != 6 { return fmt.Errorf("SECRET_API_MINT_DECIMALS must be 6 for USDC") } case "ETH": if c.MintDecimals != 18 { return fmt.Errorf("SECRET_API_MINT_DECIMALS must be 18 for ETH") } default: return fmt.Errorf("SECRET_API_MINT_CURRENCY must be USDC or ETH") } amountRaw := strings.TrimSpace(c.MintAmountAtomic) amount, ok := new(big.Int).SetString(amountRaw, 10) if !ok || amount.Sign() <= 0 { return fmt.Errorf("SECRET_API_MINT_AMOUNT_ATOMIC must be a positive base-10 integer") } if c.WalletSessionTTL <= 0 { return fmt.Errorf("SECRET_API_WALLET_SESSION_TTL_SECONDS must be positive") } if isProductionDeploymentClass(c.DeploymentClass) && !c.RequireOnchainTxVerify { return fmt.Errorf("production deployment requires SECRET_API_REQUIRE_ONCHAIN_TX_VERIFICATION=true") } if c.RequireOnchainTxVerify && strings.TrimSpace(c.ChainRPCURL) == "" { return fmt.Errorf("SECRET_API_REQUIRE_ONCHAIN_TX_VERIFICATION requires SECRET_API_CHAIN_RPC_URL") } return nil } func loadRequireOnchainTxVerify() bool { if raw, ok := os.LookupEnv("SECRET_API_REQUIRE_ONCHAIN_TX_VERIFICATION"); ok { if strings.TrimSpace(raw) != "" { return parseBool(raw, false) } } return isProductionDeploymentClass(env("SECRET_API_DEPLOYMENT_CLASS", "development")) } func env(key, fallback string) string { if v := strings.TrimSpace(os.Getenv(key)); v != "" { return v } return fallback } func envInt(key string, fallback int) int { raw := strings.TrimSpace(os.Getenv(key)) if raw == "" { return fallback } value, err := strconv.Atoi(raw) if err != nil { return fallback } return value } func envBool(key string, fallback bool) bool { raw := strings.ToLower(strings.TrimSpace(os.Getenv(key))) if raw == "" { return fallback } return parseBool(raw, fallback) } func parseBool(raw string, fallback bool) bool { raw = strings.ToLower(strings.TrimSpace(raw)) switch raw { case "1", "true", "yes", "on": return true case "0", "false", "no", "off": return false default: return fallback } } func normalizeDeploymentClass(value string) string { v := strings.ToLower(strings.TrimSpace(value)) switch v { case "prod", "production": return "production" case "staging", "stage": return "staging" case "dev", "development": return "development" default: return "development" } } func isProductionDeploymentClass(value string) bool { return normalizeDeploymentClass(value) == "production" }