package main import ( "bufio" "context" "crypto/rand" "encoding/hex" "encoding/json" "fmt" "log" "net" "os" "strings" "time" "github.com/waku-org/go-waku/waku/v2/node" "github.com/waku-org/go-waku/waku/v2/protocol" "github.com/waku-org/go-waku/waku/v2/protocol/pb" "github.com/waku-org/go-waku/waku/v2/protocol/relay" "github.com/waku-org/go-waku/waku/v2/utils" ) const sharedKey = "2b7e151628aed2a6abf7158809cf4f3c762e7160f38b4da56a784d9045190cfe" type ChatMessage struct { Timestamp time.Time `json:"timestamp"` Nick string `json:"nick"` Message string `json:"message"` Key string `json:"key"` } func main() { ctx := context.Background() hostAddr, _ := net.ResolveTCPAddr("tcp", "0.0.0.0:0") wakuNode, err := node.New( node.WithHostAddress(hostAddr), node.WithWakuRelay(), ) if err != nil { log.Fatal("Error creating waku node:", err) } if err := wakuNode.Start(ctx); err != nil { log.Fatal("Error starting waku node:", err) } defer wakuNode.Stop() pubsubTopic := protocol.DefaultPubsubTopic{}.String() contentTopic, _ := protocol.NewContentTopic("waku2chat", "1", "chat", "proto") contentFilter := protocol.NewContentFilter(pubsubTopic, contentTopic.String()) subs, err := wakuNode.Relay().Subscribe(ctx, contentFilter) if err != nil { log.Fatal("Error subscribing to topic:", err) } if len(subs) == 0 { log.Fatal("No subscriptions created") } sub := subs[0] log.Println("Waku2 Chat Started") log.Println("Node ID:", wakuNode.ID()) log.Println("Listening on:", wakuNode.ListenAddresses()) log.Println("Shared Key:", sharedKey) var connected bool if len(wakuNode.Relay().PubSub().ListPeers(pubsubTopic)) == 0 { log.Println("No peers connected yet. Trying to connect to bootstrap nodes...") connected = connectToBootstrapNodes(ctx, wakuNode) } else { connected = true } if !connected { log.Fatal("Failed to connect to Waku network. Exiting.") } go receiveMessages(ctx, sub) reader := bufio.NewReader(os.Stdin) nick := fmt.Sprintf("user-%s", generateRandomID()) fmt.Printf("Your nickname: %s\n", nick) fmt.Println("Type messages and press Enter to send (or 'quit' to exit):") for { fmt.Print("> ") text, _ := reader.ReadString('\n') text = strings.TrimSpace(text) if text == "quit" { break } if text == "" { continue } // Check if still connected before sending if len(wakuNode.Relay().PubSub().ListPeers(pubsubTopic)) == 0 { log.Println("Not connected to any peers. Message not sent.") continue } msg := ChatMessage{ Timestamp: time.Now(), Nick: nick, Message: text, Key: sharedKey, } msgBytes, err := json.Marshal(msg) if err != nil { log.Println("Error marshaling message:", err) continue } wakuMsg := &pb.WakuMessage{ Payload: msgBytes, ContentTopic: contentTopic.String(), Timestamp: utils.GetUnixEpochFrom(wakuNode.Timesource().Now()), } _, err = wakuNode.Relay().Publish(ctx, wakuMsg) if err != nil { log.Println("Error publishing message:", err) } } } func receiveMessages(ctx context.Context, sub *relay.Subscription) { for { select { case <-ctx.Done(): return case envelope := <-sub.Ch: if envelope.Message() == nil { continue } var msg ChatMessage err := json.Unmarshal(envelope.Message().Payload, &msg) if err != nil { continue } if msg.Key != sharedKey { continue } fmt.Printf("\n[%s] %s: %s\n> ", msg.Timestamp.Format("15:04:05"), msg.Nick, msg.Message) } } } func connectToBootstrapNodes(ctx context.Context, wakuNode *node.WakuNode) bool { bootstrapNodes := []string{ // Latest Waku v2 production fleet nodes from waku.org "/dns4/node-01.do-ams3.waku.test.status.im/tcp/30303/p2p/16Uiu2HAmPLe7Mzm8TsYUubgCAW1aJoeFScxrLj8ppHFivPo97bUZ", "/dns4/node-01.gc-us-central1-a.waku.test.status.im/tcp/30303/p2p/16Uiu2HAmJb2e28qLXxT5kZxVUUoJt72EMzNGXB47Rxx5hw3q4YjS", "/dns4/node-01.ac-cn-hongkong-c.waku.test.status.im/tcp/30303/p2p/16Uiu2HAkvWiyFsgRhuJEb9JfjYxEkoHLgnUQmr1N5mKWnYjxYRVm", // Waku Sandbox nodes - actively maintained "/dns4/boot-01.do-ams3.waku.sandbox.status.im/tcp/30303/p2p/16Uiu2HAmPLtEfJpCyUM9JcVDhP3zJ1Mxx1L5WzXp5qJFH2rqVGJS", "/dns4/boot-01.gc-us-central1-a.waku.sandbox.status.im/tcp/30303/p2p/16Uiu2HAmQq5okTJWCUXo8F8Kf3kQ2kLPdqVUoJmqZ3cFWV3u8HuH", "/dns4/boot-01.ac-cn-hongkong-c.waku.sandbox.status.im/tcp/30303/p2p/16Uiu2HAmQJvn5BKvdXFPRTCNBVrWwAJ3dCLKxZDPtJFhLFpHQYoS", // Current production nodes with resolved IPs "/dns4/node-01.do-ams3.wakuv2.prod.statusim.net/tcp/30303/p2p/16Uiu2HAmL5okWopX7NqZWBUKVqW8iUxCEmd5GMHLVPwCgzYzQv3e", "/dns4/node-01.gc-us-central1-a.wakuv2.prod.statusim.net/tcp/30303/p2p/16Uiu2HAmVkKntsECaYfefR1V2yCR79CegLATuTPE6B9TxgxBiiiA", "/dns4/node-01.ac-cn-hongkong-c.wakuv2.prod.statusim.net/tcp/30303/p2p/16Uiu2HAm4v86W3bmT1BiH6oSPzcsSr24iDQpSN5Qa992BCjjwgrD", // Use resolved IPs directly in case DNS is issue "/ip4/188.166.135.145/tcp/30303/p2p/16Uiu2HAmL5okWopX7NqZWBUKVqW8iUxCEmd5GMHLVPwCgzYzQv3e", "/ip4/34.121.100.108/tcp/30303/p2p/16Uiu2HAmVkKntsECaYfefR1V2yCR79CegLATuTPE6B9TxgxBiiiA", "/ip4/8.210.222.231/tcp/30303/p2p/16Uiu2HAm4v86W3bmT1BiH6oSPzcsSr24iDQpSN5Qa992BCjjwgrD", } connected := false for _, addr := range bootstrapNodes { // Create a context with 2 second timeout dialCtx, cancel := context.WithTimeout(ctx, 2*time.Second) err := wakuNode.DialPeer(dialCtx, addr) cancel() if err != nil { log.Printf("Could not connect to bootstrap node: %v", err) } else { log.Printf("Connected to bootstrap node: %s", addr) connected = true break } } if !connected { log.Println("ERROR: Could not connect to any bootstrap nodes") log.Println("The program will not be able to send or receive messages.") } return connected } func generateRandomID() string { b := make([]byte, 4) rand.Read(b) return hex.EncodeToString(b) }