add solana support

This commit is contained in:
KingKDot
2026-04-05 18:18:40 -05:00
parent a310ecb460
commit 4b506d95ff
20 changed files with 1212 additions and 9 deletions
+45 -1
View File
@@ -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),
+290
View File
@@ -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
}
+68 -4
View File
@@ -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 {
+104 -1
View File
@@ -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=="],
}
}
+1
View File
@@ -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",
+51
View File
@@ -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,
+1
View File
@@ -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",
@@ -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");
@@ -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"
><i class="fa-solid fa-hammer text-orange-400"></i> Builder</a
>
<a
href="/sol-publish"
id="sol-publish-link"
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"
><i class="fa-solid fa-link-slash text-purple-400"></i> Sol Publish</a
>
<a
href="/notifications"
id="notifications-link"
@@ -137,6 +143,7 @@ export function mountNav(host) {
roleBadge: document.getElementById("role-badge"),
usersLink: document.getElementById("users-link"),
buildLink: document.getElementById("build-link"),
solPublishLink: document.getElementById("sol-publish-link"),
pluginsLink: document.getElementById("plugins-link"),
scriptsLink: document.getElementById("scripts-link"),
logsLink: document.getElementById("logs-link"),
@@ -0,0 +1,194 @@
const rpcSelect = document.getElementById("rpc-url");
const customRpcWrapper = document.getElementById("custom-rpc-wrapper");
const customRpcInput = document.getElementById("custom-rpc-url");
const privateKeyInput = document.getElementById("private-key");
const toggleKeyBtn = document.getElementById("toggle-key-visibility");
const serverUrlInput = document.getElementById("server-url");
const previewBtn = document.getElementById("preview-btn");
const publishBtn = document.getElementById("publish-btn");
const outputSection = document.getElementById("output-section");
const outputDiv = document.getElementById("output");
const walletInfo = document.getElementById("wallet-info");
const walletAddress = document.getElementById("wallet-address");
const walletBalance = document.getElementById("wallet-balance");
async function loadRpcEndpoints() {
try {
const res = await fetch("/api/sol/rpc-endpoints", { credentials: "include" });
if (res.ok) {
const data = await res.json();
if (Array.isArray(data.endpoints)) {
data.endpoints.forEach((ep) => {
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
? '<i class="fa-solid fa-eye-slash"></i> Hide'
: '<i class="fa-solid fa-eye"></i> 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 = '<i class="fa-solid fa-spinner fa-spin"></i> 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 = '<i class="fa-solid fa-eye"></i> 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 = '<i class="fa-solid fa-spinner fa-spin"></i> 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 = '<i class="fa-solid fa-paper-plane"></i> Publish to Solana';
}
});
+19
View File
@@ -159,6 +159,25 @@
<p class="text-xs text-slate-500 mt-1">
When enabled, the URL above must be a raw HTTPS link with one wss:// endpoint per line.
</p>
<label class="mt-2 flex items-center gap-2 text-xs text-slate-400">
<input type="checkbox" id="sol-memo" name="sol-memo" class="w-4 h-4" />
Use Solana memo lookup (resolve server from blockchain)
</label>
<div id="sol-settings" class="hidden mt-2 flex flex-col gap-2 pl-6 border-l-2 border-purple-800/50">
<div>
<label class="block text-xs font-medium text-slate-400 mb-1" for="sol-address">Solana Address (Base58)</label>
<input type="text" id="sol-address" name="sol-address" placeholder="YourSolanaWalletAddressHere..."
class="w-full px-2 py-1.5 bg-slate-800/60 border border-slate-700 rounded text-sm focus:outline-none focus:border-purple-500 transition-colors font-mono" />
</div>
<div>
<label class="block text-xs font-medium text-slate-400 mb-1" for="sol-rpc-endpoints">RPC Endpoints (one per line)</label>
<textarea id="sol-rpc-endpoints" name="sol-rpc-endpoints" rows="4" placeholder="https://api.mainnet-beta.solana.com&#10;https://solana-rpc.publicnode.com"
class="w-full px-2 py-1.5 bg-slate-800/60 border border-slate-700 rounded text-xs focus:outline-none focus:border-purple-500 transition-colors font-mono"></textarea>
</div>
<p class="text-xs text-slate-500">
Client will query these endpoints to find the encrypted server URL from the Solana address's memo transactions.
</p>
</div>
</div>
<div>
+123
View File
@@ -0,0 +1,123 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Solana Memo Publisher - Overlord</title>
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<link
href="https://fonts.googleapis.com/css2?family=Inter:wght@400;600;700&display=swap"
rel="stylesheet"
/>
<link rel="stylesheet" href="/assets/tailwind.css" />
<link
rel="stylesheet"
href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.2/css/all.min.css"
crossorigin="anonymous"
referrerpolicy="no-referrer"
/>
<link rel="icon" type="image/x-icon" href="/assets/favicon.ico" />
<link rel="stylesheet" href="/assets/main.css" />
<link rel="stylesheet" href="/assets/custom.css" />
<script src="/assets/toast.js"></script>
<script src="/assets/ripple.js"></script>
</head>
<body class="min-h-screen bg-slate-950 text-slate-100">
<header id="top-nav"></header>
<main class="px-5 py-6">
<div class="max-w-3xl mx-auto flex flex-col gap-5">
<div>
<h1 class="text-2xl font-semibold flex items-center gap-2">
<i class="fa-solid fa-link-slash text-purple-400"></i> Solana Memo Publisher
</h1>
<p class="text-slate-400 mt-1">
Publish an encrypted server URL as a Solana memo transaction.
Clients configured with Solana lookup will query this address to find the server.
</p>
</div>
<div class="bg-slate-900/70 border border-slate-800 rounded-lg p-5 flex flex-col gap-5">
<!-- RPC Endpoint -->
<div>
<label class="block text-sm font-medium text-slate-300 mb-2" for="rpc-url">RPC Endpoint</label>
<select id="rpc-url"
class="w-full px-3 py-2 bg-slate-800/60 border border-slate-700 rounded-lg focus:outline-none focus:border-purple-500 transition-colors">
</select>
<div id="custom-rpc-wrapper" class="hidden mt-2">
<input type="text" id="custom-rpc-url" placeholder="https://your-rpc-endpoint.com"
class="w-full px-3 py-2 bg-slate-800/60 border border-slate-700 rounded-lg focus:outline-none focus:border-purple-500 transition-colors" />
</div>
</div>
<!-- Private Key -->
<div>
<label class="block text-sm font-medium text-slate-300 mb-2" for="private-key">Solana Private Key (Base58)</label>
<div class="flex items-center gap-2">
<input type="password" id="private-key" placeholder="Enter your Solana wallet private key"
class="flex-1 min-w-0 px-3 py-2 bg-slate-800/60 border border-slate-700 rounded-lg focus:outline-none focus:border-purple-500 transition-colors font-mono text-sm" />
<button type="button" id="toggle-key-visibility"
class="shrink-0 px-3 py-2 text-xs text-slate-400 hover:text-slate-200 bg-slate-800/60 border border-slate-700 rounded-lg transition-colors">
<i class="fa-solid fa-eye"></i> Show
</button>
</div>
<p class="text-xs text-slate-500 mt-1">
<i class="fa-solid fa-shield-halved text-amber-400"></i>
Your private key is never stored — it's used in-memory only for signing and discarded immediately.
</p>
<div id="wallet-info" class="hidden mt-2 p-2 bg-slate-800/40 rounded-lg text-xs text-slate-400">
<span id="wallet-address"></span>
<span id="wallet-balance" class="ml-2"></span>
</div>
</div>
<!-- Server URL -->
<div>
<label class="block text-sm font-medium text-slate-300 mb-2" for="server-url">Server URL to Publish</label>
<input type="text" id="server-url" placeholder="wss://your-server:5173"
class="w-full px-3 py-2 bg-slate-800/60 border border-slate-700 rounded-lg focus:outline-none focus:border-purple-500 transition-colors" />
<p class="text-xs text-slate-500 mt-1">
The server URL that clients will connect to. Multiple URLs can be separated by newlines.
</p>
</div>
<!-- Action Buttons -->
<div class="flex gap-3 flex-wrap">
<button type="button" id="preview-btn"
class="inline-flex items-center gap-2 px-4 py-2 rounded-lg bg-slate-700 hover:bg-slate-600 text-slate-200 border border-slate-600 transition-colors">
<i class="fa-solid fa-eye"></i> Preview Memo
</button>
<button type="button" id="publish-btn"
class="inline-flex items-center gap-2 px-4 py-2 rounded-lg bg-purple-700 hover:bg-purple-600 text-white border border-purple-600 transition-colors">
<i class="fa-solid fa-paper-plane"></i> Publish to Solana
</button>
</div>
<!-- Output -->
<div id="output-section" class="hidden">
<label class="block text-sm font-medium text-slate-300 mb-2">Result</label>
<div id="output" class="p-3 bg-slate-800/60 border border-slate-700 rounded-lg text-sm font-mono break-all whitespace-pre-wrap max-h-64 overflow-y-auto">
</div>
</div>
</div>
<!-- Info -->
<div class="bg-slate-900/50 border border-slate-800 rounded-lg p-4 text-sm text-slate-400">
<h3 class="text-slate-300 font-medium mb-2"><i class="fa-solid fa-circle-info text-blue-400"></i> How it works</h3>
<ul class="list-disc list-inside flex flex-col gap-1">
<li>The server URL is encrypted with AES-256-GCM using SHA-256 of your server's Agent Token as the key.</li>
<li>The encrypted memo is published as a Solana memo transaction from your wallet.</li>
<li>Clients built with Solana mode query your wallet address for the latest memo and decrypt it.</li>
<li>Publishing costs approximately 0.000005 SOL (~$0.001).</li>
<li>To update the server URL, just publish a new memo — clients always use the newest one.</li>
</ul>
</div>
</div>
</main>
<script type="module" src="/assets/nav.js"></script>
<script src="/assets/sol-publish.js"></script>
</body>
</html>
+3
View File
@@ -38,4 +38,7 @@ export type BuildConfig = {
requireAdmin?: boolean;
outputExtension?: string;
sleepSeconds?: number;
solMemo?: boolean;
solAddress?: string;
solRpcEndpoints?: string;
};
+3 -3
View File
@@ -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);
+2
View File
@@ -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,
@@ -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;
@@ -40,6 +40,7 @@ type HttpDispatchDeps<
handleAutoScriptsRoutes: RouteHandler;
handleAutoDeployRoutes: RouteHandlerWithDeps<TAutoDeploy>;
handleEnrollmentRoutes: RouteHandler;
handleSolRoutes: RouteHandler;
handleUsersRoutes: RouteHandlerWithServer<TServer>;
handleBuildRoutes: RouteHandlerWithServerDeps<TServer, TBuild>;
handleDeployRoutes: RouteHandlerWithServerDeps<TServer, TDeploy>;
@@ -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;
@@ -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,
@@ -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" },
@@ -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<Response | null> {
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 });
}
}