├── .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 | Screenshot 2024-01-21 at 9 41 28 PM 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 | Screenshot 2024-01-21 at 9 41 44 PM 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 | --------------------------------------------------------------------------------