first commit

This commit is contained in:
ether 2026-01-21 04:17:41 +02:00
commit 759da033fe
15 changed files with 1905 additions and 0 deletions

4
.gitignore vendored Normal file
View file

@ -0,0 +1,4 @@
private_keys.txt
*.txt
proxies.txt
__MACOSX

BIN
axiom-checker Executable file

Binary file not shown.

133
axiom.go Normal file
View file

@ -0,0 +1,133 @@
package main
import (
"bytes"
"encoding/json"
"fmt"
"io"
"net/http"
)
type VerifyResponse struct {
Sol string `json:"sol"`
Evm string `json:"evm"`
IsNewUser bool `json:"isNewUser"`
}
func getAxiomWallet(privateKeyB58 string) (*VerifyResponse, error) {
kp, err := keypairFromSecretKey(privateKeyB58)
if err != nil {
return nil, fmt.Errorf("failed to create keypair: %w", err)
}
addr := kp.PublicKeyBase58()
// Get nonce
transport, err := getNextProxy()
if err != nil {
return nil, fmt.Errorf("failed to get proxy: %w", err)
}
client := &http.Client{Transport: transport}
nonceReq := map[string]string{"walletAddress": addr}
nonceBody, _ := json.Marshal(nonceReq)
req, err := http.NewRequest("POST", "https://api.axiom.trade/wallet-nonce", bytes.NewReader(nonceBody))
if err != nil {
return nil, fmt.Errorf("failed to create nonce request: %w", err)
}
req.Header.Set("accept", "application/json, text/plain, */*")
req.Header.Set("content-type", "application/json")
req.Header.Set("origin", "https://axiom.trade")
req.Header.Set("referer", "https://axiom.trade/")
req.Header.Set("user-agent", "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36")
resp, err := client.Do(req)
if err != nil {
return nil, fmt.Errorf("failed to get nonce: %w", err)
}
defer resp.Body.Close()
nonceText, _ := io.ReadAll(resp.Body)
nonce := string(nonceText)
// Try to parse as JSON
var nonceData map[string]interface{}
if json.Unmarshal(nonceText, &nonceData) == nil {
if n, ok := nonceData["nonce"].(string); ok {
nonce = n
}
}
// Sign message
message := fmt.Sprintf("By signing, you agree to Axiom's Terms of Use & Privacy Policy (axiom.trade/legal).\n\nNonce: %s", nonce)
signature := signMessage(kp.PrivateKey, []byte(message))
// Verify wallet
verifyReq := map[string]interface{}{
"walletAddress": addr,
"signature": signature,
"nonce": nonce,
"referrer": nil,
"allowRegistration": true,
"isVerify": false,
"forAddCredential": false,
"allowLinking": false,
}
verifyBody, _ := json.Marshal(verifyReq)
req2, err := http.NewRequest("POST", "https://api.axiom.trade/verify-wallet-v2", bytes.NewReader(verifyBody))
if err != nil {
return nil, fmt.Errorf("failed to create verify request: %w", err)
}
req2.Header.Set("accept", "application/json, text/plain, */*")
req2.Header.Set("content-type", "application/json")
req2.Header.Set("origin", "https://axiom.trade")
req2.Header.Set("referer", "https://axiom.trade/")
req2.Header.Set("user-agent", "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36")
resp2, err := client.Do(req2)
if err != nil {
return nil, fmt.Errorf("failed to verify wallet: %w", err)
}
defer resp2.Body.Close()
responseText, _ := io.ReadAll(resp2.Body)
if resp2.StatusCode != http.StatusOK {
errMsg := string(responseText)
if len(errMsg) > 200 {
errMsg = errMsg[:200]
}
return nil, fmt.Errorf("axiom error (%d): %s", resp2.StatusCode, errMsg)
}
var result VerifyResponse
if err := json.Unmarshal(responseText, &result); err != nil {
return nil, fmt.Errorf("failed to parse response: %w", err)
}
return &result, nil
}
func getAxiomBalance(pk string) (string, float64, error) {
ax, err := getAxiomWallet(pk)
if err != nil {
return "", 0, err
}
if ax.IsNewUser {
return ax.Sol, 0, nil
}
cfg := &Config{JupiterRateLimitMs: 1100}
portfolio, err := getPortfolioValue(ax.Sol, cfg)
if err != nil {
return ax.Sol, 0, err
}
return ax.Sol, portfolio.Value, nil
}

56
config.go Normal file
View file

@ -0,0 +1,56 @@
package main
import (
"encoding/json"
"fmt"
"os"
)
type Config struct {
JupiterRateLimitMs int `json:"jupiterRateLimitMs"`
JupiterApiKeys []string `json:"jupiterApiKeys"`
Threads int `json:"threads"`
EnableSolanaCheck bool `json:"enableSolanaCheck"`
EnableAxiomModule bool `json:"enableAxiomModule"`
EnableEVMModule bool `json:"enableEVMModule"`
}
func loadConfig() (*Config, error) {
data, err := os.ReadFile("config.json")
if err != nil {
return &Config{
JupiterRateLimitMs: 1100,
Threads: 20,
EnableSolanaCheck: true,
EnableAxiomModule: false,
EnableEVMModule: false,
}, nil
}
var cfg Config
if err := json.Unmarshal(data, &cfg); err != nil {
return nil, fmt.Errorf("failed to parse config: %w", err)
}
if cfg.JupiterRateLimitMs <= 0 {
cfg.JupiterRateLimitMs = 1100
}
if cfg.Threads <= 0 {
cfg.Threads = 20
}
return &cfg, nil
}
func saveConfig(cfg *Config) error {
data, err := json.MarshalIndent(cfg, "", " ")
if err != nil {
return fmt.Errorf("failed to marshal config: %w", err)
}
if err := os.WriteFile("config.json", data, 0644); err != nil {
return fmt.Errorf("failed to write config: %w", err)
}
return nil
}

12
config.json Normal file
View file

@ -0,0 +1,12 @@
{
"jupiterRateLimitMs": 1100,
"jupiterApiKeys": [
"109e558d-f91c-4f78-8097-2ced322b6fd8",
"c1899449-ab4b-4f49-be78-b8165626ed8e",
"3016e992-0eb0-4ee4-b560-f962eb77f9e2"
],
"threads": 25,
"enableSolanaCheck": true,
"enableAxiomModule": true,
"enableEVMModule": true
}

