main.go 4.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209
  1. package main
  2. import (
  3. "bufio"
  4. "context"
  5. "crypto/ecdsa"
  6. "fmt"
  7. "math/big"
  8. "os"
  9. "strings"
  10. "sync"
  11. "fyne.io/fyne/v2"
  12. "fyne.io/fyne/v2/app"
  13. "fyne.io/fyne/v2/container"
  14. "fyne.io/fyne/v2/dialog"
  15. "fyne.io/fyne/v2/layout"
  16. "fyne.io/fyne/v2/widget"
  17. "github.com/ethereum/go-ethereum/crypto"
  18. "github.com/ethereum/go-ethereum/ethclient"
  19. )
  20. const (
  21. keyFileName = "keys.txt"
  22. nodeURLTxtName = "nodeURL.txt"
  23. )
  24. type window struct {
  25. app fyne.App
  26. mainWindow fyne.Window
  27. chainSelect *widget.Select
  28. outputTE *widget.Entry
  29. }
  30. type queryResult struct {
  31. index int
  32. message string
  33. }
  34. func main() {
  35. myApp := app.New()
  36. win := &window{
  37. app: myApp,
  38. mainWindow: myApp.NewWindow("多链余额查询器"),
  39. }
  40. win.mainWindow.Resize(fyne.NewSize(1050, 750))
  41. // 读取节点列表
  42. nodeURLs, err := readLinesNoBlank(nodeURLTxtName)
  43. if err != nil {
  44. popupFatal(win.mainWindow, fmt.Sprintf("读取 %s 失败: %v", nodeURLTxtName, err))
  45. return
  46. }
  47. if len(nodeURLs) == 0 {
  48. popupFatal(win.mainWindow, fmt.Sprintf("%s 为空,请至少填一个节点 URL", nodeURLTxtName))
  49. return
  50. }
  51. // 控件创建
  52. nodeLabel := widget.NewLabel("选择节点:")
  53. win.chainSelect = widget.NewSelect(nodeURLs, nil)
  54. if len(nodeURLs) > 0 {
  55. win.chainSelect.SetSelectedIndex(0)
  56. }
  57. queryBtn := widget.NewButton("查询余额", win.query)
  58. clearBtn := widget.NewButton("清除输出", func() {
  59. win.outputTE.SetText("")
  60. })
  61. btnContainer := container.NewHBox(
  62. layout.NewSpacer(),
  63. queryBtn,
  64. clearBtn,
  65. layout.NewSpacer(),
  66. )
  67. win.outputTE = widget.NewMultiLineEntry()
  68. win.outputTE.SetMinRowsVisible(25) // 增加行数
  69. win.outputTE.Disable()
  70. // 使用Border布局让输出框占据主要空间
  71. topSection := container.NewVBox(
  72. nodeLabel,
  73. win.chainSelect,
  74. btnContainer,
  75. widget.NewSeparator(),
  76. widget.NewLabel("输出:"),
  77. )
  78. outputScroll := container.NewScroll(win.outputTE)
  79. content := container.NewBorder(
  80. topSection,
  81. nil, nil, nil,
  82. outputScroll,
  83. )
  84. win.mainWindow.SetContent(content)
  85. win.mainWindow.ShowAndRun()
  86. }
  87. // ============ 查询逻辑 ============
  88. func (w *window) query() {
  89. w.outputTE.SetText("")
  90. // 当前选中节点
  91. if w.chainSelect.SelectedIndex() < 0 {
  92. w.log("未选择节点")
  93. return
  94. }
  95. nodeURL := w.chainSelect.Selected
  96. w.log("当前节点: %s", nodeURL)
  97. // 读取 keys
  98. keys, err := readLinesNoBlank(keyFileName)
  99. if err != nil {
  100. w.log("读取 %s 失败: %v", keyFileName, err)
  101. return
  102. }
  103. if len(keys) == 0 {
  104. w.log("%s 为空,请先填入私钥(每行一个)", keyFileName)
  105. return
  106. }
  107. // 连接
  108. client, err := ethclient.Dial(nodeURL)
  109. if err != nil {
  110. w.log("连接节点失败: %v", err)
  111. return
  112. }
  113. defer client.Close()
  114. var wg sync.WaitGroup
  115. results := make([]string, len(keys))
  116. for i, hexKey := range keys {
  117. wg.Add(1)
  118. go func(i int, hexKey string) {
  119. defer wg.Done()
  120. privateKey, err := crypto.HexToECDSA(hexKey)
  121. if err != nil {
  122. results[i] = fmt.Sprintf("钱包 %d: 私钥解析失败,跳过。err=%v", i+1, err)
  123. return
  124. }
  125. publicKey, ok := privateKey.Public().(*ecdsa.PublicKey)
  126. if !ok {
  127. results[i] = fmt.Sprintf("钱包 %d: 公钥转换失败,跳过", i+1)
  128. return
  129. }
  130. addr := crypto.PubkeyToAddress(*publicKey)
  131. balance, err := client.BalanceAt(context.Background(), addr, nil)
  132. if err != nil {
  133. results[i] = fmt.Sprintf("钱包 %d (%s): 查余额失败,err=%v", i+1, addr.Hex(), err)
  134. return
  135. }
  136. nonce, err := client.PendingNonceAt(context.Background(), addr)
  137. if err != nil {
  138. results[i] = fmt.Sprintf("钱包 %d (%s): 查 nonce 失败,err=%v", i+1, addr.Hex(), err)
  139. return
  140. }
  141. ether := new(big.Float).Quo(new(big.Float).SetInt(balance), big.NewFloat(1e18))
  142. results[i] = fmt.Sprintf("钱包 %d %s \n余额(wei): %s\n余额Token %.6f\nNonce: %d",
  143. i+1, addr.Hex(), balance.String(), ether, nonce)
  144. }(i, hexKey)
  145. }
  146. wg.Wait()
  147. // 全部结束后按顺序输出
  148. for _, res := range results {
  149. w.log(res)
  150. }
  151. }
  152. // ============ 工具函数 ============
  153. func readLinesNoBlank(filename string) ([]string, error) {
  154. f, err := os.Open(filename)
  155. if err != nil {
  156. return nil, err
  157. }
  158. defer f.Close()
  159. var out []string
  160. sc := bufio.NewScanner(f)
  161. for sc.Scan() {
  162. line := strings.TrimSpace(sc.Text())
  163. if line != "" {
  164. out = append(out, line)
  165. }
  166. }
  167. return out, sc.Err()
  168. }
  169. func (w *window) log(format string, a ...interface{}) {
  170. s := fmt.Sprintf(format, a...)
  171. currentText := w.outputTE.Text
  172. if currentText != "" {
  173. currentText += "\n"
  174. }
  175. w.outputTE.SetText(currentText + s + "\n")
  176. }
  177. func popupFatal(win fyne.Window, msg string) {
  178. dialog.ShowError(fmt.Errorf(msg), win)
  179. }