├── .gitignore
├── README.md
├── common
├── response.go
└── utils.go
├── config.toml.example
├── config
└── config.go
├── go.mod
├── go.sum
├── main.go
├── router
├── proxy.go
└── router.go
└── ws
└── init.go
/.gitignore:
--------------------------------------------------------------------------------
1 | .idea/
2 | electrumx-proxy-go
3 | .env
4 | config.toml
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Electrumx Proxy Go for Atomicals
2 |
3 |
4 |
5 | Electrumx Proxy Go is an implementation of a proxy server for Electrumx written in Go. It is designed to allow clients to connect to Electrumx servers through a high-performance, scalable Go-based intermediary. By serving as a robust buffer between clients and Electrumx servers, Electrumx Proxy Go significantly increases the stability and reliability of RPC communications within the Atomical ecosystem. This enhancement is crucial for systems that require consistent and uninterrupted access to blockchain data, ensuring that applications remain responsive and resilient to fluctuations in demand or network conditions.
6 |
7 |
8 |
9 | ## Prerequisites
10 |
11 | Before you begin, ensure you have Go version 1.21 or higher installed on your system. You can verify your Go
12 | installation and version by running:
13 |
14 | ```bash
15 | go version
16 | ```
17 |
18 | This command should output a version number. If the version number is less than 1.21, you will need to update Go to a
19 | newer version. Visit the [official Go download page](https://golang.org/dl/) for instructions on how to install or
20 | update Go.
21 |
22 | ## Installation
23 |
24 | To install Electrumx Proxy Go, clone the repository, and run the main application:
25 |
26 | ```bash
27 | git clone https://github.com/NeutronProtocol/electrumx-proxy-go.git
28 | cd electrumx-proxy-go
29 | ```
30 |
31 | ## Configuration
32 |
33 | Before starting the proxy, you need to configure the `config.toml` file with the correct parameters:
34 |
35 | - `ElectrumxServer` should be set to the address of your Electrumx server (e.g., `ws://127.0.0.1:50002`).
36 | - `ServerAddress` should be set to the address and port you want the proxy to listen on (e.g., `0.0.0.0:8080`).
37 |
38 | Open `config.toml` in a text editor and modify the settings accordingly:
39 |
40 | ```toml
41 | ElectrumxServer = "ws://127.0.0.1:50002"
42 | ServerAddress = "0.0.0.0:8080"
43 | ```
44 |
45 | Save the file after making the necessary changes.
46 |
47 | ## Starting the Proxy
48 |
49 | Once you have configured the settings, you can start the proxy by running:
50 |
51 | ```bash
52 | go run main.go
53 | ```
54 |
55 | Or
56 |
57 | ```bash
58 | go build
59 | ./electrumx-proxy-go
60 | ```
61 |
62 | ## Usage
63 |
64 | After starting the main application, the proxy will begin listening for incoming connections on the
65 | configured `ServerAddress`. Point your Electrumx server's client connections to this address.
66 |
67 | ## Features
68 |
69 | - High-performance Go-based proxy.
70 | - Easy integration with existing Electrumx servers.
71 | - Lightweight and efficient connection handling.
72 |
73 | ## License
74 |
75 | This project is licensed under the MIT License - see the `LICENSE` file for details.
76 |
77 | ## Contact
78 |
79 | Project
80 | Link: [https://github.com/NeutronProtocol/electrumx-proxy-go](https://github.com/NeutronProtocol/electrumx-proxy-go)
81 |
82 | Website: [https://www.atomicalneutron.com/](https://www.atomicalneutron.com/)
83 |
--------------------------------------------------------------------------------
/common/response.go:
--------------------------------------------------------------------------------
1 | package common
2 |
3 | import (
4 | "encoding/json"
5 | )
6 |
7 | // Result is the response structure for JSON output.
8 | type Result struct {
9 | Success bool `json:"success"`
10 | Info interface{} `json:"info"`
11 | }
12 |
13 | // Info contains detailed information to be returned in the proxy response.
14 | type Info struct {
15 | Note string `json:"note"`
16 | UsageInfo UsageInfo `json:"usageInfo"`
17 | HealthCheck string `json:"healthCheck"`
18 | Github string `json:"github"`
19 | License string `json:"license"`
20 | }
21 |
22 | // UsageInfo provides usage details for the proxy service.
23 | type UsageInfo struct {
24 | Note string `json:"note"`
25 | POST string `json:"POST"`
26 | GET string `json:"GET"`
27 | }
28 |
29 | type JsonRpcRequest struct {
30 | ID uint32 `json:"id"`
31 | Method string `json:"method"`
32 | Params []interface{} `json:"params"`
33 | }
34 |
35 | type JsonRpcResponse struct {
36 | Result *json.RawMessage `json:"result"`
37 | Error *json.RawMessage `json:"error"`
38 | ID uint32 `json:"id"`
39 | }
40 |
--------------------------------------------------------------------------------
/common/utils.go:
--------------------------------------------------------------------------------
1 | package common
2 |
--------------------------------------------------------------------------------
/config.toml.example:
--------------------------------------------------------------------------------
1 | ElectrumxServer = "ws://127.0.0.1:50002"
2 | ServerAddress = "0.0.0.0:8080"
--------------------------------------------------------------------------------
/config/config.go:
--------------------------------------------------------------------------------
1 | package config
2 |
3 | import (
4 | "github.com/BurntSushi/toml"
5 | "log"
6 | )
7 |
8 | type Config struct {
9 | ElectrumxServer string
10 | ServerAddress string
11 | }
12 |
13 | var Conf Config
14 |
15 | func InitConf() {
16 | if _, err := toml.DecodeFile("config.toml", &Conf); err != nil {
17 | log.Fatal(err)
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/go.mod:
--------------------------------------------------------------------------------
1 | module electrumx-proxy-go
2 |
3 | go 1.21
4 |
5 | require (
6 | github.com/BurntSushi/toml v1.3.2
7 | github.com/gin-gonic/gin v1.9.1
8 | github.com/gorilla/websocket v1.5.1
9 | github.com/itsjamie/gin-cors v0.0.0-20220228161158-ef28d3d2a0a8
10 | )
11 |
12 | require (
13 | github.com/bytedance/sonic v1.9.1 // indirect
14 | github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 // indirect
15 | github.com/gabriel-vasile/mimetype v1.4.2 // 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.14.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.4 // indirect
23 | github.com/leodido/go-urn v1.2.4 // indirect
24 | github.com/mattn/go-isatty v0.0.19 // 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.0.8 // indirect
28 | github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
29 | github.com/ugorji/go/codec v1.2.11 // indirect
30 | golang.org/x/arch v0.3.0 // indirect
31 | golang.org/x/crypto v0.14.0 // indirect
32 | golang.org/x/net v0.17.0 // indirect
33 | golang.org/x/sys v0.13.0 // indirect
34 | golang.org/x/text v0.13.0 // indirect
35 | google.golang.org/protobuf v1.30.0 // indirect
36 | gopkg.in/yaml.v3 v3.0.1 // indirect
37 | )
38 |
--------------------------------------------------------------------------------
/go.sum:
--------------------------------------------------------------------------------
1 | github.com/BurntSushi/toml v1.3.2 h1:o7IhLm0Msx3BaB+n3Ag7L8EVlByGnpq14C4YWiu/gL8=
2 | github.com/BurntSushi/toml v1.3.2/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
3 | github.com/bytedance/sonic v1.5.0/go.mod h1:ED5hyg4y6t3/9Ku1R6dU/4KyJ48DZ4jPhfY1O2AihPM=
4 | github.com/bytedance/sonic v1.9.1 h1:6iJ6NqdoxCDr6mbY8h18oSO+cShGSMRGCEo7F2h0x8s=
5 | github.com/bytedance/sonic v1.9.1/go.mod h1:i736AoUSYt75HyZLoJW9ERYxcy6eaN6h4BZXU064P/U=
6 | github.com/chenzhuoyu/base64x v0.0.0-20211019084208-fb5309c8db06/go.mod h1:DH46F32mSOjUmXrMHnKwZdA8wcEefY7UVqBKYGjpdQY=
7 | github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 h1:qSGYFH7+jGhDF8vLC+iwCD4WpbV1EBDSzWkJODFLams=
8 | github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311/go.mod h1:b583jCggY9gE99b6G5LEC39OIiVsWj+R97kbl5odCEk=
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.2 h1:w5qFW6JKBz9Y393Y4q372O9A7cUSequkh1Q7OhCmWKU=
13 | github.com/gabriel-vasile/mimetype v1.4.2/go.mod h1:zApsH/mKG4w07erKIaJPFiX0Tsq9BFQgN3qGY5GnNgA=
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.9.1 h1:4idEAncQnU5cB7BeOkPtxjfCSye0AAm1R0RVIqJ+Jmg=
17 | github.com/gin-gonic/gin v1.9.1/go.mod h1:hPrL7YrpYKXt5YId3A/Tnip5kqbEAP+KLuI3SUcPTeU=
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.14.0 h1:vgvQWe3XCz3gIeFDm/HnTIbj6UGmg/+t63MyGU2n5js=
25 | github.com/go-playground/validator/v10 v10.14.0/go.mod h1:9iXMNT7sEkjXb0I+enO7QXmzG6QCsPWY4zveKFVRSyU=
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/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
29 | github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU=
30 | github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
31 | github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
32 | github.com/gorilla/websocket v1.5.1 h1:gmztn0JnHVt9JZquRuzLw3g4wouNVzKL15iLr/zn/QY=
33 | github.com/gorilla/websocket v1.5.1/go.mod h1:x3kM2JMyaluk02fnUJpQuwD2dCS5NDG2ZHL0uE0tcaY=
34 | github.com/itsjamie/gin-cors v0.0.0-20220228161158-ef28d3d2a0a8 h1:3n0c+dqwjqfvvoV+Q3hWvXT58q/YGnegkFx8w56Kj44=
35 | github.com/itsjamie/gin-cors v0.0.0-20220228161158-ef28d3d2a0a8/go.mod h1:AYdLvrSBFloDBNt7Y8xkQ6gmhCODGl8CPikjyIOnNzA=
36 | github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
37 | github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
38 | github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
39 | github.com/klauspost/cpuid/v2 v2.2.4 h1:acbojRNwl3o09bUq+yDCtZFc1aiwaAAxtcn8YkZXnvk=
40 | github.com/klauspost/cpuid/v2 v2.2.4/go.mod h1:RVVoqg1df56z8g3pUjL/3lE5UfnlrJX8tyFgg4nqhuY=
41 | github.com/leodido/go-urn v1.2.4 h1:XlAE/cm/ms7TE/VMVoduSpNBoyc2dOxHs5MZSwAN63Q=
42 | github.com/leodido/go-urn v1.2.4/go.mod h1:7ZrI8mTSeBSHl/UaRyKQW1qZeMgak41ANeCNaVckg+4=
43 | github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA=
44 | github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
45 | github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
46 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
47 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
48 | github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
49 | github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
50 | github.com/pelletier/go-toml/v2 v2.0.8 h1:0ctb6s9mE31h0/lhu+J6OPmVeDxJn+kYnJc2jZR9tGQ=
51 | github.com/pelletier/go-toml/v2 v2.0.8/go.mod h1:vuYfssBdrU2XDZ9bYydBu6t+6a6PYNcZljzZR9VXg+4=
52 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
53 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
54 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
55 | github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
56 | github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
57 | github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
58 | github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
59 | github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
60 | github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
61 | github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
62 | github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
63 | github.com/stretchr/testify v1.8.3 h1:RP3t2pwF7cMEbC1dqtB6poj3niw/9gnV4Cjg5oW5gtY=
64 | github.com/stretchr/testify v1.8.3/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
65 | github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI=
66 | github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
67 | github.com/ugorji/go/codec v1.2.11 h1:BMaWp1Bb6fHwEtbplGBGJ498wD+LKlNSl25MjdZY4dU=
68 | github.com/ugorji/go/codec v1.2.11/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg=
69 | golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
70 | golang.org/x/arch v0.3.0 h1:02VY4/ZcO/gBOH6PUaoiptASxtXU10jazRCP865E97k=
71 | golang.org/x/arch v0.3.0/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
72 | golang.org/x/crypto v0.14.0 h1:wBqGXzWJW6m1XrIKlAH0Hs1JJ7+9KBwnIO8v66Q9cHc=
73 | golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4=
74 | golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM=
75 | golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE=
76 | golang.org/x/sys v0.0.0-20220704084225-05e143d24a9e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
77 | golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
78 | golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE=
79 | golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
80 | golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k=
81 | golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
82 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
83 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
84 | google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
85 | google.golang.org/protobuf v1.30.0 h1:kPPoIgf3TsEvrm0PFe15JQ+570QVxYzEvvHqChK+cng=
86 | google.golang.org/protobuf v1.30.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
87 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
88 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
89 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
90 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
91 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
92 | rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4=
93 |
--------------------------------------------------------------------------------
/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "electrumx-proxy-go/config"
5 | "electrumx-proxy-go/router"
6 | "electrumx-proxy-go/ws"
7 | "log"
8 | )
9 |
10 | func main() {
11 | config.InitConf()
12 | ws.InitWebSocket(config.Conf.ElectrumxServer)
13 | api := router.InitMasterRouter()
14 | err := api.Run(config.Conf.ServerAddress)
15 | if err != nil {
16 | log.Fatal(err)
17 | return
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/router/proxy.go:
--------------------------------------------------------------------------------
1 | package router
2 |
3 | import (
4 | "electrumx-proxy-go/common"
5 | "electrumx-proxy-go/ws"
6 | "encoding/json"
7 | "errors"
8 | "github.com/gin-gonic/gin"
9 | "github.com/gorilla/websocket"
10 | "net/http"
11 | "time"
12 | )
13 |
14 | func handleRequestToWs(method string, params []interface{}) (*common.JsonRpcResponse, error) {
15 | id := GetUniqueID()
16 | request := common.JsonRpcRequest{
17 | ID: id,
18 | Method: method,
19 | Params: params,
20 | }
21 |
22 | requestBytes, err := json.Marshal(request)
23 | if err != nil {
24 | return nil, err
25 | }
26 | responseChan := make(chan *common.JsonRpcResponse, 1)
27 | ws.CallbacksLock.Lock()
28 | ws.Callbacks[id] = responseChan
29 | ws.CallbacksLock.Unlock()
30 |
31 | if err := ws.WsTx.WriteMessage(websocket.TextMessage, requestBytes); err != nil {
32 | ws.CallbacksLock.Lock()
33 | delete(ws.Callbacks, id)
34 | ws.CallbacksLock.Unlock()
35 | return nil, err
36 | }
37 |
38 | select {
39 | case response := <-responseChan:
40 | ws.CallbacksLock.Lock()
41 | delete(ws.Callbacks, id)
42 | ws.CallbacksLock.Unlock()
43 |
44 | return response, nil
45 | case <-time.After(30 * time.Second):
46 | ws.CallbacksLock.Lock()
47 | delete(ws.Callbacks, id)
48 | ws.CallbacksLock.Unlock()
49 |
50 | return nil, errors.New("request timeout")
51 | }
52 | }
53 | func HandleProxyGet(c *gin.Context) {
54 | method := c.Param("method")
55 | queryParams := c.Request.URL.Query()
56 |
57 | var params []interface{}
58 | if paramsStr := queryParams.Get("params"); paramsStr != "" {
59 | var paramsInterface interface{}
60 | if err := json.Unmarshal([]byte(paramsStr), ¶msInterface); err != nil {
61 | c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
62 | return
63 | }
64 | params, _ = paramsInterface.([]interface{})
65 | }
66 |
67 | response, err := handleRequestToWs(method, params)
68 | if err != nil {
69 | c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
70 | return
71 | }
72 |
73 | newResponse := map[string]interface{}{
74 | "success": true,
75 | "response": response.Result,
76 | }
77 | c.JSON(http.StatusOK, newResponse)
78 | }
79 |
80 | func HandleProxyPost(c *gin.Context) {
81 | method := c.Param("method")
82 | var body struct {
83 | Params []interface{} `json:"params"`
84 | }
85 | if err := c.ShouldBindJSON(&body); err != nil {
86 | c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
87 | return
88 | }
89 |
90 | response, err := handleRequestToWs(method, body.Params)
91 | if err != nil {
92 | c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
93 | return
94 | }
95 |
96 | newResponse := map[string]interface{}{
97 | "success": true,
98 | "response": response.Result,
99 | }
100 | c.JSON(http.StatusOK, newResponse)
101 | }
102 |
--------------------------------------------------------------------------------
/router/router.go:
--------------------------------------------------------------------------------
1 | package router
2 |
3 | import (
4 | "electrumx-proxy-go/common"
5 | "electrumx-proxy-go/ws"
6 | "encoding/json"
7 | "github.com/gin-gonic/gin"
8 | "github.com/gorilla/websocket"
9 | cors "github.com/itsjamie/gin-cors"
10 | "log"
11 | "net/http"
12 | "sync/atomic"
13 | "time"
14 | )
15 |
16 | var UniqueID uint32
17 |
18 | func GetUniqueID() uint32 {
19 | return atomic.AddUint32(&UniqueID, 1)
20 | }
21 | func InitMasterRouter() *gin.Engine {
22 | r := gin.Default()
23 | gin.SetMode(gin.ReleaseMode)
24 | r.Use(cors.Middleware(cors.Config{
25 | Origins: "*",
26 | Methods: "GET, PUT, POST, DELETE",
27 | RequestHeaders: "Origin, Content-Type",
28 | MaxAge: 50 * time.Second,
29 | Credentials: false,
30 | ValidateHeaders: false,
31 | }))
32 | r.GET("/", HandlerHello)
33 | r.GET("/proxy", HandleProxy)
34 | r.GET("/proxy/health", HandleHealth)
35 | r.POST("/proxy/health", HandleHealth)
36 | r.GET("/proxy/:method", HandleProxyGet)
37 | r.POST("/proxy/:method", HandleProxyPost)
38 | return r
39 | }
40 |
41 | func HandlerHello(c *gin.Context) {
42 | c.JSON(200, gin.H{
43 | "message": "Hello from Atomical, wish you have a pleasant user experience. --Atomical Neutron Protocol",
44 | })
45 | }
46 |
47 | func HandleProxy(c *gin.Context) {
48 | info := common.Info{
49 | Note: "Atomicals Neutron ElectrumX Digital Object Proxy Online",
50 | UsageInfo: common.UsageInfo{
51 | Note: "The service offers both POST and GET requests for proxying requests to ElectrumX. To handle larger broadcast transaction payloads use the POST method instead of GET.",
52 | POST: "POST /proxy/:method with string encoded array in the field \"params\" in the request body.",
53 | GET: "GET /proxy/:method?params=[\"value1\"] with string encoded array in the query argument \"params\" in the URL.",
54 | },
55 | HealthCheck: "GET /proxy/health",
56 | Github: "https://www.atomicalneutron.com",
57 | License: "MIT",
58 | }
59 |
60 | response := common.Result{
61 | Success: true,
62 | Info: info,
63 | }
64 | c.JSON(http.StatusOK, response)
65 | }
66 |
67 | type ResponseHealth struct {
68 | Success bool `json:"success"`
69 | Health bool `json:"health,omitempty"`
70 | }
71 |
72 | func HandleHealth(c *gin.Context) {
73 | id := GetUniqueID()
74 | responseCh := make(chan *common.JsonRpcResponse)
75 | ws.CallbacksLock.Lock()
76 | ws.Callbacks[id] = responseCh
77 | ws.CallbacksLock.Unlock()
78 | request := common.JsonRpcRequest{
79 | ID: id,
80 | Method: "blockchain.atomicals.get_global",
81 | Params: []interface{}{},
82 | }
83 | requestText, err := json.Marshal(request)
84 | if err != nil {
85 | c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to marshal JSON-RPC request"})
86 | return
87 | }
88 |
89 | err = ws.WsTx.WriteMessage(websocket.TextMessage, requestText)
90 | if err != nil {
91 | c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to send message over WebSocket"})
92 | return
93 | }
94 |
95 | select {
96 | case rep := <-responseCh:
97 | ws.CallbacksLock.Lock()
98 | delete(ws.Callbacks, id)
99 | ws.CallbacksLock.Unlock()
100 | if rep.Result != nil {
101 | c.JSON(http.StatusOK, ResponseHealth{Success: true, Health: true})
102 | } else {
103 | c.JSON(http.StatusOK, ResponseHealth{Success: true, Health: false})
104 | }
105 | case <-time.After(5 * time.Second):
106 | log.Printf("<= id: %d, check health timeout, no response received after 5 seconds", id)
107 | ws.CallbacksLock.Lock()
108 | delete(ws.Callbacks, id)
109 | ws.CallbacksLock.Unlock()
110 | c.JSON(http.StatusOK, ResponseHealth{Success: true, Health: false})
111 | }
112 | }
113 |
--------------------------------------------------------------------------------
/ws/init.go:
--------------------------------------------------------------------------------
1 | package ws
2 |
3 | import (
4 | "electrumx-proxy-go/common"
5 | "electrumx-proxy-go/config"
6 | "encoding/json"
7 | "fmt"
8 | "github.com/gorilla/websocket"
9 | "log"
10 | "net/url"
11 | "sync"
12 | "time"
13 | )
14 |
15 | var Callbacks = make(map[uint32]chan *common.JsonRpcResponse)
16 |
17 | var WsTx *websocket.Conn
18 |
19 | var CallbacksLock = sync.RWMutex{}
20 |
21 | func InitWebSocket(urlStr string) {
22 | for {
23 | err := connectWebSocket(urlStr)
24 | if err != nil {
25 | log.Printf("Error connecting to WebSocket, will retry in 5 seconds: %v", err)
26 | time.Sleep(5 * time.Second)
27 | continue
28 | }
29 | break
30 | }
31 | }
32 | func connectWebSocket(urlStr string) error {
33 | u, err := url.Parse(urlStr)
34 | if err != nil {
35 | return fmt.Errorf("error parsing URL: %v", err)
36 | }
37 | dialer := websocket.DefaultDialer
38 | conn, _, err := dialer.Dial(u.String(), nil)
39 | if err != nil {
40 | return fmt.Errorf("error connecting to WebSocket: %v", err)
41 | }
42 | WsTx = conn
43 | go readMessages(conn)
44 | return nil
45 | }
46 | func readMessages(conn *websocket.Conn) {
47 | defer conn.Close()
48 | for {
49 | _, message, err := conn.ReadMessage()
50 | if err != nil {
51 | if websocket.IsUnexpectedCloseError(err, websocket.CloseGoingAway, websocket.CloseAbnormalClosure) {
52 | log.Printf("WebSocket closed unexpectedly: %v", err)
53 | InitWebSocket(config.Conf.ElectrumxServer)
54 | } else {
55 | log.Printf("Error reading websocket message: %v", err)
56 | }
57 | break
58 | }
59 | var response common.JsonRpcResponse
60 | if err := json.Unmarshal(message, &response); err != nil {
61 | log.Printf("Error unmarshalling websocket message: %v", err)
62 | continue
63 | }
64 | CallbacksLock.Lock()
65 | if ch, ok := Callbacks[response.ID]; ok {
66 | ch <- &response
67 | delete(Callbacks, response.ID)
68 | }
69 | CallbacksLock.Unlock()
70 | }
71 | }
72 |
--------------------------------------------------------------------------------