327
debank.go Normal file
View file

@ -0,0 +1,327 @@
package main
import (
"crypto/rand"
"crypto/sha256"
"encoding/hex"
"encoding/json"
"fmt"
"io"
"math"
"math/big"
"net/http"
"net/url"
"sort"
"strconv"
"strings"
"time"
)
type DeBankToken struct {
Symbol string `json:"symbol"`
Chain string `json:"chain"`
Balance string `json:"balance"`
Decimals int `json:"decimals"`
Price float64 `json:"price"`
Amount float64 `json:"amount"`
}
type DeBankResponse struct {
Data []DeBankToken `json:"data"`
}
var networks = map[string]string{
"eth": "Ethereum",
"bsc": "BSC",
"avax": "Avalanche",
"matic": "Polygon",
"arb": "Arbitrum",
"ftm": "Fantom",
"op": "Optimism",
"cro": "Cronos",
}
type DebankClient struct {
account *BrowserUID
nonce *DebankNonce
signer *DebankSigner
client *http.Client
}
func NewDebankClient(transport *http.Transport) *DebankClient {
if transport == nil {
transport = &http.Transport{}
}
account := makeBrowserUID()
return &DebankClient{
account: &account,
nonce: NewDebankNonce(),
signer: &DebankSigner{},
client: &http.Client{
Transport: transport,
Timeout: 30 * time.Second,
},
}
}
func (d *DebankClient) Get(pathname string, params map[string]string) ([]byte, error) {
return d.request("GET", pathname, params)
}
func (d *DebankClient) request(method, pathname string, params map[string]string) ([]byte, error) {
values := url.Values{}
for k, v := range params {
values.Add(k, v)
}
keys := make([]string, 0, len(values))
for k := range values {
keys = append(keys, k)
}
sort.Strings(keys)
for i, j := 0, len(keys)-1; i < j; i, j = i+1, j-1 {
keys[i], keys[j] = keys[j], keys[i]
}
var queryParts []string
for _, k := range keys {
queryParts = append(queryParts, url.QueryEscape(k)+"="+url.QueryEscape(values.Get(k)))
}
queryString := strings.Join(queryParts, "&")
apiURL := "https://api.debank.com" + pathname
if queryString != "" {
apiURL += "?" + queryString
}
req, err := http.NewRequest(method, apiURL, nil)
if err != nil {
return nil, err
}
headers := d.makeHeaders(method, pathname, queryString)
for key, value := range headers {
req.Header.Set(key, value)
}
resp, err := d.client.Do(req)
if err != nil {
return nil, err
}
defer resp.Body.Close()
body, err := io.ReadAll(resp.Body)
if err != nil {
return nil, err
}
if resp.StatusCode != http.StatusOK {
return nil, fmt.Errorf("debank API error: status %d, body: %s", resp.StatusCode, string(body))
}
var errorResp struct {
ErrorCode int `json:"error_code"`
ErrorMsg string `json:"error_msg"`
}
if err := json.Unmarshal(body, &errorResp); err == nil && errorResp.ErrorCode != 0 {
return nil, fmt.Errorf("%d: %s", errorResp.ErrorCode, errorResp.ErrorMsg)
}
return body, nil
}
func (d *DebankClient) makeHeaders(method, pathname, query string) map[string]string {
if len(query) > 0 && query[0] == '?' {
query = query[1:]
}
nonceStr := d.nonce.Next()
ts := time.Now().Unix()
signature := d.signer.Sign(method, pathname, query, nonceStr, ts)
accountJSON, _ := json.Marshal(d.account)
return map[string]string{
"x-api-nonce": "n_" + nonceStr,
"x-api-sign": signature,
"x-api-ts": strconv.FormatInt(ts, 10),
"x-api-ver": "v2",
"source": "web",
"account": string(accountJSON),
"User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36",
"Accept": "*/*",
"Accept-Language": "en-US,en;q=0.9",
"Referer": "https://debank.com/",
"Origin": "https://debank.com",
}
}
type BrowserUID struct {
RandomAt int64 `json:"random_at"`
RandomID string `json:"random_id"`
UserAddr *string `json:"user_addr"`
}
type DebankNonce struct {
abc string
local int64
}
func NewDebankNonce() *DebankNonce {
return &DebankNonce{
abc: "0123456789ABCDEFGHIJKLMNOPQRSTUVWXTZabcdefghiklmnopqrstuvwxyz",
local: 0,
}
}
func (n *DebankNonce) pcg32() uint32 {
const multiplier = 6364136223846793005
const increment = 1
n.local = n.local*multiplier + increment
return uint32((uint64(n.local) >> 33) & 0xFFFFFFFF)
}
func (n *DebankNonce) Next() string {
result := make([]byte, 40)
for i := 0; i < 40; i++ {
index := int(float64(n.pcg32()) / 2147483647.0 * 61.0)
if index >= len(n.abc) {
index = len(n.abc) - 1
}
result[i] = n.abc[index]
}
return string(result)
}
type DebankSigner struct{}
func (s *DebankSigner) Sign(method, pathname, query string, nonce string, ts int64) string {
data1 := method + "\n" + pathname + "\n" + query
hash1 := sha256.Sum256([]byte(data1))
hash1Hex := hex.EncodeToString(hash1[:])
data2 := "debank-api\nn_" + nonce + "\n" + strconv.FormatInt(ts, 10)
hash2 := sha256.Sum256([]byte(data2))
hash2Hex := hex.EncodeToString(hash2[:])
xor1, xor2 := s.xor(hash2Hex)
h1Data := xor1 + hash1Hex
h1 := sha256.Sum256([]byte(h1Data))
h2Data := append([]byte(xor2), h1[:]...)
h2 := sha256.Sum256(h2Data)
return hex.EncodeToString(h2[:])
}
func (s *DebankSigner) xor(hash string) (string, string) {
rez1 := make([]byte, 64)
rez2 := make([]byte, 64)
for i := 0; i < 64; i++ {
rez1[i] = hash[i] ^ 54
rez2[i] = hash[i] ^ 92
}
return string(rez1), string(rez2)
}
func makeBrowserUID() BrowserUID {
return BrowserUID{
RandomAt: time.Now().Unix(),
RandomID: strings.ReplaceAll(generateUUID(), "-", ""),
UserAddr: nil,
}
}
func generateUUID() string {
b := make([]byte, 16)
rand.Read(b)
b[6] = (b[6] & 0x0f) | 0x40
b[8] = (b[8] & 0x3f) | 0x80
return fmt.Sprintf("%x-%x-%x-%x-%x", b[0:4], b[4:6], b[6:8], b[8:10], b[10:])
}
type DebankUser struct {
ID string `json:"id"`
Desc struct {
BornAt int64 `json:"born_at"`
USDValue float64 `json:"usd_value"`
} `json:"desc"`
Stats struct {
USDValue float64 `json:"usd_value"`
} `json:"stats"`
}
type DebankUserResponse struct {
User DebankUser `json:"user"`
}
func (d *DebankClient) GetUser(address string) (*DebankUserResponse, error) {
body, err := d.Get("/user", map[string]string{
"id": address,
})
if err != nil {
return nil, err
}
var wrapper struct {
Data DebankUserResponse `json:"data"`
}
if err := json.Unmarshal(body, &wrapper); err != nil {
return nil, err
}
return &wrapper.Data, nil
}
func getDeBankBalance(address string) (float64, string, error) {
transport, err := getNextProxy()
if err != nil {
transport = &http.Transport{}
}
client := NewDebankClient(transport)
userResp, err := client.GetUser(address)
if err != nil {
return 0, "", fmt.Errorf("debank API error: %v", err)
}
totalValue := userResp.User.Desc.USDValue
if totalValue == 0 {
totalValue = userResp.User.Stats.USDValue
}
if totalValue == 0 {
return 0, "", nil
}
return totalValue, "", nil
}
func getBalanceValue(balance string, decimals int) float64 {
if balance == "" {
return 0
}
bal := new(big.Int)
bal.SetString(balance, 10)
if bal.Sign() == 0 {
return 0
}
divisor := new(big.Float).SetInt(new(big.Int).Exp(big.NewInt(10), big.NewInt(int64(decimals)), nil))
balFloat := new(big.Float).SetInt(bal)
result := new(big.Float).Quo(balFloat, divisor)
resultFloat, _ := result.Float64()
return math.Round(resultFloat*100000) / 100000
}

