main.go 4.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198
  1. package main
  2. import (
  3. "bufio"
  4. "context"
  5. "crypto/ecdsa"
  6. "fmt"
  7. "math/big"
  8. "os"
  9. "strings"
  10. "sync"
  11. "github.com/ethereum/go-ethereum/crypto"
  12. "github.com/ethereum/go-ethereum/ethclient"
  13. "github.com/therecipe/qt/core"
  14. "github.com/therecipe/qt/widgets"
  15. )
  16. const (
  17. keyFileName = "keys.txt"
  18. nodeURLTxtName = "nodeURL.txt"
  19. )
  20. type window struct {
  21. *widgets.QMainWindow
  22. chainCB *widgets.QComboBox
  23. outputTE *widgets.QTextEdit
  24. }
  25. type queryResult struct {
  26. index int
  27. message string
  28. }
  29. func main() {
  30. app := widgets.NewQApplication(len(os.Args), os.Args)
  31. // 1.5 倍全局放大
  32. app.SetStyleSheet(`
  33. * {
  34. font-size: 15pt;
  35. padding: 6px;
  36. }
  37. QPushButton {
  38. min-height: 34px;
  39. }
  40. `)
  41. win := &window{QMainWindow: widgets.NewQMainWindow(nil, 0)}
  42. win.SetWindowTitle("多链余额查询器")
  43. win.SetMinimumSize2(1050, 750) // 700*1.5 , 500*1.5
  44. central := widgets.NewQWidget(nil, 0)
  45. layout := widgets.NewQVBoxLayout2(central)
  46. // ---- 1. 读取节点列表 ----
  47. nodeURLs, err := readLinesNoBlank(nodeURLTxtName)
  48. if err != nil {
  49. popupFatal(fmt.Sprintf("读取 %s 失败: %v", nodeURLTxtName, err))
  50. }
  51. if len(nodeURLs) == 0 {
  52. popupFatal(fmt.Sprintf("%s 为空,请至少填一个节点 URL", nodeURLTxtName))
  53. }
  54. // ---- 2. 下拉框 ----
  55. layout.AddWidget(widgets.NewQLabel2("选择节点:", nil, 0), 0, 0)
  56. win.chainCB = widgets.NewQComboBox(nil)
  57. for _, u := range nodeURLs {
  58. win.chainCB.AddItem(u, core.NewQVariant1(u))
  59. }
  60. layout.AddWidget(win.chainCB, 0, 0)
  61. // ---- 3. 按钮区域 ----
  62. btnLayout := widgets.NewQHBoxLayout()
  63. queryBtn := widgets.NewQPushButton2("查询余额", nil)
  64. queryBtn.ConnectClicked(func(bool) { win.query() })
  65. btnLayout.AddWidget(queryBtn, 0, 0)
  66. clearBtn := widgets.NewQPushButton2("清除输出", nil)
  67. clearBtn.ConnectClicked(func(bool) { win.outputTE.Clear() })
  68. btnLayout.AddWidget(clearBtn, 0, 0)
  69. layout.AddLayout(btnLayout, 0)
  70. // ---- 4. 输出框 ----
  71. win.outputTE = widgets.NewQTextEdit(nil)
  72. win.outputTE.SetReadOnly(true)
  73. layout.AddWidget(win.outputTE, 0, 0)
  74. win.SetCentralWidget(central)
  75. win.Show()
  76. widgets.QApplication_Exec()
  77. }
  78. // ============ 查询逻辑 ============
  79. func (w *window) query() {
  80. w.outputTE.Clear()
  81. // 当前选中节点
  82. idx := w.chainCB.CurrentIndex()
  83. if idx < 0 {
  84. w.log("未选择节点")
  85. return
  86. }
  87. nodeURL := w.chainCB.CurrentText()
  88. w.log("当前节点: %s", nodeURL)
  89. // 读取 keys
  90. keys, err := readLinesNoBlank(keyFileName)
  91. if err != nil {
  92. w.log("读取 %s 失败: %v", keyFileName, err)
  93. return
  94. }
  95. if len(keys) == 0 {
  96. w.log("%s 为空,请先填入私钥(每行一个)", keyFileName)
  97. return
  98. }
  99. // 连接
  100. client, err := ethclient.Dial(nodeURL)
  101. if err != nil {
  102. w.log("连接节点失败: %v", err)
  103. return
  104. }
  105. defer client.Close()
  106. var wg sync.WaitGroup
  107. results := make([]string, len(keys))
  108. for i, hexKey := range keys {
  109. wg.Add(1)
  110. go func(i int, hexKey string) {
  111. defer wg.Done()
  112. privateKey, err := crypto.HexToECDSA(hexKey)
  113. if err != nil {
  114. results[i] = fmt.Sprintf("钱包 %d: 私钥解析失败,跳过。err=%v", i+1, err)
  115. return
  116. }
  117. publicKey, ok := privateKey.Public().(*ecdsa.PublicKey)
  118. if !ok {
  119. results[i] = fmt.Sprintf("钱包 %d: 公钥转换失败,跳过", i+1)
  120. return
  121. }
  122. addr := crypto.PubkeyToAddress(*publicKey)
  123. balance, err := client.BalanceAt(context.Background(), addr, nil)
  124. if err != nil {
  125. results[i] = fmt.Sprintf("钱包 %d (%s): 查余额失败,err=%v", i+1, addr.Hex(), err)
  126. return
  127. }
  128. nonce, err := client.PendingNonceAt(context.Background(), addr)
  129. if err != nil {
  130. results[i] = fmt.Sprintf("钱包 %d (%s): 查 nonce 失败,err=%v", i+1, addr.Hex(), err)
  131. return
  132. }
  133. ether := new(big.Float).Quo(new(big.Float).SetInt(balance), big.NewFloat(1e18))
  134. results[i] = fmt.Sprintf("钱包 %d %s \n余额(wei): %s\n余额Token %.6f\nNonce: %d",
  135. i+1, addr.Hex(), balance.String(), ether, nonce)
  136. }(i, hexKey)
  137. }
  138. wg.Wait()
  139. // 全部结束后按顺序输出
  140. for _, res := range results {
  141. w.log(res)
  142. }
  143. }
  144. // ============ 工具函数 ============
  145. func readLinesNoBlank(filename string) ([]string, error) {
  146. f, err := os.Open(filename)
  147. if err != nil {
  148. return nil, err
  149. }
  150. defer f.Close()
  151. var out []string
  152. sc := bufio.NewScanner(f)
  153. for sc.Scan() {
  154. line := strings.TrimSpace(sc.Text())
  155. if line != "" {
  156. out = append(out, line)
  157. }
  158. }
  159. return out, sc.Err()
  160. }
  161. func (w *window) log(format string, a ...interface{}) {
  162. s := fmt.Sprintf(format, a...)
  163. w.outputTE.Append(s + "\n")
  164. }
  165. func popupFatal(msg string) {
  166. widgets.QMessageBox_Critical(nil, "错误", msg, widgets.QMessageBox__Ok, widgets.QMessageBox__Ok)
  167. os.Exit(1)
  168. }