Skip to content

Commit

Permalink
V1.0.0 (#4)
Browse files Browse the repository at this point in the history
* Added reverse engineering docs

* Update .gitignore

* Added protobuf for Hypurr

* Added better documentation

* Added hyperr utils package

* Added env utils

* Added function to convert unix ms to time.Time

* Added method to extend HyperliquidLaunch

* Added functions to sort launches

* Exported GetValFromEnv

* Added method to format supply

* Added session handlers

* Added default config

* Added helpers to create new launch embeds

* Updated webhook color

* Added new launch monitor

* Added dockerfile

* Added docs on how to run docker image
  • Loading branch information
Matthew17-21 authored Jan 6, 2025
1 parent 1e61a20 commit 2915a3e
Show file tree
Hide file tree
Showing 26 changed files with 10,368 additions and 1 deletion.
4 changes: 4 additions & 0 deletions .Dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
reverse_engineering/
.env
.gitignore
README.md
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -23,3 +23,6 @@ go.work.sum

# env file
.env

# Reverse engineering specifics
reverse_engineering
40 changes: 40 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
###############################################################################
# Build stage
###############################################################################
FROM golang:latest AS builder

# Create and/or change our directory to /build
WORKDIR /build

# Copy go mod & go.sum files so we can verify they haven't been tampered with
COPY go.mod go.sum ./
RUN go mod download && go mod verify

# Copy everything from our root into /build
COPY . .

# Create and/or change our directory to /new-project-monitor
WORKDIR /build/cmd/new-project-monitor

# Create the binary for the app
RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -o app-binary

###############################################################################
# Production stage
###############################################################################
# Start a new stage from scratch so that our final image is way smaller
# We use alpine instead of stratch since we need OS packages
FROM alpine:latest

# Create and/or change our directory to /app
WORKDIR /app

# Copy the binary from the build stage to the final stage
COPY --from=builder /build/cmd/new-project-monitor .


# Expose Port
EXPOSE 8080

# Run the app
CMD ["/app/app-binary"]
98 changes: 97 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,2 +1,98 @@
# HypurrFun
Program to monitor HypurrFun releases

A tool for interacting with hypurr.fun

## Overview

This project allows you to monitor and interact releases on HypurrFun by reverse engineering and decoding the binary gRPC data used in their client-server communication. Since the platform doesn't provide official documentation for their `.protobuf` files or enable gRPC reflection, I've made a quick package to help decode their protocol.

## Features

- Hypurr Protobuf file
- Streaming support
- New project monitor
- Lightning quick
- Discord webhook notification

## How to use

### Docker

To use the new project monitor, you can simply use Docker to build and run the image. To do so, use:

Build the image:

```bash
docker build -t hypurr-project-monitor:latest .
```

Run the image:

```bash
docker run -d -i \
-e NEW_LAUNCHES_WEBHOOK=<your-webhook-url> \
--name hypurr-new-releases hypurr-project-monitor:latest
```

Replace `<your-webhook-url>` with your actual webhook URL for notifications.

### Manually

Alternatively, you can do:

```bash
go run cmd/new-project-monitor/main.go
```

### Integration

#### Go Integration

To use this library in your own Go program, simply follow these steps:

1. Install the package:

```bash
go get -u github.com/Matthew17-21/HypurrFun
```

2. Use in your code:

```go
package main

import (
"log"
hypurrutils "github.com/Matthew17-21/HypurrFun/hypurr_utils"
)

func main() {
userAgent := "<user-agent>" // Replace with your user agent

// Initialize the client
client, err := hypurrutils.NewStaticClient(userAgent)
if err != nil {
log.Fatalln(err)
}

// Your implementation here
}
```

#### Other Languages

The project supports integration with multiple programming languages through Protocol Buffers:

1. Locate the Proto file at [`pb/hypurr.proto`](/pb/hypurr.proto)
2. Generate code for your target language using `protoc`:

```bash
protoc --<language>_out=. pb/hypurr.proto
```

Replace `<language>` with your desired language (e.g., python, java, cpp).

## TODOs

- [ ] Tests
- [ ] Tests for the `hypurr_utils` package
131 changes: 131 additions & 0 deletions cmd/new-project-monitor/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
package main

import (
"context"
"io"
"log"
"time"

hypurrutils "github.com/Matthew17-21/HypurrFun/hypurr_utils"
launchutils "github.com/Matthew17-21/HypurrFun/internal/launch_utils"
"github.com/Matthew17-21/HypurrFun/internal/webhook"
"github.com/Matthew17-21/HypurrFun/pb"
"github.com/joho/godotenv"
"google.golang.org/grpc"
)

const userAgent = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36"

const (
errorDelay = 1500 * time.Millisecond
)

func main() {
// Initialize app
initialize()

// Create a new client
client, err := hypurrutils.NewStaticClient(userAgent)
if err != nil {
log.Fatalln(err)
}

// Create new context
ctx, cancel := context.WithCancel(context.Background())
defer cancel()

// Monitor for new projects
monitorNewLaunches(ctx, client)
}

// initialize sets up the application environment, such as loading `.env` variables.
func initialize() {
log.Println("Initializing application...")

// Attempt to load environment variables from a `.env` file.
// If the file is missing or cannot be read, log a warning and proceed.
if err := godotenv.Load(); err != nil {
log.Printf("WARNING: Failed to load environment variables from .env file: %v", err)
}
}

func monitorNewLaunches(ctx context.Context, client pb.StaticClient) {
log.Println("Monitoring for new Hypurr.fun launches...")

// Create a function to establish the stream
establishStream := func() (grpc.ServerStreamingClient[pb.HyperliquidLaunchStreamResponse], error) {
return client.HyperliquidLaunchStream(ctx, &pb.HyperliquidLaunchStreamRequest{})
}

// Create the stream
launchesStream, err := establishStream()
if err != nil {
log.Fatalln("error with stream:", err)
}

// Define a var to reference when getting new coins
// No need to have an array of launches, we only need to keep track of the lastest one
var latestCoin *launchutils.LaunchExtended

// Listen to for new events
for {
resp, err := launchesStream.Recv()
if err != nil {
// Check to see if stream is closed
if err == io.EOF {
log.Println("Stream is closed.")
return
}
log.Println("Error receiving message from stream:", err)
launchesStream.CloseSend()
time.Sleep(errorDelay)

// Attempt to reestablish the stream
launchesStream, err = establishStream()
if err != nil {
log.Fatalln("error with stream:", err)
}
continue
}

// The first response message from the stream will be a list of tokens sorted by latest activity
// Update our initial array to to that
if latestCoin == nil {
// Sort by listed timestamp in descending order (most recent first)
tmp := launchutils.SortByListedTimestampDesc(resp.Launches)
if len(tmp) == 0 {
continue
}
log.Printf("Received initial launch list with %d launches\n", len(tmp))

// Get the first coin (latest one) and set it as the latest
latestCoin = launchutils.ToLaunchExtended(tmp[0])
continue
}

// For subsequent messages, if the listed timestamp is newer than the first element, add to array & send webhook
for _, launch := range resp.Launches {
// Filter out launches that are either:
// - Duplicates (same ID as an existing launch)
// - Or out of chronological order (earlier timestamp than what we already have)
coin := launchutils.ToLaunchExtended(launch)
if shouldSkipLaunch(coin, latestCoin) {
continue
}
log.Printf("New launch detected: %+v\n", launch)

// Send webhook
webhook.SendNewLaunch(coin)

// Update the latest coin
latestCoin = coin
}
}
}

func shouldSkipLaunch(coin, latestCoin *launchutils.LaunchExtended) bool {
return coin == nil ||
latestCoin == nil ||
coin.Id == latestCoin.Id ||
coin.LaunchTime.Before(latestCoin.LaunchTime)
}
22 changes: 22 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
module github.com/Matthew17-21/HypurrFun

go 1.22.2

require (
github.com/Monumental-Shopping/go-discord-webhook v1.1.1
github.com/joho/godotenv v1.5.1
github.com/stretchr/testify v1.8.4
github.com/test-go/testify v1.1.4
google.golang.org/grpc v1.69.2
google.golang.org/protobuf v1.36.1
)

require (
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
golang.org/x/net v0.33.0 // indirect
golang.org/x/sys v0.28.0 // indirect
golang.org/x/text v0.21.0 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20241230172942-26aa7a208def // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
48 changes: 48 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
github.com/Monumental-Shopping/go-discord-webhook v1.1.1 h1:s4Xit/WweCncUuapna0H2xmYA0/ZALX6il3UJNz2GTI=
github.com/Monumental-Shopping/go-discord-webhook v1.1.1/go.mod h1:WV+RisyjGyR9IzjLMzcSABNzIvFbYTk354DWQN54sxI=
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/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY=
github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0=
github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/test-go/testify v1.1.4 h1:Tf9lntrKUMHiXQ07qBScBTSA0dhYQlu83hswqelv1iE=
github.com/test-go/testify v1.1.4/go.mod h1:rH7cfJo/47vWGdi4GPj16x3/t1xGOj2YxzmNQzk2ghU=
go.opentelemetry.io/otel v1.31.0 h1:NsJcKPIW0D0H3NgzPDHmo0WW6SptzPdqg/L1zsIm2hY=
go.opentelemetry.io/otel v1.31.0/go.mod h1:O0C14Yl9FgkjqcCZAsE053C13OaddMYr/hz6clDkEJE=
go.opentelemetry.io/otel/metric v1.31.0 h1:FSErL0ATQAmYHUIzSezZibnyVlft1ybhy4ozRPcF2fE=
go.opentelemetry.io/otel/metric v1.31.0/go.mod h1:C3dEloVbLuYoX41KpmAhOqNriGbA+qqH6PQ5E5mUfnY=
go.opentelemetry.io/otel/sdk v1.31.0 h1:xLY3abVHYZ5HSfOg3l2E5LUj2Cwva5Y7yGxnSW9H5Gk=
go.opentelemetry.io/otel/sdk v1.31.0/go.mod h1:TfRbMdhvxIIr/B2N2LQW2S5v9m3gOQ/08KsbbO5BPT0=
go.opentelemetry.io/otel/sdk/metric v1.31.0 h1:i9hxxLJF/9kkvfHppyLL55aW7iIJz4JjxTeYusH7zMc=
go.opentelemetry.io/otel/sdk/metric v1.31.0/go.mod h1:CRInTMVvNhUKgSAMbKyTMxqOBC0zgyxzW55lZzX43Y8=
go.opentelemetry.io/otel/trace v1.31.0 h1:ffjsj1aRouKewfr85U2aGagJ46+MvodynlQ1HYdmJys=
go.opentelemetry.io/otel/trace v1.31.0/go.mod h1:TXZkRk7SM2ZQLtR6eoAWQFIHPvzQ06FJAsO1tJg480A=
golang.org/x/net v0.33.0 h1:74SYHlV8BIgHIFC/LrYkOGIwL19eTYXQ5wc6TBuO36I=
golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4=
golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA=
golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo=
golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
google.golang.org/genproto/googleapis/rpc v0.0.0-20241230172942-26aa7a208def h1:4P81qv5JXI/sDNae2ClVx88cgDDA6DPilADkG9tYKz8=
google.golang.org/genproto/googleapis/rpc v0.0.0-20241230172942-26aa7a208def/go.mod h1:bdAgzvd4kFrpykc5/AC2eLUiegK9T/qxZHD4hXYf/ho=
google.golang.org/grpc v1.69.2 h1:U3S9QEtbXC0bYNvRtcoklF3xGtLViumSYxWykJS+7AU=
google.golang.org/grpc v1.69.2/go.mod h1:vyjdE6jLBI76dgpDojsFGNaHlxdjXN9ghpnd2o7JGZ4=
google.golang.org/protobuf v1.36.1 h1:yBPeRvTftaleIgM3PZ/WBIZ7XM/eEYAaEyCwvyjq/gk=
google.golang.org/protobuf v1.36.1/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
25 changes: 25 additions & 0 deletions hypurr_utils/client.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package hypurrutils

import (
"fmt"

"github.com/Matthew17-21/HypurrFun/pb"
)

// NewStaticClient creates a new gRPC client to communicate with Hypurr.fun's static servers
func NewStaticClient(userAgent string) (pb.StaticClient, error) {
conn, err := createGRPCConnection(userAgent)
if err != nil {
return nil, fmt.Errorf("createGRPCConnection error: %w", err)
}
return pb.NewStaticClient(conn), nil
}

// NewTelegramClient creates a new gRPC client to communicate with Hypurr.fun's telegram servers
func NewTelegramClient(userAgent string) (pb.TelegramClient, error) {
conn, err := createGRPCConnection(userAgent)
if err != nil {
return nil, fmt.Errorf("createGRPCConnection error: %w", err)
}
return pb.NewTelegramClient(conn), nil
}
Loading

0 comments on commit 2915a3e

Please sign in to comment.