176
display.go Normal file
View file

@ -0,0 +1,176 @@
package main
import (
"fmt"
"sync"
"time"
)
// ANSI color codes
const (
ColorReset = "\033[0m"
ColorRed = "\033[31m"
ColorGreen = "\033[32m"
ColorYellow = "\033[33m"
ColorBlue = "\033[34m"
ColorPurple = "\033[35m"
ColorCyan = "\033[36m"
ColorWhite = "\033[37m"
ColorBold = "\033[1m"
)
// ProgressTracker tracks checking progress
type ProgressTracker struct {
Total int
Checked int
WithBalance int
TotalValue float64
StartTime time.Time
mu sync.Mutex
}
// NewProgressTracker creates a new progress tracker
func NewProgressTracker(total int) *ProgressTracker {
return &ProgressTracker{
Total: total,
StartTime: time.Now(),
}
}
// Increment increments progress counters
func (pt *ProgressTracker) Increment(balance float64) {
pt.mu.Lock()
defer pt.mu.Unlock()
pt.Checked++
pt.TotalValue += balance
if balance > 0 {
pt.WithBalance++
}
}
// GetStats returns current statistics
func (pt *ProgressTracker) GetStats() (int, int, int, float64, time.Duration) {
pt.mu.Lock()
defer pt.mu.Unlock()
elapsed := time.Since(pt.StartTime)
return pt.Total, pt.Checked, pt.WithBalance, pt.TotalValue, elapsed
}
// CalculateETA calculates estimated time to completion
func (pt *ProgressTracker) CalculateETA() string {
pt.mu.Lock()
defer pt.mu.Unlock()
if pt.Checked == 0 {
return "calculating..."
}
elapsed := time.Since(pt.StartTime)
avgTimePerKey := elapsed / time.Duration(pt.Checked)
remaining := pt.Total - pt.Checked
eta := avgTimePerKey * time.Duration(remaining)
if eta < time.Second {
return "< 1s"
} else if eta < time.Minute {
return fmt.Sprintf("%ds", int(eta.Seconds()))
} else if eta < time.Hour {
mins := int(eta.Minutes())
secs := int(eta.Seconds()) % 60
return fmt.Sprintf("%dm %ds", mins, secs)
} else {
hours := int(eta.Hours())
mins := int(eta.Minutes()) % 60
return fmt.Sprintf("%dh %dm", hours, mins)
}
}
type StatusDisplay struct {
tracker *ProgressTracker
stopChan chan bool
wg sync.WaitGroup
}
// NewStatusDisplay creates new status display
func NewStatusDisplay(tracker *ProgressTracker) *StatusDisplay {
return &StatusDisplay{
tracker: tracker,
stopChan: make(chan bool),
}
}
// Start starts the display update loop
func (sd *StatusDisplay) Start() {
sd.wg.Add(1)
go sd.updateLoop()
}
// Stop stops the display
func (sd *StatusDisplay) Stop() {
sd.stopChan <- true
sd.wg.Wait()
sd.clearLine()
}
func (sd *StatusDisplay) clearLine() {
fmt.Print("\r\033[K")
}
func (sd *StatusDisplay) updateLoop() {
defer sd.wg.Done()
ticker := time.NewTicker(100 * time.Millisecond)
defer ticker.Stop()
for {
select {
case <-sd.stopChan:
return
case <-ticker.C:
sd.update()
}
}
}
func (sd *StatusDisplay) update() {
total, checked, withBalance, totalValue, _ := sd.tracker.GetStats()
eta := sd.tracker.CalculateETA()
percentage := 0
if total > 0 {
percentage = (checked * 100) / total
}
progressColor := ColorYellow
if percentage >= 100 {
progressColor = ColorGreen
} else if percentage >= 75 {
progressColor = ColorCyan
}
status := fmt.Sprintf("%s%s[%d%%]%s %s%d/%d%s checked | %s%d%s with balance | %s$%.2f%s total | ETA: %s%s%s",
ColorBold, progressColor, percentage, ColorReset,
ColorCyan, checked, total, ColorReset,
ColorGreen, withBalance, ColorReset,
ColorYellow, totalValue, ColorReset,
ColorPurple, eta, ColorReset,
)
sd.clearLine()
fmt.Print("\r" + status)
}
func ColorPrint(color, text string) {
fmt.Print(color + text + ColorReset)
}
func ColorPrintln(color, text string) {
fmt.Println(color + text + ColorReset)
}
func colorPrint(color, text string) {
fmt.Print(color + text + ColorReset)
}
func colorPrintln(color, text string) {
fmt.Println(color + text + ColorReset)
}

