├── .env.example ├── go.mod ├── .gitignore ├── README.md ├── go.sum └── cmd └── monitor.go /.env.example: -------------------------------------------------------------------------------- 1 | GRPC_URL=solana-yellowstone-grpc.publicnode.com:443 2 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module grpc-demo-golang 2 | 3 | go 1.23.3 4 | 5 | require ( 6 | github.com/joho/godotenv v1.5.1 // indirect 7 | github.com/mr-tron/base58 v1.2.0 // indirect 8 | github.com/rpcpool/yellowstone-grpc/examples/golang v0.0.0-20250107190324-f113ddc9f521 // indirect 9 | golang.org/x/net v0.30.0 // indirect 10 | golang.org/x/sys v0.26.0 // indirect 11 | golang.org/x/text v0.19.0 // indirect 12 | google.golang.org/genproto/googleapis/rpc v0.0.0-20241015192408-796eee8c2d53 // indirect 13 | google.golang.org/grpc v1.69.2 // indirect 14 | google.golang.org/protobuf v1.35.1 // indirect 15 | ) 16 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # If you prefer the allow list template instead of the deny list, see community template: 2 | # https://github.com/github/gitignore/blob/main/community/Golang/Go.AllowList.gitignore 3 | # 4 | # Binaries for programs and plugins 5 | *.exe 6 | *.exe~ 7 | *.dll 8 | *.so 9 | *.dylib 10 | 11 | # Test binary, built with `go test -c` 12 | *.test 13 | 14 | # Output of the go coverage tool, specifically when used with LiteIDE 15 | *.out 16 | 17 | # Dependency directories (remove the comment below to include it) 18 | # vendor/ 19 | 20 | # Go workspace file 21 | go.work 22 | go.work.sum 23 | 24 | # env file 25 | .env 26 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Pump.fun 新币监控 - gRPC Golang Demo 2 | 3 | ## 功能 4 | 5 | - 实时监控新币创建 6 | - 显示代币铸造地址和相关信息 7 | - 连接健康检查 (ping/pong) 8 | 9 | ## 使用方法 10 | 11 | 1. 克隆项目 12 | 13 | ```bash 14 | git clone https://github.com/ChainBuff/grpc-demo-golang.git 15 | ``` 16 | 17 | 2. 在项目根目录创建 `.env` 文件 18 | 19 | ```bash 20 | GRPC_URL=你的_grpc_节点地址 21 | # 可以使用免费节点,比如 solana-yellowstone-grpc.publicnode.com:443 22 | # 注意: 不需要再加 https:// 前缀 23 | ``` 24 | 25 | 3. 运行 26 | 27 | ```bash 28 | go mod tidy # 安装依赖 29 | go run cmd/monitor.go 30 | ``` 31 | 32 | ## 输出示例 33 | 34 | ``` 35 | 2025/01/09 15:54:35.802204 ----- New Token Created ----- 36 | Signature: 4zcaz5NKKKSTUnKp4u4iKhAPNNAQrGhETXp5wW27qLexjpjpFgpThvp9vF9a9QXAdFUZ2GTx38a4yk6BFKmkapoj 37 | Slot: 312906820 38 | Mint: 9S6hpnUdFFRgRVwuwvS4kw4Un16CTMRzbE6AeuMWpump 39 | Bonding Curve: EEjyPV1514jCRRVCn98CcjBWWgcg7SDpPBwJmpnG1qZh 40 | Associated Bonding Curve: BhsTp2BWJKhT9yZZbbz9gQgE1dMoiAv4xrzQjtrtwQLu 41 | ``` 42 | 43 | ## 环境要求 44 | 45 | - Go 1.21 或更高版本 46 | - Solana gRPC 节点 47 | 48 | ## 贡献 49 | 50 | 如果发现任何 **bug** 或有功能改进建议,欢迎提交 Issues 或 Pull Requests 至本仓库。 51 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0= 2 | github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= 3 | github.com/mr-tron/base58 v1.2.0 h1:T/HDJBh4ZCPbU39/+c3rRvE0uKBQlU27+QI8LJ4t64o= 4 | github.com/mr-tron/base58 v1.2.0/go.mod h1:BinMc/sQntlIE1frQmRFPUoPA1Zkr8VRgBdjWI2mNwc= 5 | github.com/rpcpool/yellowstone-grpc/examples/golang v0.0.0-20250107190324-f113ddc9f521 h1:+JiV3pMvj9A3hu0O+S1B+5tuFKrHUZBxUgmvT6gp6UU= 6 | github.com/rpcpool/yellowstone-grpc/examples/golang v0.0.0-20250107190324-f113ddc9f521/go.mod h1:dDiynCK1mRAhOOqTVuWYd+MpR2TVq47yVHwGL/lR+iM= 7 | golang.org/x/net v0.30.0 h1:AcW1SDZMkb8IpzCdQUaIq2sP4sZ4zw+55h6ynffypl4= 8 | golang.org/x/net v0.30.0/go.mod h1:2wGyMJ5iFasEhkwi13ChkO/t1ECNC4X4eBKkVFyYFlU= 9 | golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo= 10 | golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= 11 | golang.org/x/text v0.19.0 h1:kTxAhCbGbxhK0IwgSKiMO5awPoDQ0RpfiVYBfK860YM= 12 | golang.org/x/text v0.19.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= 13 | google.golang.org/genproto/googleapis/rpc v0.0.0-20241015192408-796eee8c2d53 h1:X58yt85/IXCx0Y3ZwN6sEIKZzQtDEYaBWrDvErdXrRE= 14 | google.golang.org/genproto/googleapis/rpc v0.0.0-20241015192408-796eee8c2d53/go.mod h1:GX3210XPVPUjJbTUbvwI8f2IpZDMZuPJWDzDuebbviI= 15 | google.golang.org/grpc v1.69.2 h1:U3S9QEtbXC0bYNvRtcoklF3xGtLViumSYxWykJS+7AU= 16 | google.golang.org/grpc v1.69.2/go.mod h1:vyjdE6jLBI76dgpDojsFGNaHlxdjXN9ghpnd2o7JGZ4= 17 | google.golang.org/protobuf v1.35.1 h1:m3LfL6/Ca+fqnjnlqQXNpFPABW1UD7mjh8KO2mKFytA= 18 | google.golang.org/protobuf v1.35.1/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= 19 | -------------------------------------------------------------------------------- /cmd/monitor.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bytes" 5 | "context" 6 | "log" 7 | "os" 8 | "os/signal" 9 | "sync/atomic" 10 | "syscall" 11 | "time" 12 | 13 | "github.com/joho/godotenv" 14 | "github.com/mr-tron/base58" 15 | pb "github.com/rpcpool/yellowstone-grpc/examples/golang/proto" 16 | "google.golang.org/grpc" 17 | "google.golang.org/grpc/credentials" 18 | ) 19 | 20 | const ( 21 | pingInterval = 5 * time.Second 22 | pumpAddr = "6EF8rrecthR5Dkzon8Nwu78hRvfCKubJ14M5uBEwF6P" 23 | ) 24 | 25 | // 统计ping的平均延迟 26 | var ( 27 | lastPingTime time.Time 28 | totalLatency time.Duration 29 | pingCount int64 30 | ) 31 | 32 | // 预编译字符串匹配 33 | var initMintPattern = []byte("InitializeMint2") 34 | 35 | var grpcClient pb.GeyserClient 36 | 37 | // 监控代币事件 38 | func monitorTokens(ctx context.Context, stream pb.Geyser_SubscribeClient) { 39 | for { 40 | if err := ctx.Err(); err != nil { 41 | return 42 | } 43 | 44 | resp, err := stream.Recv() 45 | if err != nil { 46 | log.Printf("Stream error: %v", err) 47 | time.Sleep(5 * time.Second) 48 | continue 49 | } 50 | 51 | // 接收响应时计算延迟 52 | if pong := resp.GetPong(); pong != nil { 53 | latency := time.Since(lastPingTime) 54 | totalLatency += latency 55 | pingCount++ 56 | avgLatency := totalLatency / time.Duration(pingCount) 57 | 58 | log.Printf("Ping Count: %d, Latency: %v, Avg Latency: %v", 59 | pingCount, latency, avgLatency) 60 | continue 61 | } 62 | 63 | if data := resp.GetTransaction(); data != nil { 64 | meta := data.GetTransaction().GetMeta() 65 | 66 | logMessages := meta.GetLogMessages() 67 | if len(logMessages) == 0 { 68 | continue 69 | } 70 | 71 | // 检查是否是新代币创建 72 | // 使用 bytes.Contains 性能更好 73 | hasInitMint := false 74 | for _, log := range logMessages { 75 | if bytes.Contains([]byte(log), initMintPattern) { 76 | hasInitMint = true 77 | break 78 | } 79 | } 80 | 81 | if hasInitMint { 82 | accountKeys := data.GetTransaction().GetTransaction().GetMessage().GetAccountKeys() 83 | 84 | mintAddress := base58.Encode(accountKeys[1]) 85 | bondingCurveAddress := base58.Encode(accountKeys[2]) 86 | associatedBondingCurveAddress := base58.Encode(accountKeys[3]) 87 | 88 | // 异步发送数据 89 | // TODO 90 | 91 | // 不必要的信息放到后边 92 | // 获取交易签名 93 | sig := base58.Encode(data.GetTransaction().GetTransaction().GetSignatures()[0]) 94 | slot := data.GetSlot() 95 | 96 | // 异步打印 97 | go log.Printf("----- New Token Created -----\n"+ 98 | "Signature: %s\n"+ 99 | "Slot: %d\n"+ 100 | "Mint: %s\n"+ 101 | "Bonding Curve: %s\n"+ 102 | "Associated Bonding Curve: %s\n\n", 103 | sig, 104 | slot, 105 | mintAddress, 106 | bondingCurveAddress, 107 | associatedBondingCurveAddress, 108 | ) 109 | } 110 | } 111 | } 112 | } 113 | 114 | func main() { 115 | log.SetFlags(log.Ldate | log.Ltime | log.Lmicroseconds | log.LUTC) 116 | 117 | // 加载 .env 文件 118 | if err := godotenv.Load(); err != nil { 119 | log.Fatal("Error loading .env file") 120 | } 121 | 122 | grpcURL := os.Getenv("GRPC_URL") 123 | if grpcURL == "" { 124 | log.Fatalf("GRPC_URL is not set") 125 | } 126 | 127 | ctx, cancel := context.WithCancel(context.Background()) 128 | defer cancel() 129 | 130 | // 监听系统信号 131 | sigChan := make(chan os.Signal, 1) 132 | signal.Notify(sigChan, syscall.SIGINT, syscall.SIGTERM) 133 | go func() { 134 | sig := <-sigChan 135 | log.Printf("Received signal: %v, shutting down...", sig) 136 | cancel() 137 | }() 138 | 139 | // 设置 gRPC 连接选项 140 | opts := []grpc.DialOption{ 141 | grpc.WithTransportCredentials(credentials.NewTLS(nil)), 142 | } 143 | 144 | // 连接到 gRPC 服务 145 | conn, err := grpc.NewClient(grpcURL, opts...) 146 | if err != nil { 147 | log.Fatalf("Failed to connect to gRPC server: %v", err) 148 | } 149 | defer conn.Close() 150 | 151 | // 创建 Geyser 客户端 152 | grpcClient = pb.NewGeyserClient(conn) 153 | 154 | // 创建订阅请求 155 | falsePtr := false 156 | commitment := pb.CommitmentLevel_PROCESSED 157 | subscription := &pb.SubscribeRequest{ 158 | Transactions: map[string]*pb.SubscribeRequestFilterTransactions{ 159 | "pump_subscription": { 160 | Vote: &falsePtr, 161 | Failed: &falsePtr, 162 | AccountInclude: []string{pumpAddr}, 163 | }, 164 | }, 165 | Commitment: &commitment, 166 | } 167 | 168 | // 创建 gRPC 流 169 | stream, err := grpcClient.Subscribe(ctx) 170 | if err != nil { 171 | log.Fatalf("Failed to start subscription: %v", err) 172 | } 173 | 174 | // 发送订阅请求 175 | if err := stream.Send(subscription); err != nil { 176 | log.Fatalf("Failed to send subscription request: %v", err) 177 | } 178 | 179 | // 创建 ping 请求 180 | var globalID atomic.Int32 181 | pingRequest := &pb.SubscribeRequest{ 182 | Ping: &pb.SubscribeRequestPing{ 183 | Id: 0, 184 | }, 185 | } 186 | ticker := time.NewTicker(pingInterval) 187 | defer ticker.Stop() 188 | 189 | // 在单独的 goroutine 中发送 ping 190 | go func() { 191 | for { 192 | select { 193 | case <-ctx.Done(): 194 | return 195 | case <-ticker.C: 196 | pingRequest.Ping.Id = globalID.Add(1) 197 | lastPingTime = time.Now() // 记录发送时间 198 | if err := stream.Send(pingRequest); err != nil { 199 | log.Printf("Failed to send ping: %v", err) 200 | } 201 | } 202 | } 203 | }() 204 | 205 | // 监控代币事件 206 | monitorTokens(ctx, stream) 207 | } 208 | --------------------------------------------------------------------------------