jack 3 月之前
父节点
当前提交
64c0022bec
共有 4 个文件被更改,包括 171 次插入87 次删除
  1. 二进制
      project/CheckBalance/checkBalance
  2. 5 1
      project/CheckBalance/go.mod
  3. 23 0
      project/CheckBalance/go.sum
  4. 143 86
      project/CheckBalance/main.go

二进制
project/CheckBalance/checkBalance


+ 5 - 1
project/CheckBalance/go.mod

@@ -4,7 +4,10 @@ go 1.23.0
 
 toolchain go1.24.6
 
-require github.com/ethereum/go-ethereum v1.16.3
+require (
+	github.com/ethereum/go-ethereum v1.16.3
+	github.com/therecipe/qt v0.0.0-20200904063919-c0c124a5770d
+)
 
 require (
 	github.com/Microsoft/go-winio v0.6.2 // indirect
@@ -18,6 +21,7 @@ require (
 	github.com/ethereum/c-kzg-4844/v2 v2.1.0 // indirect
 	github.com/ethereum/go-verkle v0.2.2 // indirect
 	github.com/go-ole/go-ole v1.3.0 // indirect
+	github.com/gopherjs/gopherjs v0.0.0-20190411002643-bd77b112433e // indirect
 	github.com/gorilla/websocket v1.4.2 // indirect
 	github.com/holiman/uint256 v1.3.2 // indirect
 	github.com/shirou/gopsutil v3.21.4-0.20210419000835-c7a38de76ee5+incompatible // indirect

+ 23 - 0
project/CheckBalance/go.sum

@@ -32,6 +32,7 @@ github.com/crate-crypto/go-eth-kzg v1.3.0 h1:05GrhASN9kDAidaFJOda6A4BEvgvuXbazXg
 github.com/crate-crypto/go-eth-kzg v1.3.0/go.mod h1:J9/u5sWfznSObptgfa92Jq8rTswn6ahQWEuiLHOjCUI=
 github.com/crate-crypto/go-ipa v0.0.0-20240724233137-53bbb0ceb27a h1:W8mUrRp6NOVl3J+MYp5kPMoUZPp7aOYHtaua31lwRHg=
 github.com/crate-crypto/go-ipa v0.0.0-20240724233137-53bbb0ceb27a/go.mod h1:sTwzHBvIzm2RfVCGNEBZgRyjwK40bVoun3ZnGOCafNM=
+github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
 github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
 github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
 github.com/dchest/siphash v1.2.3 h1:QXwFc8cFOR2dSa/gE6o/HokBMWtLUaNDVd+22aKHeEA=
@@ -67,6 +68,8 @@ github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek
 github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
 github.com/golang/snappy v0.0.5-0.20220116011046-fa5810519dcb h1:PBC98N2aIaM3XXiurYmW7fx4GZkL8feAMVq7nEjURHk=
 github.com/golang/snappy v0.0.5-0.20220116011046-fa5810519dcb/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
+github.com/gopherjs/gopherjs v0.0.0-20190411002643-bd77b112433e h1:XWcjeEtTFTOVA9Fs1w7n2XBftk5ib4oZrhzWk0B+3eA=
+github.com/gopherjs/gopherjs v0.0.0-20190411002643-bd77b112433e/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
 github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc=
 github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
 github.com/hashicorp/go-bexpr v0.1.10 h1:9kuI5PFotCboP3dkDYFr/wi0gg0QVbSNz5oFRpxn4uE=
@@ -85,6 +88,8 @@ github.com/klauspost/compress v1.16.0 h1:iULayQNOReoYUe+1qtKOqw9CwJv3aNQu8ivo7lw
 github.com/klauspost/compress v1.16.0/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE=
 github.com/klauspost/cpuid/v2 v2.0.9 h1:lgaqFMSdTdQYdZ04uHyN2d/eKdOMyi2YLSvlQIBFYa4=
 github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
+github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
+github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
 github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
 github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
 github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
@@ -141,12 +146,20 @@ github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf
 github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
 github.com/shirou/gopsutil v3.21.4-0.20210419000835-c7a38de76ee5+incompatible h1:Bn1aCHHRnjv4Bl16T8rcaFjYSrGrIZvpiGO6P3Q4GpU=
 github.com/shirou/gopsutil v3.21.4-0.20210419000835-c7a38de76ee5+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA=
+github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q=
+github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
+github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
+github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE=
+github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
+github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
 github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
 github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
 github.com/supranational/blst v0.3.14 h1:xNMoHRJOTwMn63ip6qoWJ2Ymgvj7E2b9jY2FAwY+qRo=
 github.com/supranational/blst v0.3.14/go.mod h1:jZJtfjgudtNl4en1tzwPIV3KjUnQUvG3/j+w+fVonLw=
 github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7 h1:epCh84lMvA70Z7CTTCmYQn2CKbY8j86K7/FAIr141uY=
 github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7/go.mod h1:q4W45IWZaF22tdD+VEXcAWRA037jwmWEB5VWYORlTpc=
+github.com/therecipe/qt v0.0.0-20200904063919-c0c124a5770d h1:T+d8FnaLSvM/1BdlDXhW4d5dr2F07bAbB+LpgzMxx+o=
+github.com/therecipe/qt v0.0.0-20200904063919-c0c124a5770d/go.mod h1:SUUR2j3aE1z6/g76SdD6NwACEpvCxb3fvG82eKbD6us=
 github.com/tklauser/go-sysconf v0.3.12 h1:0QaGUFOdQaIVdPgfITYzaTegZvdCjmYO52cSFAEVmqU=
 github.com/tklauser/go-sysconf v0.3.12/go.mod h1:Ho14jnntGE1fpdOqQEEaiKRpvIavV0hSfmBq8nJbHYI=
 github.com/tklauser/numcpus v0.6.1 h1:ng9scYS7az0Bk4OZLvrNXNSAO2Pxr1XXRAPyjhIx+Fk=
@@ -155,22 +168,32 @@ github.com/urfave/cli/v2 v2.27.5 h1:WoHEJLdsXr6dDWoJgMq/CboDmyY/8HMMH1fTECbih+w=
 github.com/urfave/cli/v2 v2.27.5/go.mod h1:3Sevf16NykTbInEnD0yKkjDAeZDS0A6bzhBH5hrMvTQ=
 github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1 h1:gEOO8jv9F4OT7lGCjxCBTO/36wtF6j2nSip77qHd4x4=
 github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1/go.mod h1:Ohn+xnUBiLI6FVj/9LpzZWtj1/D6lUovWYBkxHVV3aM=
+golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
+golang.org/x/crypto v0.0.0-20190418165655-df01cb2cc480/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE=
 golang.org/x/crypto v0.36.0 h1:AnAEvhDddvBdpY+uR+MyHmuZzzNqXSe/GvuDeob5L34=
 golang.org/x/crypto v0.36.0/go.mod h1:Y4J0ReaxCR1IMaabaSMugxJES1EpwhBHhv2bDHklZvc=
 golang.org/x/exp v0.0.0-20230626212559-97b1e661b5df h1:UA2aFVmmsIlefxMk29Dp2juaUSth8Pyn3Tq5Y5mJGME=
 golang.org/x/exp v0.0.0-20230626212559-97b1e661b5df/go.mod h1:FXUEEKJgO7OQYeo8N01OfiKP8RXMtf6e8aTskBGqWdc=
+golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
+golang.org/x/net v0.0.0-20190420063019-afa5a82059c6/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
 golang.org/x/sync v0.12.0 h1:MHc5BpPuC30uJk597Ri8TV3CNZcTLu6B6z4lJy+g6Jw=
 golang.org/x/sync v0.12.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
+golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20190419153524-e8e3143a4f4a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik=
 golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
+golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
 golang.org/x/text v0.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY=
 golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4=
 golang.org/x/time v0.9.0 h1:EsRrnYcQiGH+5FfbgvV4AP7qEZstoyrHB0DzarOQ4ZY=
 golang.org/x/time v0.9.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
+golang.org/x/tools v0.0.0-20190420181800-aa740d480789/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
 google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg=
 google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw=
 gopkg.in/natefinch/lumberjack.v2 v2.2.1 h1:bBRl1b0OH9s/DuPhuXpNl+VtCaJXFZ5/uEFST95x9zc=

+ 143 - 86
project/CheckBalance/main.go

@@ -5,14 +5,15 @@ import (
 	"context"
 	"crypto/ecdsa"
 	"fmt"
-	"log"
 	"math/big"
 	"os"
-	"strconv"
 	"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 (
@@ -20,122 +21,178 @@ const (
 	nodeURLTxtName = "nodeURL.txt"
 )
 
+type window struct {
+	*widgets.QMainWindow
+	chainCB  *widgets.QComboBox
+	outputTE *widgets.QTextEdit
+}
+
+type queryResult struct {
+	index   int
+	message string
+}
+
 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)
 	if err != nil {
-		log.Fatalf("读取%s失败: %v", nodeURLTxtName, err)
+		popupFatal(fmt.Sprintf("读取 %s 失败: %v", nodeURLTxtName, err))
 	}
 	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
 	}
+	nodeURL := w.chainCB.CurrentText()
+	w.log("当前节点: %s", nodeURL)
 
-	// 读取keys.txt
-	myKeyArr, err := readLinesNoBlank(keyFileName)
+	// 读取 keys
+	keys, err := readLinesNoBlank(keyFileName)
 	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
 	}
 
-	// 连接以太坊节点
+	// 连接
 	client, err := ethclient.Dial(nodeURL)
 	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) {
-	file, err := os.Open(filename)
+	f, err := os.Open(filename)
 	if err != nil {
 		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 != "" {
-			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)
 }