14
go.mod Normal file
View file

@ -0,0 +1,14 @@
module axiom-checker
go 1.21
require (
github.com/manifoldco/promptui v0.9.0
github.com/mr-tron/base58 v1.2.0
golang.org/x/net v0.20.0
)
require (
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e // indirect
golang.org/x/sys v0.16.0 // indirect
)

15
go.sum Normal file
View file

@ -0,0 +1,15 @@
github.com/chzyer/logex v1.1.10 h1:Swpa1K6QvQznwJRcfTfQJmTE72DqScAa40E+fbHEXEE=
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e h1:fY5BOSpyZCqRo5OhCuC+XN+r/bBCmeuuJtjz+bCNIf8=
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1 h1:q763qf9huN11kDQavWsoZXJNW3xEE4JJyHa5Q25/sd8=
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
github.com/manifoldco/promptui v0.9.0 h1:3V4HzJk1TtXW1MTZMP7mdlwbBpIinw3HztaIlYthEiA=
github.com/manifoldco/promptui v0.9.0/go.mod h1:ka04sppxSGFAtxX0qhlYQjISsg9mR4GWtQEhdbn6Pgg=
github.com/mr-tron/base58 v1.2.0 h1:T/HDJBh4ZCPbU39/+c3rRvE0uKBQlU27+QI8LJ4t64o=
github.com/mr-tron/base58 v1.2.0/go.mod h1:BinMc/sQntlIE1frQmRFPUoPA1Zkr8VRgBdjWI2mNwc=
golang.org/x/net v0.20.0 h1:aCL9BSgETF1k+blQaYUBx9hJ9LOGP3gAVemcZlf1Kpo=
golang.org/x/net v0.20.0/go.mod h1:z8BVo6PvndSri0LbOE3hAn0apkU+1YvI6E70E9jsnvY=
golang.org/x/sys v0.0.0-20181122145206-62eef0e2fa9b/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.16.0 h1:xWw16ngr6ZMtmxDyKyIgsE93KNKz5HKmMa3b8ALHidU=
golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=

475
main.go Normal file
View file

