|
@@ -5,14 +5,15 @@ import (
|
|
|
"context"
|
|
"context"
|
|
|
"crypto/ecdsa"
|
|
"crypto/ecdsa"
|
|
|
"fmt"
|
|
"fmt"
|
|
|
- "log"
|
|
|
|
|
"math/big"
|
|
"math/big"
|
|
|
"os"
|
|
"os"
|
|
|
- "strconv"
|
|
|
|
|
"strings"
|
|
"strings"
|
|
|
|
|
+ "sync"
|
|
|
|
|
|
|
|
"github.com/ethereum/go-ethereum/crypto"
|
|
"github.com/ethereum/go-ethereum/crypto"
|
|
|
"github.com/ethereum/go-ethereum/ethclient"
|
|
"github.com/ethereum/go-ethereum/ethclient"
|
|
|
|
|
+ "github.com/therecipe/qt/core"
|
|
|
|
|
+ "github.com/therecipe/qt/widgets"
|
|
|
)
|
|
)
|
|
|
|
|
|
|
|
const (
|
|
const (
|
|
@@ -20,122 +21,178 @@ const (
|
|
|
nodeURLTxtName = "nodeURL.txt"
|
|
nodeURLTxtName = "nodeURL.txt"
|
|
|
)
|
|
)
|
|
|
|
|
|
|
|
|
|
+type window struct {
|
|
|
|
|
+ *widgets.QMainWindow
|
|
|
|
|
+ chainCB *widgets.QComboBox
|
|
|
|
|
+ outputTE *widgets.QTextEdit
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+type queryResult struct {
|
|
|
|
|
+ index int
|
|
|
|
|
+ message string
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
func main() {
|
|
func main() {
|
|
|
- // 1. 读取 nodeURL.txt
|
|
|
|
|
|
|
+ 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)
|
|
nodeURLs, err := readLinesNoBlank(nodeURLTxtName)
|
|
|
if err != nil {
|
|
if err != nil {
|
|
|
- log.Fatalf("读取%s失败: %v", nodeURLTxtName, err)
|
|
|
|
|
|
|
+ popupFatal(fmt.Sprintf("读取 %s 失败: %v", nodeURLTxtName, err))
|
|
|
}
|
|
}
|
|
|
if len(nodeURLs) == 0 {
|
|
if len(nodeURLs) == 0 {
|
|
|
- log.Fatalf("%s 文件为空,请填入至少一个节点URL(每行一个)后重试。", nodeURLTxtName)
|
|
|
|
|
|
|
+ popupFatal(fmt.Sprintf("%s 为空,请至少填一个节点 URL", nodeURLTxtName))
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- // 2. 展示节点列表,提示选择
|
|
|
|
|
- fmt.Println("可用网络节点列表:")
|
|
|
|
|
- for i, url := range nodeURLs {
|
|
|
|
|
- fmt.Printf(" %d. %s\n", i+1, url)
|
|
|
|
|
|
|
+ // ---- 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))
|
|
|
}
|
|
}
|
|
|
- fmt.Printf("\n输入要使用的节点编号 (1-%d): ", len(nodeURLs))
|
|
|
|
|
-
|
|
|
|
|
- var urlIndex int
|
|
|
|
|
- reader := bufio.NewReader(os.Stdin)
|
|
|
|
|
- for {
|
|
|
|
|
- fmt.Printf("请输入要使用的节点编号 (1-%d): ", len(nodeURLs))
|
|
|
|
|
- line, err := reader.ReadString('\n')
|
|
|
|
|
- if err != nil {
|
|
|
|
|
- fmt.Println("读取输入失败,请重试。")
|
|
|
|
|
- continue
|
|
|
|
|
- }
|
|
|
|
|
- line = strings.TrimSpace(line)
|
|
|
|
|
- // 检查line是否只包含数字
|
|
|
|
|
- num, err := strconv.Atoi(line)
|
|
|
|
|
- if err != nil || num < 1 || num > len(nodeURLs) {
|
|
|
|
|
- fmt.Printf("输入无效,请输入 1 到 %d 之间的数字。\n", len(nodeURLs))
|
|
|
|
|
- continue
|
|
|
|
|
- }
|
|
|
|
|
- urlIndex = num - 1
|
|
|
|
|
- break
|
|
|
|
|
- }
|
|
|
|
|
- nodeURL := nodeURLs[urlIndex]
|
|
|
|
|
- fmt.Printf("\n当前使用节点: %s\n\n", nodeURL)
|
|
|
|
|
-
|
|
|
|
|
- // 检查keys.txt是否存在
|
|
|
|
|
- if _, err := os.Stat(keyFileName); os.IsNotExist(err) {
|
|
|
|
|
- file, err := os.Create(keyFileName)
|
|
|
|
|
- if err != nil {
|
|
|
|
|
- log.Fatalf("创建%s失败: %v", keyFileName, err)
|
|
|
|
|
- }
|
|
|
|
|
- defer file.Close()
|
|
|
|
|
- fmt.Printf("没有%s文件,已创建,请填入你的key,每行一个,然后重新运行本程序。\n", keyFileName)
|
|
|
|
|
|
|
+ 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
|
|
return
|
|
|
}
|
|
}
|
|
|
|
|
+ nodeURL := w.chainCB.CurrentText()
|
|
|
|
|
+ w.log("当前节点: %s", nodeURL)
|
|
|
|
|
|
|
|
- // 读取keys.txt
|
|
|
|
|
- myKeyArr, err := readLinesNoBlank(keyFileName)
|
|
|
|
|
|
|
+ // 读取 keys
|
|
|
|
|
+ keys, err := readLinesNoBlank(keyFileName)
|
|
|
if err != nil {
|
|
if err != nil {
|
|
|
- log.Fatalf("打开%s失败: %v", keyFileName, err)
|
|
|
|
|
|
|
+ w.log("读取 %s 失败: %v", keyFileName, err)
|
|
|
|
|
+ return
|
|
|
}
|
|
}
|
|
|
- if len(myKeyArr) == 0 {
|
|
|
|
|
- fmt.Printf("%s 为空,请至少填入一个私钥(每行一个),然后重新运行本程序。\n", keyFileName)
|
|
|
|
|
|
|
+ if len(keys) == 0 {
|
|
|
|
|
+ w.log("%s 为空,请先填入私钥(每行一个)", keyFileName)
|
|
|
return
|
|
return
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- // 连接以太坊节点
|
|
|
|
|
|
|
+ // 连接
|
|
|
client, err := ethclient.Dial(nodeURL)
|
|
client, err := ethclient.Dial(nodeURL)
|
|
|
if err != nil {
|
|
if err != nil {
|
|
|
- log.Fatalf("连接节点失败: %v", err)
|
|
|
|
|
|
|
+ w.log("连接节点失败: %v", err)
|
|
|
|
|
+ return
|
|
|
}
|
|
}
|
|
|
|
|
+ defer client.Close()
|
|
|
|
|
|
|
|
- // 查询每个key对应地址的余额
|
|
|
|
|
- for i, key := range myKeyArr {
|
|
|
|
|
- privateKey, err := crypto.HexToECDSA(key)
|
|
|
|
|
- if err != nil {
|
|
|
|
|
- fmt.Printf("钱包 %d: 私钥解析失败,跳过。错误:%v\n\n", i+1, err)
|
|
|
|
|
- continue // 跳过无效私钥
|
|
|
|
|
- }
|
|
|
|
|
- publicKey := privateKey.Public()
|
|
|
|
|
- publicKeyECDSA, ok := publicKey.(*ecdsa.PublicKey)
|
|
|
|
|
- if !ok {
|
|
|
|
|
- fmt.Printf("钱包 %d: 公钥转化失败,跳过。\n\n", i+1)
|
|
|
|
|
- continue
|
|
|
|
|
- }
|
|
|
|
|
- address := crypto.PubkeyToAddress(*publicKeyECDSA)
|
|
|
|
|
- fmt.Printf("钱包 %d , 钱包地址: %s\n", i+1, address.Hex())
|
|
|
|
|
|
|
+ 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()
|
|
|
|
|
|
|
|
- balance, err := client.BalanceAt(context.Background(), address, nil)
|
|
|
|
|
- if err != nil {
|
|
|
|
|
- fmt.Printf("钱包 %d: 查询余额失败,跳过。错误:%v\n\n", i+1, err)
|
|
|
|
|
- continue
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ 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)
|
|
|
|
|
|
|
|
- nonce, err := client.PendingNonceAt(context.Background(), address)
|
|
|
|
|
- if err != nil {
|
|
|
|
|
- fmt.Printf("钱包 %d: 查询Nonce失败,跳过。错误:%v\n\n", i+1, err)
|
|
|
|
|
- continue
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ 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()
|
|
|
|
|
|
|
|
- fmt.Printf("余额(wei): %s\n", balance.String())
|
|
|
|
|
- etherValue := new(big.Float).Quo(new(big.Float).SetInt(balance), big.NewFloat(1e18))
|
|
|
|
|
- fmt.Printf("余额(Token): %f\n", etherValue)
|
|
|
|
|
- fmt.Printf("Nonce: %d\n\n", nonce)
|
|
|
|
|
|
|
+ // 全部结束后按顺序输出
|
|
|
|
|
+ for _, res := range results {
|
|
|
|
|
+ w.log(res)
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-// 读取文件每行,过滤空行和首尾空白
|
|
|
|
|
|
|
+// ============ 工具函数 ============
|
|
|
func readLinesNoBlank(filename string) ([]string, error) {
|
|
func readLinesNoBlank(filename string) ([]string, error) {
|
|
|
- file, err := os.Open(filename)
|
|
|
|
|
|
|
+ f, err := os.Open(filename)
|
|
|
if err != nil {
|
|
if err != nil {
|
|
|
return nil, err
|
|
return nil, err
|
|
|
}
|
|
}
|
|
|
- defer file.Close()
|
|
|
|
|
|
|
+ defer f.Close()
|
|
|
|
|
|
|
|
- var lines []string
|
|
|
|
|
- scanner := bufio.NewScanner(file)
|
|
|
|
|
- for scanner.Scan() {
|
|
|
|
|
- line := strings.TrimSpace(scanner.Text())
|
|
|
|
|
|
|
+ var out []string
|
|
|
|
|
+ sc := bufio.NewScanner(f)
|
|
|
|
|
+ for sc.Scan() {
|
|
|
|
|
+ line := strings.TrimSpace(sc.Text())
|
|
|
if line != "" {
|
|
if line != "" {
|
|
|
- lines = append(lines, line)
|
|
|
|
|
|
|
+ out = append(out, line)
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
- return lines, scanner.Err()
|
|
|
|
|
|
|
+ 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)
|
|
|
}
|
|
}
|