web/backend/secretapi/config.go
Joshua c80b1db18b
Some checks are pending
check / secretapi (push) Waiting to run
W0: add deterministic quote cost envelope and docs sync
2026-02-19 18:02:30 -08:00

177 lines
6.3 KiB
Go

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"
}