@ -0,0 +1,475 @@
package main
import (
"flag"
"fmt"
"os"
"sort"
"strings"
"sync"
"time"
)
type Mode string
const (
ModePrivateKeys Mode = "private-keys"
ModeAxiom Mode = "axiom"
ModeBoth Mode = "both"
)
type CheckResult struct {
Line string
Balance float64
}
type Statistics struct {
TotalBalance float64
KeysWithBalance int
EmptyKeys int
TotalKeys int
Duration time.Duration
Mode string
}
var (
results []CheckResult
resultsMutex sync.Mutex
stats Statistics
)
func addResult(line string, balance float64) {
resultsMutex.Lock()
defer resultsMutex.Unlock()
results = append(results, CheckResult{Line: line, Balance: balance})
stats.TotalBalance += balance
if balance > 0 {
stats.KeysWithBalance++
} else {
stats.EmptyKeys++
}
}
func main() {
addressFlag := flag.String("address", "", "Check specific address")
flag.Parse()
printHeader()
cfg, err := loadConfig()
if err != nil {
fmt.Fprintf(os.Stderr, "Failed to load config: %v\n", err)
os.Exit(1)
}
initProxies()
if *addressFlag != "" {
checkSingleAddress(*addressFlag, cfg)
return
}
for {
choice, err := showMainMenu(cfg)
if err != nil {
fmt.Fprintf(os.Stderr, "\nCancelled\n")
os.Exit(1)
}
switch choice {
case "settings":
if err := showSettingsMenu(cfg); err != nil {
fmt.Fprintf(os.Stderr, "\nSettings cancelled\n")
continue
}
case "start":
initApiKeyWorkers(cfg)
fmt.Println()
printConfigSummary(cfg)
moduleManager := NewModuleManager(cfg)
enabledModules := moduleManager.GetEnabledModules()
if cfg.EnableSolanaCheck || len(enabledModules) > 0 {
colorPrint(ColorGreen, "Active modules: ")
modules := []string{}
if cfg.EnableSolanaCheck {
modules = append(modules, "Solana")
}
for _, m := range enabledModules {
modules = append(modules, m.Name())
}
for i, m := range modules {
if i > 0 {
fmt.Print(", ")
}
colorPrint(ColorCyan, m)
}
fmt.Println()
}
fmt.Printf("Loaded proxies: %d\n", len(proxies))
keys := loadPrivateKeys()
if len(keys) == 0 {
fmt.Fprintln(os.Stderr, "\nNo keys found!\nAdd to: private_keys.txt or keys/*.txt")
continue
}
stats.TotalKeys = len(keys)
runChecker(keys, cfg, moduleManager)
fmt.Println("\nPress Enter to return to main menu...")
fmt.Scanln()
printHeader()
case "exit":
colorPrintln(ColorYellow, "\nGoodbye!")
os.Exit(0)
}
}
}
func printHeader() {
colorPrintln(ColorCyan, "")
colorPrintln(ColorCyan, " ___ _ ___ _ _ ")
colorPrintln(ColorCyan, " / _ \\__ _(_)___ _ __ ___ / __| |_ ___ __| |_____ _ _ ")
colorPrintln(ColorCyan, " | (_| \\ \\ / / _ \\ ' \\/ -_) | (__| ' \\/ -_) _| / / -_) '_|")
colorPrintln(ColorCyan, " \\___/_\\_\\_\\\\___/_|_|_\\___| \\___|_||_\\___\\__|_\\_\\___|_| ")
fmt.Print(" ")
colorPrintln(ColorYellow, "by @septum")
colorPrintln(ColorCyan, "")
}
func checkSingleAddress(address string, cfg *Config) {
if strings.HasPrefix(address, "0x") || strings.HasPrefix(address, "0X") {
balance, details, err := getDeBankBalance(address)
if err != nil {
fmt.Printf("[evm address][debank] %s - Error: %v\n", address, err)
return
}
fmt.Printf("[evm address][debank] %s - $%.2f\n", address, balance)
if details != "" {
fmt.Print(details)
}
} else {
portfolio, err := getPortfolioValue(address, cfg)
if err != nil {
fmt.Fprintf(os.Stderr, "Error: %v\n", err)
os.Exit(1)
}
tokenStr := ""
if portfolio.Value > 0 {
if portfolio.TokenCount > 10 {
tokenStr = " (>10 tokens)"
} else {
tokenStr = fmt.Sprintf(" (%d tokens)", portfolio.TokenCount)
}
}
valueStr := "0.00"
if portfolio.Value > 0 {
valueStr = fmt.Sprintf("%.2f", portfolio.Value)
}
fmt.Printf("[solana address][jup.ag] %s - $%s%s\n", address, valueStr, tokenStr)
}
}
func runChecker(keys []string, cfg *Config, moduleManager *ModuleManager) {
outputFile := fmt.Sprintf("results_%s.txt", time.Now().Format("2006-01-02_15-04-05"))
fmt.Printf("\n%s\n", strings.Repeat("━", 60))
fmt.Printf("Keys to check: %d\n", len(keys))
fmt.Printf("Output file: %s\n", outputFile)
fmt.Printf("%s\n\n", strings.Repeat("━", 60))
startTime := time.Now()
processKeys(keys, cfg, moduleManager)
stats.Duration = time.Since(startTime)
sort.Slice(results, func(i, j int) bool { return results[i].Balance > results[j].Balance })
if err := writeResults(outputFile); err != nil {
fmt.Fprintf(os.Stderr, "Failed to write results: %v\n", err)
return
}
fmt.Printf("\n\n")
colorPrintln(ColorGreen, fmt.Sprintf("✅ Results saved to: %s", outputFile))
printSummary()
}
func processKeys(keys []string, cfg *Config, moduleManager *ModuleManager) {
tracker := NewProgressTracker(len(keys))
display := NewStatusDisplay(tracker)
display.Start()
defer display.Stop()
for i := 0; i < len(keys); i += cfg.Threads {
end := min(i+cfg.Threads, len(keys))
var wg sync.WaitGroup
for _, key := range keys[i:end] {
wg.Add(1)
go func(pk string) {
defer wg.Done()
checkKey(pk, cfg, moduleManager, tracker)
}(key)
}
wg.Wait()
}
}
func checkKey(pk string, cfg *Config, moduleManager *ModuleManager, tracker *ProgressTracker) {
defer func() {
if r := recover(); r != nil {
line := fmt.Sprintf("Error: %v", r)
addResult(line, 0)
tracker.Increment(0)
}
}()
var line string
totalBalance := 0.0
if cfg.EnableSolanaCheck {
kp, err := keypairFromSecretKey(pk)
if err != nil {
addResult(fmt.Sprintf("Error: %v", err), 0)
tracker.Increment(0)
return
}
addr := kp.PublicKeyBase58()
pkBalance, _ := getPortfolioValue(addr, cfg)
line += fmt.Sprintf("[Private Key] %s\n", pk)
line += fmt.Sprintf(" [SOL] %s - $%.2f", addr, pkBalance.Value)
if pkBalance.TokenCount > 0 {
line += fmt.Sprintf(" (%d tokens)", pkBalance.TokenCount)
}
line += "\n"
totalBalance += pkBalance.Value
} else {
line += fmt.Sprintf("[Private Key] %s\n", pk)
}
moduleBalance, moduleDetails := moduleManager.CheckAll(pk)
if moduleDetails != "" {
line += moduleDetails
totalBalance += moduleBalance
}
if totalBalance > 0 {
line += fmt.Sprintf(" %s[TOTAL: $%.2f]%s\n", ColorYellow, totalBalance, ColorReset)
}
addResult(line, totalBalance)
tracker.Increment(totalBalance)
}
func min(a, b int) int {
if a < b {
return a
}
return b
}
func printSummary() {
fmt.Println("\n" + strings.Repeat("=", 60))
fmt.Println("SUMMARY")
fmt.Println(strings.Repeat("=", 60))
fmt.Printf("Mode: %s\n", stats.Mode)
fmt.Printf("Total Balance: $%.2f\n", stats.TotalBalance)
fmt.Printf("Keys with Balance: %d\n", stats.KeysWithBalance)
fmt.Printf("Empty Keys: %d\n", stats.EmptyKeys)
fmt.Printf("Total Keys: %d\n", stats.TotalKeys)
fmt.Printf("Duration: %s\n", stats.Duration.Round(time.Second))
fmt.Println(strings.Repeat("=", 60))
}
func writeResults(filename string) error {
f, err := os.Create(filename)
if err != nil {
return err
}
defer f.Close()
fmt.Fprintf(f, "RESULTS\n")
fmt.Fprintf(f, "Generated: %s\n\n", time.Now().Format("2006-01-02 15:04:05"))
fmt.Fprintf(f, "Mode: %s\n", stats.Mode)
fmt.Fprintf(f, "Total Balance: $%.2f\n", stats.TotalBalance)
fmt.Fprintf(f, "Accounts with Balance: %d\n", stats.KeysWithBalance)
fmt.Fprintf(f, ":0.01$> accounts%d\n", stats.EmptyKeys)
fmt.Fprintf(f, "Total Keys: %d\n", stats.TotalKeys)
fmt.Fprintf(f, "Duration: %s\n", stats.Duration.Round(time.Second))
fmt.Fprintf(f, "\n%s\n\n", strings.Repeat("=", 80))
// Write sorted results
for _, result := range results {
fmt.Fprintln(f, result.Line)
}
return nil
}
func check(pk string, mode Mode, cfg *Config) {
defer func() {
if r := recover(); r != nil {
line := fmt.Sprintf("Error: %v", r)
fmt.Println(line)
addResult(line, 0)
}
}()
if mode == ModePrivateKeys {
kp, err := keypairFromSecretKey(pk)
if err != nil {
line := fmt.Sprintf("Error: %v", err)
fmt.Println(line)
addResult(line, 0)
return
}
addr := kp.PublicKeyBase58()
portfolio, err := getPortfolioValue(addr, cfg)
if err != nil {
line := fmt.Sprintf("Error: %v", err)
fmt.Println(line)
addResult(line, 0)
return
}
tokenStr := ""
if portfolio.Value > 0 {
if portfolio.TokenCount > 10 {
tokenStr = " (>10 tokens)"
} else {
tokenStr = fmt.Sprintf(" (%d tokens)", portfolio.TokenCount)
}
}
valueStr := "0.00"
if portfolio.Value > 0 {
valueStr = fmt.Sprintf("%.2f", portfolio.Value)
}
line := fmt.Sprintf("[private key][jup.ag] %s | %s - $%s%s", pk, addr, valueStr, tokenStr)
fmt.Println(line)
addResult(line, portfolio.Value)
return
}
ax, err := getAxiomWallet(pk)
if err != nil {
line := fmt.Sprintf("Error: %v", err)
fmt.Println(line)
addResult(line, 0)
return
}
if ax.IsNewUser {
line := fmt.Sprintf("[axiom][jup.ag] %s | %s - NEW USER (skipped)", pk, ax.Sol)
fmt.Println(line)
addResult(line, 0)
return
}
if mode == ModeAxiom {
portfolio, err := getPortfolioValue(ax.Sol, cfg)
if err != nil {
line := fmt.Sprintf("Error: %v", err)
fmt.Println(line)
addResult(line, 0)
return
}
tokenStr := ""
if portfolio.Value > 0 {
if portfolio.TokenCount > 10 {
tokenStr = " (>10 tokens)"
} else {
tokenStr = fmt.Sprintf(" (%d tokens)", portfolio.TokenCount)
}
}
valueStr := "0.00"
if portfolio.Value > 0 {
valueStr = fmt.Sprintf("%.2f", portfolio.Value)
}
line := fmt.Sprintf("[axiom][jup.ag] %s | %s - $%s%s", pk, ax.Sol, valueStr, tokenStr)
fmt.Println(line)
addResult(line, portfolio.Value)
return
}
if mode == ModeBoth {
kp, err := keypairFromSecretKey(pk)
if err != nil {
line := fmt.Sprintf("Error: %v", err)
fmt.Println(line)
addResult(line, 0)
return
}
addr := kp.PublicKeyBase58()
var p1, p2 *PortfolioValue
var wg sync.WaitGroup
var err1, err2 error
wg.Add(2)
go func() {
defer wg.Done()
p1, err1 = getPortfolioValue(addr, cfg)
}()
go func() {
defer wg.Done()
p2, err2 = getPortfolioValue(ax.Sol, cfg)
}()
wg.Wait()
if err1 != nil || err2 != nil {
line := "Error fetching portfolios"
fmt.Println(line)
addResult(line, 0)
return
}
total := p1.Value + p2.Value
count := p1.TokenCount + p2.TokenCount
tokenStr := ""
if total > 0 {
if count > 10 {
tokenStr = " (>10 tokens)"
} else {
tokenStr = fmt.Sprintf(" (%d tokens)", count)
}
}
valueStr := "0.00"
if total > 0 {
valueStr = fmt.Sprintf("%.2f", total)
}
line := fmt.Sprintf("[pk+axiom][jup.ag] %s | %s | %s - $%s%s", pk, addr, ax.Sol, valueStr, tokenStr)
fmt.Println(line)
addResult(line, total)
}
}
func tern(cond bool, t, f string) string {
if cond {
return t
}
return f
}

