├── Dockerfile
├── go.mod
├── README.md
├── hugging-allora.sh
├── run.sh
├── go.sum
└── main.go
/Dockerfile:
--------------------------------------------------------------------------------
1 | # Use a lightweight base image
2 | FROM golang:1.22-alpine AS builder
3 |
4 | # Set the working directory
5 | WORKDIR /app
6 |
7 | # Copy go module files
8 | COPY go.mod go.sum ./
9 |
10 | # Download dependencies
11 | RUN go mod download
12 |
13 | # Copy the rest of the application code
14 | COPY . .
15 |
16 | # Build the binary
17 | RUN go build -o main .
18 |
19 | # Use a smaller base image for the final image
20 | FROM alpine:latest
21 |
22 | # Copy the binary from the builder stage
23 | COPY --from=builder /app/main /app/
24 |
25 | # Set the working directory
26 | WORKDIR /app
27 |
28 | # Expose the port your application listens on
29 | EXPOSE 8000
30 |
31 | # Command to run the application
32 | CMD ["./main"]
--------------------------------------------------------------------------------
/go.mod:
--------------------------------------------------------------------------------
1 | module skate
2 |
3 | go 1.21.6
4 |
5 | require (
6 | github.com/gin-gonic/gin v1.10.0
7 | golang.org/x/exp v0.0.0-20240808152545-0cdaa3abc0fa
8 | )
9 |
10 | require (
11 | github.com/bytedance/sonic v1.11.6 // indirect
12 | github.com/bytedance/sonic/loader v0.1.1 // indirect
13 | github.com/cloudwego/base64x v0.1.4 // indirect
14 | github.com/cloudwego/iasm v0.2.0 // indirect
15 | github.com/gabriel-vasile/mimetype v1.4.3 // indirect
16 | github.com/gin-contrib/sse v0.1.0 // indirect
17 | github.com/go-playground/locales v0.14.1 // indirect
18 | github.com/go-playground/universal-translator v0.18.1 // indirect
19 | github.com/go-playground/validator/v10 v10.20.0 // indirect
20 | github.com/goccy/go-json v0.10.2 // indirect
21 | github.com/json-iterator/go v1.1.12 // indirect
22 | github.com/klauspost/cpuid/v2 v2.2.7 // indirect
23 | github.com/leodido/go-urn v1.4.0 // indirect
24 | github.com/mattn/go-isatty v0.0.20 // indirect
25 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
26 | github.com/modern-go/reflect2 v1.0.2 // indirect
27 | github.com/pelletier/go-toml/v2 v2.2.2 // indirect
28 | github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
29 | github.com/ugorji/go/codec v1.2.12 // indirect
30 | golang.org/x/arch v0.8.0 // indirect
31 | golang.org/x/crypto v0.23.0 // indirect
32 | golang.org/x/net v0.25.0 // indirect
33 | golang.org/x/sys v0.20.0 // indirect
34 | golang.org/x/text v0.15.0 // indirect
35 | google.golang.org/protobuf v1.34.1 // indirect
36 | gopkg.in/yaml.v3 v3.0.1 // indirect
37 | )
38 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # allora-worker
2 |
3 | ## Install requirements
4 | ```
5 | sudo apt update && sudo apt upgrade -y
6 | sudo apt install jq -y
7 |
8 | # install docker
9 | curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /usr/share/keyrings/docker-archive-keyring.gpg
10 |
11 | echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
12 |
13 | sudo apt-get update
14 | sudo apt-get install docker-ce docker-ce-cli containerd.io
15 | docker version
16 |
17 | # install docker-compose
18 | VER=$(curl -s https://api.github.com/repos/docker/compose/releases/latest | grep tag_name | cut -d '"' -f 4)
19 |
20 | curl -L "https://github.com/docker/compose/releases/download/"$VER"/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose
21 |
22 | chmod +x /usr/local/bin/docker-compose
23 | docker-compose --version
24 | ```
25 |
26 |
27 | request some faucet from the [Allora Testnet Faucet](https://faucet.testnet-1.testnet.allora.network/)
28 |
29 | ## Stop the old version
30 | If you've previously run the old version and want to stop it before proceeding, follow these commands
31 | ```
32 | cd ~
33 | rm -r ~/allora-worker/
34 | docker stop custom-inference custom-worker custom-worker-0 custom-worker-1
35 | docker container prune -f
36 | docker rmi custom-inference
37 | ```
38 |
39 | ## Run the custom model
40 | 1. Create an account and obtain an Upshot ApiKey [here](https://developer.upshot.xyz)
41 |
42 | 2. Clone the git repository
43 | ```
44 | git clone https://github.com/sarox0987/allora-worker.git
45 | cd allora-worker
46 | ```
47 |
48 | 3. Run the bash script
49 | ```
50 | bash run.sh
51 | ```
52 | * **Index:** Set your worker index.(the index gives you the ability to run multiple workers on one server)
53 | * **Mnemonic Phrase:** Import you menmonic phrase
54 | * **Upshot ApiKey:** Import your upshot apikey
55 |
56 |
57 | make sure both `custom-worker-0` & `custom-inference` containers are running with `docker ps`
58 |
59 |
60 |
61 | check the worker container with `docker logs -f custom-worker-0` command
62 |
63 |
64 |
65 | make sure `custom-inference` returns correct response
66 | ```
67 | curl http://localhost:8001/inference/ETH
68 | curl http://localhost:8001/inference/MEME
69 | curl http://localhost:8001/inference/ELECTION
70 | ```
71 |
72 | ## [DEPRECATED] Run with hugging model and pass your mnemonic phrase to it
73 | ```
74 | curl -LOs https://raw.githubusercontent.com/sarox0987/allora-worker/main/hugging-allora.sh && bash ./hugging-allora.sh
75 | ```
76 |
77 | make sure both `hugging-worker` & `hugging-inference` containers are running with `docker ps`
78 |
79 |
80 |
81 | check the worker container with `docker logs -f hugging-worker` command
82 |
83 | make sure `hugging-inference` is responsive
84 | ```
85 | curl http://localhost:8002/inference/ETH
86 | ```
87 |
88 |
--------------------------------------------------------------------------------
/hugging-allora.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | # Get mnemonic phrase from user
4 | read -p "Enter your mnemonic phrase: " mnemonic_phrase
5 |
6 | mkdir hugging-allora
7 | cd hugging-allora
8 | git clone https://github.com/allora-network/allora-huggingface-walkthrough.git
9 | cd allora-huggingface-walkthrough
10 | mkdir worker-data
11 | touch worker-data/env_file
12 | chmod -R 777 worker-data
13 | rm docker-compose.yaml
14 |
15 | cat << EOF > docker-compose.yaml
16 | services:
17 | hugging-inference:
18 | container_name: hugging-inference
19 | build:
20 | context: .
21 | dockerfile: Dockerfile
22 | command: python -u /app/app.py
23 | ports:
24 | - "8002:8000"
25 |
26 | hugging-worker:
27 | container_name: hugging-worker
28 | image: alloranetwork/allora-offchain-node:latest
29 | volumes:
30 | - ./worker-data:/data
31 | depends_on:
32 | - hugging-inference
33 | env_file:
34 | - ./worker-data/env_file
35 |
36 | volumes:
37 | inference-data:
38 | worker-data:
39 | EOF
40 |
41 |
42 | # Write the JSON content to config.json
43 | cat < config.json
44 | {
45 | "wallet": {
46 | "addressKeyName": "test",
47 | "addressRestoreMnemonic": "$mnemonic_phrase",
48 | "alloraHomeDir": "",
49 | "gas": "1000000",
50 | "gasAdjustment": 1.0,
51 | "nodeRpc": "https://allora-rpc.testnet-1.testnet.allora.network/",
52 | "maxRetries": 1,
53 | "delay": 1,
54 | "submitTx": false
55 | },
56 | "worker": [
57 | {
58 | "topicId": 1,
59 | "inferenceEntrypointName": "api-worker-reputer",
60 | "loopSeconds": 1,
61 | "parameters": {
62 | "InferenceEndpoint": "http://hugging-inference:8000/inference/{Token}",
63 | "Token": "ETH"
64 | }
65 | },
66 | {
67 | "topicId": 2,
68 | "inferenceEntrypointName": "api-worker-reputer",
69 | "loopSeconds": 3,
70 | "parameters": {
71 | "InferenceEndpoint": "http://hugging-inference:8000/inference/{Token}",
72 | "Token": "ETH"
73 | }
74 | },
75 | {
76 | "topicId": 3,
77 | "inferenceEntrypointName": "api-worker-reputer",
78 | "loopSeconds": 5,
79 | "parameters": {
80 | "InferenceEndpoint": "http://hugging-inference:8000/inference/{Token}",
81 | "Token": "BTC"
82 | }
83 | },
84 | {
85 | "topicId": 4,
86 | "inferenceEntrypointName": "api-worker-reputer",
87 | "loopSeconds": 2,
88 | "parameters": {
89 | "InferenceEndpoint": "http://hugging-inference:8000/inference/{Token}",
90 | "Token": "BTC"
91 | }
92 | },
93 | {
94 | "topicId": 5,
95 | "inferenceEntrypointName": "api-worker-reputer",
96 | "loopSeconds": 4,
97 | "parameters": {
98 | "InferenceEndpoint": "http://hugging-inference:8000/inference/{Token}",
99 | "Token": "SOL"
100 | }
101 | },
102 | {
103 | "topicId": 6,
104 | "inferenceEntrypointName": "api-worker-reputer",
105 | "loopSeconds": 5,
106 | "parameters": {
107 | "InferenceEndpoint": "http://hugging-inference:8000/inference/{Token}",
108 | "Token": "SOL"
109 | }
110 | },
111 | {
112 | "topicId": 7,
113 | "inferenceEntrypointName": "api-worker-reputer",
114 | "loopSeconds": 2,
115 | "parameters": {
116 | "InferenceEndpoint": "http://hugging-inference:8000/inference/{Token}",
117 | "Token": "ETH"
118 | }
119 | },
120 | {
121 | "topicId": 8,
122 | "inferenceEntrypointName": "api-worker-reputer",
123 | "loopSeconds": 3,
124 | "parameters": {
125 | "InferenceEndpoint": "http://hugging-inference:8000/inference/{Token}",
126 | "Token": "BNB"
127 | }
128 | },
129 | {
130 | "topicId": 9,
131 | "inferenceEntrypointName": "api-worker-reputer",
132 | "loopSeconds": 5,
133 | "parameters": {
134 | "InferenceEndpoint": "http://hugging-inference:8000/inference/{Token}",
135 | "Token": "ARB"
136 | }
137 | }
138 |
139 | ]
140 | }
141 | EOF
142 |
143 | chmod +x init.config
144 | ./init.config
145 |
146 | # Run docker containers
147 | docker-compose up -d
148 |
--------------------------------------------------------------------------------
/run.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | #rpc="https://allora-rpc.testnet.allora.network"
4 | rpc="https://rpc.ankr.com/allora_testnet"
5 |
6 | read -p "Enter your worker index: " index
7 | read -p "Enter your mnemonic phrase: " mnemonic_phrase
8 | read -p "Enter your upshot apikey: " upshot_apikey
9 |
10 | mkdir worker-data-$index
11 | chmod -R 777 worker-data-$index
12 |
13 | cat << EOF > .env
14 | RPC=$rpc
15 | UPSHOT_APIKEY="$upshot_apikey"
16 | EOF
17 |
18 |
19 | cat << EOF > docker-compose.yaml
20 | services:
21 | custom-inference:
22 | build: .
23 | image: custom-inference
24 | container_name: custom-inference
25 | env_file: .env
26 | ports:
27 | - "8001:8000"
28 |
29 | custom-worker-$index:
30 | container_name: custom-worker-$index
31 | image: alloranetwork/allora-offchain-node:latest
32 | volumes:
33 | - ./worker-data-$index:/data
34 | depends_on:
35 | - custom-inference
36 | env_file:
37 | - ./worker-data-$index/env_file
38 | EOF
39 |
40 |
41 | cat << EOF > init.config
42 | #!/bin/bash
43 |
44 | set -e
45 |
46 | if [ ! -f config.json ]; then
47 | echo "Error: config.json file not found, please provide one"
48 | exit 1
49 | fi
50 |
51 | nodeName=\$(jq -r '.wallet.addressKeyName' config.json)
52 | if [ -z "\$nodeName" ]; then
53 | echo "No wallet name provided for the node, please provide your preferred wallet name. config.json >> wallet.addressKeyName"
54 | exit 1
55 | fi
56 |
57 | # Ensure the worker-data-$index directory exists
58 | mkdir -p ./worker-data-$index
59 |
60 | json_content=\$(cat ./config.json)
61 | stringified_json=\$(echo "\$json_content" | jq -c .)
62 |
63 | mnemonic=\$(jq -r '.wallet.addressRestoreMnemonic' config.json)
64 | if [ -n "\$mnemonic" ]; then
65 | echo "ALLORA_OFFCHAIN_NODE_CONFIG_JSON='\$stringified_json'" > ./worker-data-$index/env_file
66 | echo "NAME=\$nodeName" >> ./worker-data-$index/env_file
67 | echo "ENV_LOADED=true" >> ./worker-data-$index/env_file
68 |
69 | echo "wallet mnemonic already provided by you, loading config.json . Please proceed to run docker compose"
70 | exit 1
71 | fi
72 |
73 | if [ ! -f ./worker-data-$index/env_file ]; then
74 | echo "ENV_LOADED=false" > ./worker-data-$index/env_file
75 | fi
76 |
77 | ENV_LOADED=\$(grep '^ENV_LOADED=' ./worker-data-$index/env_file | cut -d '=' -f 2)
78 | if [ "\$ENV_LOADED" = "false" ]; then
79 | json_content=\$(cat ./config.json)
80 | stringified_json=\$(echo "\$json_content" | jq -c .)
81 |
82 | docker run -it --entrypoint=bash -v \$(pwd)/worker-data-$index:/data -v \$(pwd)/scripts:/scripts -e NAME=\${nodeName}" -e ALLORA_OFFCHAIN_NODE_CONFIG_JSON="\${stringified_json}" alloranetwork/allora-chain:latest -c "bash /scripts/init.sh"
83 | echo "config.json saved to ./worker-data-$index/env_file"
84 | else
85 | echo "config.json is already loaded, skipping the operation. You can set ENV_LOADED variable to false in ./worker-data-$index/env_file to reload the config.json"
86 | fi
87 | EOF
88 |
89 |
90 | cat < config.json
91 | {
92 | "wallet": {
93 | "addressKeyName": "test",
94 | "addressRestoreMnemonic": "$mnemonic_phrase",
95 | "alloraHomeDir": "",
96 | "gas": "1000000",
97 | "gasAdjustment": 1.0,
98 | "nodeRpc": "$rpc",
99 | "maxRetries": 1,
100 | "delay": 1,
101 | "submitTx": true
102 | },
103 | "worker": [
104 | {
105 | "topicId": 1,
106 | "inferenceEntrypointName": "api-worker-reputer",
107 | "loopSeconds": 1,
108 | "parameters": {
109 | "InferenceEndpoint": "http://custom-inference:8000/inference/{Token}",
110 | "Token": "ETH"
111 | }
112 | },
113 | {
114 | "topicId": 2,
115 | "inferenceEntrypointName": "api-worker-reputer",
116 | "loopSeconds": 3,
117 | "parameters": {
118 | "InferenceEndpoint": "http://custom-inference:8000/inference/{Token}",
119 | "Token": "ETH"
120 | }
121 | },
122 | {
123 | "topicId": 3,
124 | "inferenceEntrypointName": "api-worker-reputer",
125 | "loopSeconds": 5,
126 | "parameters": {
127 | "InferenceEndpoint": "http://custom-inference:8000/inference/{Token}",
128 | "Token": "BTC"
129 | }
130 | },
131 | {
132 | "topicId": 4,
133 | "inferenceEntrypointName": "api-worker-reputer",
134 | "loopSeconds": 2,
135 | "parameters": {
136 | "InferenceEndpoint": "http://custom-inference:8000/inference/{Token}",
137 | "Token": "BTC"
138 | }
139 | },
140 | {
141 | "topicId": 5,
142 | "inferenceEntrypointName": "api-worker-reputer",
143 | "loopSeconds": 4,
144 | "parameters": {
145 | "InferenceEndpoint": "http://custom-inference:8000/inference/{Token}",
146 | "Token": "SOL"
147 | }
148 | },
149 | {
150 | "topicId": 6,
151 | "inferenceEntrypointName": "api-worker-reputer",
152 | "loopSeconds": 5,
153 | "parameters": {
154 | "InferenceEndpoint": "http://custom-inference:8000/inference/{Token}",
155 | "Token": "SOL"
156 | }
157 | },
158 | {
159 | "topicId": 7,
160 | "inferenceEntrypointName": "api-worker-reputer",
161 | "loopSeconds": 2,
162 | "parameters": {
163 | "InferenceEndpoint": "http://custom-inference:8000/inference/{Token}",
164 | "Token": "ETH"
165 | }
166 | },
167 | {
168 | "topicId": 8,
169 | "inferenceEntrypointName": "api-worker-reputer",
170 | "loopSeconds": 3,
171 | "parameters": {
172 | "InferenceEndpoint": "http://custom-inference:8000/inference/{Token}",
173 | "Token": "BNB"
174 | }
175 | },
176 | {
177 | "topicId": 9,
178 | "inferenceEntrypointName": "api-worker-reputer",
179 | "loopSeconds": 5,
180 | "parameters": {
181 | "InferenceEndpoint": "http://custom-inference:8000/inference/{Token}",
182 | "Token": "ARB"
183 | }
184 | },
185 | {
186 | "topicId": 10,
187 | "inferenceEntrypointName": "api-worker-reputer",
188 | "loopSeconds": 5,
189 | "parameters": {
190 | "InferenceEndpoint": "http://custom-inference:8000/inference/{Token}",
191 | "Token": "MEME"
192 | }
193 | },
194 | {
195 | "topicId": 11,
196 | "inferenceEntrypointName": "api-worker-reputer",
197 | "loopSeconds": 5,
198 | "parameters": {
199 | "InferenceEndpoint": "http://custom-inference:8000/inference/{Token}",
200 | "Token": "ELECTION"
201 | }
202 | }
203 |
204 | ]
205 | }
206 | EOF
207 |
208 | chmod +x init.config
209 | ./init.config
210 |
211 | # Run docker containers
212 | docker-compose up -d
213 |
--------------------------------------------------------------------------------
/go.sum:
--------------------------------------------------------------------------------
1 | github.com/bytedance/sonic v1.11.6 h1:oUp34TzMlL+OY1OUWxHqsdkgC/Zfc85zGqw9siXjrc0=
2 | github.com/bytedance/sonic v1.11.6/go.mod h1:LysEHSvpvDySVdC2f87zGWf6CIKJcAvqab1ZaiQtds4=
3 | github.com/bytedance/sonic/loader v0.1.1 h1:c+e5Pt1k/cy5wMveRDyk2X4B9hF4g7an8N3zCYjJFNM=
4 | github.com/bytedance/sonic/loader v0.1.1/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU=
5 | github.com/cloudwego/base64x v0.1.4 h1:jwCgWpFanWmN8xoIUHa2rtzmkd5J2plF/dnLS6Xd/0Y=
6 | github.com/cloudwego/base64x v0.1.4/go.mod h1:0zlkT4Wn5C6NdauXdJRhSKRlJvmclQ1hhJgA0rcu/8w=
7 | github.com/cloudwego/iasm v0.2.0 h1:1KNIy1I1H9hNNFEEH3DVnI4UujN+1zjpuk6gwHLTssg=
8 | github.com/cloudwego/iasm v0.2.0/go.mod h1:8rXZaNYT2n95jn+zTI1sDr+IgcD2GVs0nlbbQPiEFhY=
9 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
10 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
11 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
12 | github.com/gabriel-vasile/mimetype v1.4.3 h1:in2uUcidCuFcDKtdcBxlR0rJ1+fsokWf+uqxgUFjbI0=
13 | github.com/gabriel-vasile/mimetype v1.4.3/go.mod h1:d8uq/6HKRL6CGdk+aubisF/M5GcPfT7nKyLpA0lbSSk=
14 | github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE=
15 | github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
16 | github.com/gin-gonic/gin v1.10.0 h1:nTuyha1TYqgedzytsKYqna+DfLos46nTv2ygFy86HFU=
17 | github.com/gin-gonic/gin v1.10.0/go.mod h1:4PMNQiOhvDRa013RKVbsiNwoyezlm2rm0uX/T7kzp5Y=
18 | github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
19 | github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
20 | github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=
21 | github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
22 | github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
23 | github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
24 | github.com/go-playground/validator/v10 v10.20.0 h1:K9ISHbSaI0lyB2eWMPJo+kOS/FBExVwjEviJTixqxL8=
25 | github.com/go-playground/validator/v10 v10.20.0/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM=
26 | github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU=
27 | github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
28 | github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
29 | github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
30 | github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
31 | github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
32 | github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
33 | github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
34 | github.com/klauspost/cpuid/v2 v2.2.7 h1:ZWSB3igEs+d0qvnxR/ZBzXVmxkgt8DdzP6m9pfuVLDM=
35 | github.com/klauspost/cpuid/v2 v2.2.7/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws=
36 | github.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M=
37 | github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ=
38 | github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI=
39 | github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
40 | github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
41 | github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
42 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
43 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
44 | github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
45 | github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
46 | github.com/pelletier/go-toml/v2 v2.2.2 h1:aYUidT7k73Pcl9nb2gScu7NSrKCSHIDE89b3+6Wq+LM=
47 | github.com/pelletier/go-toml/v2 v2.2.2/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h4qDAS4n929Rs=
48 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
49 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
50 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
51 | github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
52 | github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
53 | github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
54 | github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
55 | github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
56 | github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
57 | github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
58 | github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
59 | github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
60 | github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
61 | github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
62 | github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI=
63 | github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
64 | github.com/ugorji/go/codec v1.2.12 h1:9LC83zGrHhuUA9l16C9AHXAqEV/2wBQ4nkvumAE65EE=
65 | github.com/ugorji/go/codec v1.2.12/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg=
66 | golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
67 | golang.org/x/arch v0.8.0 h1:3wRIsP3pM4yUptoR96otTUOXI367OS0+c9eeRi9doIc=
68 | golang.org/x/arch v0.8.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys=
69 | golang.org/x/crypto v0.23.0 h1:dIJU/v2J8Mdglj/8rJ6UUOM3Zc9zLZxVZwwxMooUSAI=
70 | golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8=
71 | golang.org/x/exp v0.0.0-20240808152545-0cdaa3abc0fa h1:ELnwvuAXPNtPk1TJRuGkI9fDTwym6AYBu0qzT8AcHdI=
72 | golang.org/x/exp v0.0.0-20240808152545-0cdaa3abc0fa/go.mod h1:akd2r19cwCdwSwWeIdzYQGa/EZZyqcOdwWiwj5L5eKQ=
73 | golang.org/x/net v0.25.0 h1:d/OCCoBEUq33pjydKrGQhw7IlUPI2Oylr+8qLx49kac=
74 | golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM=
75 | golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
76 | golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
77 | golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y=
78 | golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
79 | golang.org/x/text v0.15.0 h1:h1V/4gjBv8v9cjcR6+AR5+/cIYK5N/WAgiv4xlsEtAk=
80 | golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
81 | google.golang.org/protobuf v1.34.1 h1:9ddQBjfCyZPOHPUiPxpYESBLc+T8P3E+Vo4IbKZgFWg=
82 | google.golang.org/protobuf v1.34.1/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
83 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
84 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
85 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
86 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
87 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
88 | nullprogram.com/x/optparse v1.0.0/go.mod h1:KdyPE+Igbe0jQUrVfMqDMeJQIJZEuyV7pjYmp6pbG50=
89 | rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4=
90 |
--------------------------------------------------------------------------------
/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "encoding/json"
5 | "fmt"
6 | "io"
7 | "net/http"
8 | "net/url"
9 | "os"
10 | "strconv"
11 | "time"
12 |
13 | "github.com/gin-gonic/gin"
14 | "golang.org/x/exp/rand"
15 | )
16 |
17 | type Kline struct {
18 | OpenTime time.Time
19 | CloseTime time.Time
20 | Interval string
21 | Symbol string
22 | Open string
23 | High string
24 | Low string
25 | Close string
26 | Volume string
27 | Closed bool
28 | }
29 |
30 | func main() {
31 |
32 | cfg := &envConfig{
33 | APIKey: os.Getenv("UPSHOT_APIKEY"),
34 | RPC: os.Getenv("RPC"),
35 | }
36 |
37 | fmt.Println("UPSHOT_APIKEY: ", cfg.APIKey)
38 | fmt.Println("RPC: ", cfg.RPC)
39 |
40 | router := gin.Default()
41 |
42 | router.GET("/inference/:token", func(c *gin.Context) {
43 | token := c.Param("token")
44 | if token == "MEME" {
45 | handleMemeRequest(c, cfg)
46 | return
47 | }
48 |
49 | if token == "ELECTION" {
50 | handleElectRequest(c)
51 | return
52 | }
53 |
54 | symbol := fmt.Sprintf("%sUSDT", token)
55 |
56 | k, err := getLastKlines(symbol, "15m")
57 | if err != nil {
58 | fmt.Println(err)
59 | return
60 | }
61 |
62 | rate, err := calculatePriceChangeRate(*k)
63 | if err != nil {
64 | fmt.Println(err)
65 | return
66 | }
67 | rate = multiplyChangeRate(rate)
68 | close, _ := strconv.ParseFloat(k.Close, 64)
69 | price := close + (close * rate)
70 |
71 | c.String(200, strconv.FormatFloat(price, 'g', -1, 64))
72 | })
73 |
74 | router.Run(":8000")
75 |
76 | }
77 |
78 | func handleMemeRequest(c *gin.Context, cfg *envConfig) {
79 |
80 | if cfg.APIKey == "" {
81 | c.String(400, "need api key")
82 | }
83 |
84 | if cfg.RPC == "" {
85 | panic("Invalid env.json file")
86 | }
87 |
88 | lb, err := getLatestBlock(cfg.RPC)
89 | if err != nil {
90 | fmt.Println(err)
91 | return
92 | }
93 |
94 | meme, err := getMemeOracleData(lb, cfg.APIKey)
95 | if err != nil {
96 | fmt.Println(err)
97 | return
98 | }
99 |
100 | mp, err := getMemePrice(meme.Data.Platform, meme.Data.Address)
101 | if err != nil {
102 | fmt.Println(err)
103 | return
104 | }
105 |
106 | fmt.Printf("\nBlockHeight: \"%s\", Meme: \"%s\", Platform: \"%s\", Price: \"%s\"\n\n",
107 | lb, meme.Data.TokenSymbol, meme.Data.Platform, mp)
108 |
109 | mpf, _ := strconv.ParseFloat(mp, 64)
110 |
111 | c.String(http.StatusOK, strconv.FormatFloat(random(mpf), 'g', -1, 64))
112 | }
113 |
114 | func getLastKlines(symbol, interval string) (*Kline, error) {
115 |
116 | ur, _ := url.Parse("https://api.binance.com/api/v1/klines")
117 | queryParams := url.Values{}
118 | queryParams.Add("endTime", strconv.Itoa(int(time.Now().UnixMilli())))
119 | queryParams.Add("limit", "1")
120 | queryParams.Add("symbol", symbol)
121 | queryParams.Add("interval", interval)
122 | ur.RawQuery = queryParams.Encode()
123 | resp, err := http.DefaultClient.Get(ur.String())
124 | if err != nil {
125 | return nil, err
126 | }
127 |
128 | defer resp.Body.Close()
129 | if resp.StatusCode != http.StatusOK {
130 | return nil, fmt.Errorf("status code %d", resp.StatusCode)
131 | }
132 |
133 | var ks [][]interface{}
134 | b, err := io.ReadAll(resp.Body)
135 | if err != nil {
136 | return nil, err
137 | }
138 |
139 | err = json.Unmarshal(b, &ks)
140 | if err != nil {
141 | return nil, err
142 | }
143 |
144 | if len(ks) == 0 {
145 | return nil, err
146 | }
147 |
148 | kline := ks[0]
149 | return &Kline{
150 | OpenTime: time.UnixMilli(int64(kline[0].(float64))),
151 | Interval: interval,
152 | Symbol: symbol,
153 | Open: kline[1].(string),
154 | High: kline[4].(string),
155 | Low: kline[2].(string),
156 | Close: kline[3].(string),
157 | Volume: kline[5].(string),
158 | }, nil
159 | }
160 |
161 | func calculatePriceChangeRate(kline Kline) (float64, error) {
162 | open, err := strconv.ParseFloat(kline.Open, 64)
163 | if err != nil {
164 | return 0, err
165 | }
166 | close, err := strconv.ParseFloat(kline.Close, 64)
167 | if err != nil {
168 | return 0, err
169 | }
170 |
171 | if open == 0 {
172 | return 0, fmt.Errorf("open price cannot be zero")
173 | }
174 |
175 | priceChangeRate := (close - open) / open
176 | return priceChangeRate, nil
177 | }
178 |
179 | func multiplyChangeRate(changeRate float64) float64 {
180 | r := rand.New(rand.NewSource(uint64(time.Now().UnixNano())))
181 |
182 | multiplier := r.Float64()*0.8 + 0.1
183 | newChangeRate := changeRate * multiplier
184 | return newChangeRate + changeRate
185 | }
186 |
187 | func getMemePrice(network, memeAddress string) (string, error) {
188 | url := fmt.Sprintf("https://api.geckoterminal.com/api/v2/simple/networks/%s/token_price/%s", network, memeAddress)
189 |
190 | req, err := http.NewRequest(http.MethodGet, url, nil)
191 | if err != nil {
192 | return "", fmt.Errorf("failed to create new request: %w", err)
193 | }
194 | req.Header.Set("accept", "application/json")
195 |
196 | client := &http.Client{}
197 | resp, err := client.Do(req)
198 | if err != nil {
199 | return "", fmt.Errorf("failed to execute request: %w", err)
200 | }
201 | defer resp.Body.Close()
202 |
203 | body, err := io.ReadAll(resp.Body)
204 | if err != nil {
205 | return "", fmt.Errorf("failed to read response body: %w", err)
206 | }
207 |
208 | res := &tokenPriceResponse{}
209 | err = json.Unmarshal(body, res)
210 | if err != nil {
211 | return "", fmt.Errorf("failed to unmarshal response: %w", err)
212 | }
213 |
214 | return res.Data.Attributes.TokenPrices[memeAddress], nil
215 | }
216 |
217 | type tokenPriceResponse struct {
218 | Data struct {
219 | Attributes struct {
220 | TokenPrices map[string]string `json:"token_prices"`
221 | } `json:"attributes"`
222 | } `json:"data"`
223 | }
224 |
225 | type latestBlockResponse struct {
226 | Result struct {
227 | SyncInfo struct {
228 | LatestBlockHeight string `json:"latest_block_height"`
229 | } `json:"sync_info"`
230 | } `json:"result"`
231 | }
232 |
233 | func getLatestBlock(rpc string) (string, error) {
234 | req, err := http.NewRequest(http.MethodGet, fmt.Sprintf("%s/status", rpc), nil)
235 | if err != nil {
236 | return "", fmt.Errorf("failed to create new request: %w", err)
237 | }
238 |
239 | client := &http.Client{}
240 | resp, err := client.Do(req)
241 | if err != nil {
242 | return "", fmt.Errorf("failed to execute request: %w", err)
243 | }
244 | defer resp.Body.Close()
245 |
246 | body, err := io.ReadAll(resp.Body)
247 | if err != nil {
248 | return "", fmt.Errorf("failed to read response body: %w", err)
249 | }
250 |
251 | var response latestBlockResponse
252 | err = json.Unmarshal(body, &response)
253 | if err != nil {
254 | return "", fmt.Errorf("failed to unmarshal response: %w", err)
255 | }
256 |
257 | return response.Result.SyncInfo.LatestBlockHeight, nil
258 | }
259 |
260 | type envConfig struct {
261 | RPC string `json:"rpc"`
262 | APIKey string `json:"api_key"`
263 | }
264 |
265 | type memeOracleResponse struct {
266 | RequestID string `json:"request_id"`
267 | Status bool `json:"status"`
268 | Data struct {
269 | TokenID string `json:"token_id"`
270 | TokenSymbol string `json:"token_symbol"`
271 | Platform string `json:"platform"`
272 | Address string `json:"address"`
273 | } `json:"data"`
274 | }
275 |
276 | func getMemeOracleData(blockHeight string, apiKey string) (*memeOracleResponse, error) {
277 | url := fmt.Sprintf("https://api.upshot.xyz/v2/allora/tokens-oracle/token/%s", blockHeight)
278 |
279 | req, err := http.NewRequest(http.MethodGet, url, nil)
280 | if err != nil {
281 | return nil, fmt.Errorf("failed to create new request: %w", err)
282 | }
283 | req.Header.Set("accept", "application/json")
284 | req.Header.Set("x-api-key", apiKey)
285 |
286 | client := &http.Client{}
287 | resp, err := client.Do(req)
288 | if err != nil {
289 | return nil, err
290 | }
291 | defer resp.Body.Close()
292 |
293 | body, err := io.ReadAll(resp.Body)
294 | if err != nil {
295 | return nil, err
296 | }
297 |
298 | res := &memeOracleResponse{}
299 | err = json.Unmarshal(body, res)
300 | if err != nil {
301 | return nil, fmt.Errorf("failed to unmarshal response: %w", err)
302 | }
303 |
304 | return res, nil
305 | }
306 |
307 | func random(price float64) float64 {
308 | randomPercent := rand.Float64()*6 - 3
309 |
310 | priceChange := price * (randomPercent / 100)
311 |
312 | return price + priceChange
313 | }
314 |
315 | type PriceData struct {
316 | History []struct {
317 | T int64 `json:"t"`
318 | P float64 `json:"p"`
319 | } `json:"history"`
320 | }
321 |
322 | func handleElectRequest(c *gin.Context) {
323 | url := "https://clob.polymarket.com/prices-history?interval=all&market=21742633143463906290569050155826241533067272736897614950488156847949938836455&fidelity=1000"
324 | data, err := getPriceData(url)
325 | if err != nil {
326 | fmt.Println(err)
327 | return
328 | }
329 |
330 | price, err := getRandomPrice(data, 100)
331 | if err != nil {
332 | fmt.Println(err)
333 | return
334 | }
335 |
336 | p := strconv.FormatFloat(random(price*100), 'f', 2, 64)
337 | fmt.Println("Election Response: ", p)
338 | c.String(http.StatusOK, p)
339 | }
340 |
341 | func getPriceData(url string) (*PriceData, error) {
342 | resp, err := http.Get(url)
343 | if err != nil {
344 | return nil, err
345 | }
346 | defer resp.Body.Close()
347 |
348 | body, err := io.ReadAll(resp.Body)
349 | if err != nil {
350 | return nil, err
351 | }
352 |
353 | var priceData PriceData
354 | err = json.Unmarshal(body, &priceData)
355 | if err != nil {
356 | return nil, err
357 | }
358 |
359 | return &priceData, nil
360 | }
361 |
362 | func getRandomPrice(data *PriceData, n int) (float64, error) {
363 | historyLen := len(data.History)
364 | if historyLen == 0 {
365 | return 0, fmt.Errorf("no price data available")
366 | }
367 |
368 | if n > historyLen {
369 | n = historyLen
370 | }
371 |
372 | rand.Seed(uint64(time.Now().UnixNano()))
373 | randomIndex := rand.Intn(n)
374 | fmt.Println(data.History[len(data.History)-1])
375 | return data.History[historyLen-n+randomIndex].P, nil
376 | }
377 |
--------------------------------------------------------------------------------