package main import ( "bufio" "context" "crypto/ecdsa" "fmt" "math/big" "os" "strings" "sync" "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/ethclient" "github.com/therecipe/qt/core" "github.com/therecipe/qt/widgets" ) const ( keyFileName = "keys.txt" nodeURLTxtName = "nodeURL.txt" ) type window struct { *widgets.QMainWindow chainCB *widgets.QComboBox outputTE *widgets.QTextEdit } type queryResult struct { index int message string } func main() { app := widgets.NewQApplication(len(os.Args), os.Args) // 1.5 倍全局放大 app.SetStyleSheet(` * { font-size: 15pt; padding: 6px; } QPushButton { min-height: 34px; } `) win := &window{QMainWindow: widgets.NewQMainWindow(nil, 0)} win.SetWindowTitle("区块链多链余额查询器") win.SetMinimumSize2(1050, 750) // 700*1.5 , 500*1.5 central := widgets.NewQWidget(nil, 0) layout := widgets.NewQVBoxLayout2(central) // ---- 1. 读取节点列表 ---- nodeURLs, err := readLinesNoBlank(nodeURLTxtName) if err != nil { popupFatal(fmt.Sprintf("读取 %s 失败: %v", nodeURLTxtName, err)) } if len(nodeURLs) == 0 { popupFatal(fmt.Sprintf("%s 为空,请至少填一个节点 URL", nodeURLTxtName)) } // ---- 2. 下拉框 ---- layout.AddWidget(widgets.NewQLabel2("选择节点:", nil, 0), 0, 0) win.chainCB = widgets.NewQComboBox(nil) for _, u := range nodeURLs { win.chainCB.AddItem(u, core.NewQVariant1(u)) } layout.AddWidget(win.chainCB, 0, 0) // ---- 3. 按钮区域 ---- btnLayout := widgets.NewQHBoxLayout() queryBtn := widgets.NewQPushButton2("查询余额", nil) queryBtn.ConnectClicked(func(bool) { win.query() }) btnLayout.AddWidget(queryBtn, 0, 0) clearBtn := widgets.NewQPushButton2("清除输出", nil) clearBtn.ConnectClicked(func(bool) { win.outputTE.Clear() }) btnLayout.AddWidget(clearBtn, 0, 0) layout.AddLayout(btnLayout, 0) // ---- 4. 输出框 ---- win.outputTE = widgets.NewQTextEdit(nil) win.outputTE.SetReadOnly(true) layout.AddWidget(win.outputTE, 0, 0) win.SetCentralWidget(central) win.Show() widgets.QApplication_Exec() } // ============ 查询逻辑 ============ func (w *window) query() { w.outputTE.Clear() // 当前选中节点 idx := w.chainCB.CurrentIndex() if idx < 0 { w.log("未选择节点") return } nodeURL := w.chainCB.CurrentText() w.log("当前节点: %s", nodeURL) // 读取 keys keys, err := readLinesNoBlank(keyFileName) if err != nil { w.log("读取 %s 失败: %v", keyFileName, err) return } if len(keys) == 0 { w.log("%s 为空,请先填入私钥(每行一个)", keyFileName) return } // 连接 client, err := ethclient.Dial(nodeURL) if err != nil { w.log("连接节点失败: %v", err) return } defer client.Close() var wg sync.WaitGroup results := make([]string, len(keys)) for i, hexKey := range keys { wg.Add(1) go func(i int, hexKey string) { defer wg.Done() privateKey, err := crypto.HexToECDSA(hexKey) if err != nil { results[i] = fmt.Sprintf("钱包 %d: 私钥解析失败,跳过。err=%v", i+1, err) return } publicKey, ok := privateKey.Public().(*ecdsa.PublicKey) if !ok { results[i] = fmt.Sprintf("钱包 %d: 公钥转换失败,跳过", i+1) return } addr := crypto.PubkeyToAddress(*publicKey) balance, err := client.BalanceAt(context.Background(), addr, nil) if err != nil { results[i] = fmt.Sprintf("钱包 %d (%s): 查余额失败,err=%v", i+1, addr.Hex(), err) return } nonce, err := client.PendingNonceAt(context.Background(), addr) if err != nil { results[i] = fmt.Sprintf("钱包 %d (%s): 查 nonce 失败,err=%v", i+1, addr.Hex(), err) return } ether := new(big.Float).Quo(new(big.Float).SetInt(balance), big.NewFloat(1e18)) results[i] = fmt.Sprintf("钱包 %d %s \n余额(wei): %s\n余额Token %.6f\nNonce: %d", i+1, addr.Hex(), balance.String(), ether, nonce) }(i, hexKey) } wg.Wait() // 全部结束后按顺序输出 for _, res := range results { w.log(res) } } // ============ 工具函数 ============ func readLinesNoBlank(filename string) ([]string, error) { f, err := os.Open(filename) if err != nil { return nil, err } defer f.Close() var out []string sc := bufio.NewScanner(f) for sc.Scan() { line := strings.TrimSpace(sc.Text()) if line != "" { out = append(out, line) } } return out, sc.Err() } func (w *window) log(format string, a ...interface{}) { s := fmt.Sprintf(format, a...) w.outputTE.Append(s + "\n") } func popupFatal(msg string) { widgets.QMessageBox_Critical(nil, "错误", msg, widgets.QMessageBox__Ok, widgets.QMessageBox__Ok) os.Exit(1) }