160
menu.go Normal file
View file

@ -0,0 +1,160 @@
package main
import (
"fmt"
"strconv"
"strings"
"github.com/manifoldco/promptui"
)
func showMainMenu(cfg *Config) (string, error) {
hasSettings := len(cfg.JupiterApiKeys) > 0
items := []string{"Settings", "Start", "Exit"}
if !hasSettings {
items = []string{"Settings (configure API keys first)", "Exit"}
}
mainMenu := promptui.Select{
Label: "Main Menu",
Items: items,
Templates: &promptui.SelectTemplates{
Label: "{{ . }}",
Active: "▸ {{ . | cyan }}",
Inactive: " {{ . }}",
Selected: "{{ . | green }}",
},
}
idx, _, err := mainMenu.Run()
if err != nil {
return "", err
}
if !hasSettings {
if idx == 0 {
return "settings", nil
}
return "exit", nil
}
switch idx {
case 0:
return "settings", nil
case 1:
return "start", nil
case 2:
return "exit", nil
default:
return "exit", nil
}
}
func showSettingsMenu(cfg *Config) error {
for {
items := []string{
fmt.Sprintf("Jupiter API Keys (%d configured)", len(cfg.JupiterApiKeys)),
fmt.Sprintf("Threads: %d", cfg.Threads),
"",
fmt.Sprintf("Solana Check: %s", tern(cfg.EnableSolanaCheck, "✓ enabled", "✗ disabled")),
fmt.Sprintf("Axiom Module: %s", tern(cfg.EnableAxiomModule, "✓ enabled", "✗ disabled")),
fmt.Sprintf("EVM Module: %s", tern(cfg.EnableEVMModule, "✓ enabled", "✗ disabled")),
"",
"Back",
}
settingsMenu := promptui.Select{
Label: "Settings",
Items: items,
Templates: &promptui.SelectTemplates{
Label: "{{ . }}",
Active: "▸ {{ . | cyan }}",
Inactive: " {{ . }}",
Selected: "{{ . | green }}",
},
}
idx, _, err := settingsMenu.Run()
if err != nil {
return err
}
switch idx {
case 0:
if err := configureJupiterKeys(cfg); err != nil {
return err
}
case 1:
if err := configureThreads(cfg); err != nil {
return err
}
case 2:
continue
case 3:
cfg.EnableSolanaCheck = !cfg.EnableSolanaCheck
saveConfig(cfg)
case 4:
cfg.EnableAxiomModule = !cfg.EnableAxiomModule
saveConfig(cfg)
case 5:
cfg.EnableEVMModule = !cfg.EnableEVMModule
saveConfig(cfg)
case 6:
continue
case 7:
return nil
}
}
}
func configureJupiterKeys(cfg *Config) error {
prompt := promptui.Prompt{
Label: "Jupiter API Keys (comma-separated)",
Default: strings.Join(cfg.JupiterApiKeys, ","),
}
result, err := prompt.Run()
if err != nil {
return err
}
if result == "" {
cfg.JupiterApiKeys = []string{}
} else {
keys := strings.Split(result, ",")
for i := range keys {
keys[i] = strings.TrimSpace(keys[i])
}
cfg.JupiterApiKeys = keys
}
return saveConfig(cfg)
}
func configureThreads(cfg *Config) error {
prompt := promptui.Prompt{
Label: "Threads",
Default: strconv.Itoa(cfg.Threads),
Validate: func(s string) error {
_, err := strconv.Atoi(s)
return err
},
}
result, err := prompt.Run()
if err != nil {
return err
}
cfg.Threads, _ = strconv.Atoi(result)
return saveConfig(cfg)
}
func printConfigSummary(cfg *Config) {
colorPrint(ColorCyan, "Jupiter API keys: ")
fmt.Printf("%d\n", len(cfg.JupiterApiKeys))
colorPrint(ColorCyan, "Threads: ")
fmt.Printf("%d\n", cfg.Threads)
}

143
modules.go Normal file
View file

@ -0,0 +1,143 @@
package main
import (
"fmt"
"sync"
)
type Module interface {
Name() string
Check(pk string, cfg *Config) (*ModuleResult, error)
Enabled(cfg *Config) bool
}
type ModuleResult struct {
Address string
Balance float64
Details string
Tokens int
}
type ModuleManager struct {
modules []Module
cfg *Config
}
func NewModuleManager(cfg *Config) *ModuleManager {
mm := &ModuleManager{
cfg: cfg,
modules: []Module{},
}
if cfg.EnableAxiomModule {
mm.modules = append(mm.modules, &AxiomModule{})
}
if cfg.EnableEVMModule {
mm.modules = append(mm.modules, &EVMModule{})
}
return mm
}
func (mm *ModuleManager) GetEnabledModules() []Module {
return mm.modules
}
func (mm *ModuleManager) CheckAll(pk string) (float64, string) {
if len(mm.modules) == 0 {
return 0, ""
}
var wg sync.WaitGroup
resultsChan := make(chan *ModuleResult, len(mm.modules))
for _, module := range mm.modules {
wg.Add(1)
go func(m Module) {
defer wg.Done()
result, err := m.Check(pk, mm.cfg)
if err == nil && result != nil {
resultsChan <- result
}
}(module)
}
wg.Wait()
close(resultsChan)
var totalBalance float64
var details string
for result := range resultsChan {
totalBalance += result.Balance
details += result.Details
}
return totalBalance, details
}
type AxiomModule struct{}
func (m *AxiomModule) Name() string {
return "Axiom"
}
func (m *AxiomModule) Enabled(cfg *Config) bool {
return cfg.EnableAxiomModule
}
func (m *AxiomModule) Check(pk string, cfg *Config) (*ModuleResult, error) {
address, balance, err := getAxiomBalance(pk)
if err != nil {
return nil, nil
}
return &ModuleResult{
Address: address,
Balance: balance,
Details: fmt.Sprintf(" [Axiom] %s - $%.2f\n", address, balance),
}, nil
}
type EVMModule struct{}
func (m *EVMModule) Name() string {
return "EVM (DeBank)"
}
func (m *EVMModule) Enabled(cfg *Config) bool {
return cfg.EnableEVMModule
}
func (m *EVMModule) Check(pk string, cfg *Config) (*ModuleResult, error) {
ax, err := getAxiomWallet(pk)
if err != nil {
return nil, nil
}
if ax.Evm == "" {
return nil, nil
}
balance, details, err := getDeBankBalance(ax.Evm)
if err != nil {
return nil, nil
}
if balance == 0 {
return nil, nil
}
result := &ModuleResult{
Address: ax.Evm,
Balance: balance,
Details: fmt.Sprintf(" [EVM] %s - $%.2f\n", ax.Evm, balance),
}
if details != "" {
result.Details += details
}
return result, nil
}

231
portfolio.go Normal file
View file

@ -0,0 +1,231 @@
package main
import (
"encoding/json"
"fmt"
"io"
"net/http"
"strings"
"sync"
"time"
)
const SOL_MINT = "So11111111111111111111111111111111111111112"
type ApiKeyWorker struct {
apiKey string
lastRequestTime time.Time
mutex sync.Mutex
}
var (
apiKeyWorkers []*ApiKeyWorker
workerIndex int
workerMutex sync.Mutex
)
func initApiKeyWorkers(cfg *Config) {
apiKeyWorkers = make([]*ApiKeyWorker, len(cfg.JupiterApiKeys))
for i, key := range cfg.JupiterApiKeys {
apiKeyWorkers[i] = &ApiKeyWorker{
apiKey: key,
lastRequestTime: time.Time{},
}
}
workerIndex = 0
}
func getNextApiKeyWorker() *ApiKeyWorker {
workerMutex.Lock()
defer workerMutex.Unlock()
worker := apiKeyWorkers[workerIndex]
workerIndex = (workerIndex + 1) % len(apiKeyWorkers)
return worker
}
func (w *ApiKeyWorker) rateLimit(cfg *Config) {
w.mutex.Lock()
defer w.mutex.Unlock()
elapsed := time.Since(w.lastRequestTime)
waitTime := time.Duration(cfg.JupiterRateLimitMs)*time.Millisecond - elapsed
if waitTime > 0 {
time.Sleep(waitTime)
}
w.lastRequestTime = time.Now()
}
type PortfolioValue struct {
Value float64
TokenCount int
}
type Balance struct {
UiAmount float64 `json:"uiAmount"`
}
type PriceData struct {
UsdPrice float64 `json:"usdPrice"`
}
func getPortfolioValue(address string, cfg *Config) (*PortfolioValue, error) {
const MAX_RETRIES = 2
// Get a worker for this request
worker := getNextApiKeyWorker()
for attempt := 0; attempt < MAX_RETRIES; attempt++ {
result, err := attemptGetPortfolio(address, cfg, worker)
if err == nil {
return result, nil
}
if attempt == MAX_RETRIES-1 {
return &PortfolioValue{Value: 0, TokenCount: 0}, nil
}
}
return &PortfolioValue{Value: 0, TokenCount: 0}, nil
}
func attemptGetPortfolio(address string, cfg *Config, worker *ApiKeyWorker) (*PortfolioValue, error) {
const BATCH_SIZE = 100
const MAX_TOKENS = 100
worker.rateLimit(cfg)
transport, err := getNextProxy()
if err != nil {
return nil, fmt.Errorf("failed to get proxy: %w", err)
}
client := &http.Client{
Transport: transport,
Timeout: 10 * time.Second,
}
// Get balances
req, err := http.NewRequest("GET", fmt.Sprintf("https://api.jup.ag/ultra/v1/balances/%s", address), nil)
if err != nil {
return nil, err
}
req.Header.Set("x-api-key", worker.apiKey)
resp, err := client.Do(req)
if err != nil {
return nil, err
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
return nil, fmt.Errorf("balances request failed: %d", resp.StatusCode)
}
body, err := io.ReadAll(resp.Body)
if err != nil {
return nil, err
}
var balances map[string]Balance
if err := json.Unmarshal(body, &balances); err != nil {
return nil, err
}
if len(balances) == 0 {
return &PortfolioValue{Value: 0, TokenCount: 0}, nil
}
// Build token list
var tokens []string
for addr := range balances {
if addr != "SOL" {
tokens = append(tokens, addr)
}
}
// Add SOL mint if SOL balance exists
if _, hasSol := balances["SOL"]; hasSol {
tokens = append(tokens, SOL_MINT)
}
if len(tokens) > MAX_TOKENS {
tokens = tokens[:MAX_TOKENS]
}
if len(tokens) == 0 {
return &PortfolioValue{Value: 0, TokenCount: 0}, nil
}
// Split into batches
var batches [][]string
for i := 0; i < len(tokens); i += BATCH_SIZE {
end := i + BATCH_SIZE
if end > len(tokens) {
end = len(tokens)
}
batches = append(batches, tokens[i:end])
}
worker.rateLimit(cfg)
// Fetch prices for all batches
allPrices := make(map[string]PriceData)
for _, batch := range batches {
ids := strings.Join(batch, "%2C")
url := fmt.Sprintf("https://api.jup.ag/price/v3?ids=%s", ids)
req, err := http.NewRequest("GET", url, nil)
if err != nil {
continue
}
req.Header.Set("x-api-key", worker.apiKey)
resp, err := client.Do(req)
if err != nil {
continue
}
if resp.StatusCode == http.StatusOK {
body, err := io.ReadAll(resp.Body)
resp.Body.Close()
if err == nil {
var prices map[string]PriceData
if json.Unmarshal(body, &prices) == nil {
for k, v := range prices {
allPrices[k] = v
}
}
}
} else {
resp.Body.Close()
}
}
// Calculate total value
var total float64
var count int
for addr, bal := range balances {
mint := addr
if addr == "SOL" {
mint = SOL_MINT
}
price := 0.0
if priceData, ok := allPrices[mint]; ok {
price = priceData.UsdPrice
}
val := bal.UiAmount * price
if val > 0.01 {
total += val
count++
}
}
return &PortfolioValue{Value: total, TokenCount: count}, nil
}

41
solana.go Normal file
View file

@ -0,0 +1,41 @@
package main
import (
"crypto/ed25519"
"fmt"
"github.com/mr-tron/base58"
)
type Keypair struct {
PublicKey ed25519.PublicKey
PrivateKey ed25519.PrivateKey
}
func keypairFromSecretKey(privateKeyB58 string) (*Keypair, error) {
secretKey, err := base58.Decode(privateKeyB58)
if err != nil {
return nil, fmt.Errorf("failed to decode private key: %w", err)
}
if len(secretKey) != 64 {
return nil, fmt.Errorf("invalid secret key length: expected 64, got %d", len(secretKey))
}
privateKey := ed25519.PrivateKey(secretKey)
publicKey := privateKey.Public().(ed25519.PublicKey)
return &Keypair{
PublicKey: publicKey,
PrivateKey: privateKey,
}, nil
}
func (kp *Keypair) PublicKeyBase58() string {
return base58.Encode(kp.PublicKey)
}
func signMessage(privateKey ed25519.PrivateKey, message []byte) string {
signature := ed25519.Sign(privateKey, message)
return base58.Encode(signature)
}

118
utils.go Normal file
View file

@ -0,0 +1,118 @@
package main
import (
"bufio"
"fmt"
"net/http"
"net/url"
"os"
"path/filepath"
"strings"
"github.com/mr-tron/base58"
"golang.org/x/net/proxy"
)
func readLines(path string) []string {
file, err := os.Open(path)
if err != nil {
return []string{}
}
defer file.Close()
var lines []string
scanner := bufio.NewScanner(file)
for scanner.Scan() {
line := strings.TrimSpace(scanner.Text())
if line != "" && !strings.HasPrefix(line, "#") {
lines = append(lines, line)
}
}
return lines
}
func isValidKey(k string) bool {
decoded, err := base58.Decode(k)
if err != nil {
return false
}
return len(decoded) == 64
}
func loadPrivateKeys() []string {
keys := []string{}
// Load from private_keys.txt
for _, key := range readLines("private_keys.txt") {
if isValidKey(key) {
keys = append(keys, key)
}
}
// Load from keys/*.txt
keysDir := "keys"
if info, err := os.Stat(keysDir); err == nil && info.IsDir() {
files, _ := filepath.Glob(filepath.Join(keysDir, "*.txt"))
for _, file := range files {
for _, key := range readLines(file) {
if isValidKey(key) {
keys = append(keys, key)
}
}
}
}
return keys
}
func loadProxies() []string {
return readLines("proxies.txt")
}
var proxies []string
var proxyIndex int
func initProxies() {
proxies = loadProxies()
proxyIndex = 0
}
func getNextProxy() (*http.Transport, error) {
if len(proxies) == 0 {
return &http.Transport{}, nil
}
proxyURL := proxies[proxyIndex]
proxyIndex = (proxyIndex + 1) % len(proxies)
// Parse SOCKS5 proxy
u, err := url.Parse(proxyURL)
if err != nil {
return nil, fmt.Errorf("invalid proxy URL: %w", err)
}
if u.Scheme == "socks5" {
var auth *proxy.Auth
if u.User != nil {
password, _ := u.User.Password()
auth = &proxy.Auth{
User: u.User.Username(),
Password: password,
}
}
dialer, err := proxy.SOCKS5("tcp", u.Host, auth, proxy.Direct)
if err != nil {
return nil, fmt.Errorf("failed to create SOCKS5 dialer: %w", err)
}
return &http.Transport{
Dial: dialer.Dial,
}, nil
}
// For HTTP/HTTPS proxies
return &http.Transport{
Proxy: http.ProxyURL(u),
}, nil
}