├── .editorconfig ├── .github └── workflows │ └── build.yaml ├── .gitignore ├── .prettierrc ├── LICENSE ├── README.md ├── docs ├── add_secret.png ├── add_variable.png ├── confirm.png ├── encrypt.png └── rpc_proxy.png ├── package.json ├── src └── index.ts ├── tsconfig.json └── wrangler.toml /.editorconfig: -------------------------------------------------------------------------------- 1 | # http://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | indent_style = tab 6 | tab_width = 2 7 | end_of_line = lf 8 | charset = utf-8 9 | trim_trailing_whitespace = true 10 | insert_final_newline = true 11 | 12 | [*.yml] 13 | indent_style = space 14 | -------------------------------------------------------------------------------- /.github/workflows/build.yaml: -------------------------------------------------------------------------------- 1 | name: Build 2 | on: 3 | push: 4 | pull_request: 5 | repository_dispatch: 6 | jobs: 7 | deploy: 8 | runs-on: ubuntu-latest 9 | timeout-minutes: 60 10 | steps: 11 | - uses: actions/checkout@v3 12 | - name: Setup Node.js 13 | uses: actions/setup-node@v4 14 | with: 15 | node-version: '20' 16 | - name: Install dependencies 17 | run: npm install 18 | - name: Publish 19 | uses: cloudflare/wrangler-action@3.0.1 20 | with: 21 | apiToken: ${{ secrets.CF_API_TOKEN }} 22 | env: 23 | CF_ACCOUNT_ID: ${{ secrets.CF_ACCOUNT_ID }} 24 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | /node_modules 3 | *-lock.* 4 | *.lock 5 | *.log 6 | 7 | /dist 8 | 9 | test.js 10 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "semi": true, 3 | "printWidth": 100, 4 | "singleQuote": true, 5 | "bracketSpacing": true, 6 | "insertPragma": false, 7 | "requirePragma": false, 8 | "jsxSingleQuote": false, 9 | "bracketSameLine": false, 10 | "embeddedLanguageFormatting": "auto", 11 | "htmlWhitespaceSensitivity": "css", 12 | "vueIndentScriptAndStyle": true, 13 | "quoteProps": "consistent", 14 | "proseWrap": "preserve", 15 | "trailingComma": "es5", 16 | "arrowParens": "avoid", 17 | "useTabs": true, 18 | "tabWidth": 2 19 | } 20 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2023 Helius Labs 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 4 | 5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 6 | 7 | THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Helius RPC Proxy 2 | 3 | [![RPC Proxy](docs/rpc_proxy.png)](https://helius.xyz) 4 | 5 | This repo hosts a one-click-deploy Cloudflare worker that proxies RPC requests to Helius. The proxy will allow you to keep your API key 6 | hidden from public requests made by clients. You will need both a [Helius](https://helius.xyz) account and a [Cloudflare](https://cloudflare.com) account to deploy this. Helius offers 100k credits for free each month, and Cloudflare workers can execute 100k invocations each day for free. Most projects can easily get started within these free tiers. 7 | 8 | Both standard JSON RPC and Websockets are supported! 9 | 10 | [Video Walkthrough](https://www.loom.com/share/a7add579f1c349d2a4bcab96ee04c47e) 11 | 12 | # Setup 13 | ### Step 1 14 | 15 | Press the button below to deploy this to your own Cloudflare account: 16 | 17 | [![Deploy to Cloudflare Workers](https://deploy.workers.cloudflare.com/button)](https://deploy.workers.cloudflare.com/?url=https://github.com/helius-labs/helius-rpc-proxy) 18 | 19 | ### Step 2 20 | 21 | Navigate to your newly deployed worker, and click "Settings" and then "Variables": 22 | 23 | ![Variables](docs/add_variable.png) 24 | 25 | ### Step 3 26 | Add a new variable with the key name `HELIUS_API_KEY` and your Helius API key as the value: 27 | 28 | ![Add Secret](docs/add_secret.png) 29 | 30 | > NOTE: We recommend selecting "Encrypt". This will hide your key from the UI and API responses, and redact them from logs. 31 | 32 | ![Encrypt](docs/encrypt.png) 33 | 34 | ### Step 4 35 | Refresh the page and confirm that your key is now saved and encrypted: 36 | 37 | ![Confirm](docs/confirm.png) 38 | 39 | You can now use your worker URL as an the RPC endpoint in all SDK and client side configurations without your API key leaking! 40 | # Additional Security Steps 41 | This implementation is intentionally left in a less-than-ideal security state to facilitate easy deployment by anyone. If you would like to 42 | lock down your RPC proxy further, consider the following steps after you have successfully deployed the worker: 43 | 44 | 45 | * Update the `Access-Control-Allow-Origin` header by adding a new variable with the key name `CORS_ALLOW_ORIGIN` to contain the host that your requests are coming from (usually your client application). For example, if you wanted to allow requests from `https://example.com`, you would change the header to `https://example.com`. To support multiple domains, set `CORS_ALLOW_ORIGIN` to a comma separated list of domains (e.g. `https://example.com,https://beta.example.com`). 46 | * [Cloudflare Web Application Firewall (WAF)](https://www.cloudflare.com/lp/ppc/waf-x/) - You can configure the WAF to inspect requests and allow/deny based on your own business logic. 47 | * Modify the IP address allow list in Helius for your API key to only accept connections from the Cloudflare ranges (https://cloudflare.com/ips-v4). 48 | -------------------------------------------------------------------------------- /docs/add_secret.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/helius-labs/helius-rpc-proxy/d99f25eb1ed8cd6908a6945957c25a55cf88d686/docs/add_secret.png -------------------------------------------------------------------------------- /docs/add_variable.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/helius-labs/helius-rpc-proxy/d99f25eb1ed8cd6908a6945957c25a55cf88d686/docs/add_variable.png -------------------------------------------------------------------------------- /docs/confirm.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/helius-labs/helius-rpc-proxy/d99f25eb1ed8cd6908a6945957c25a55cf88d686/docs/confirm.png -------------------------------------------------------------------------------- /docs/encrypt.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/helius-labs/helius-rpc-proxy/d99f25eb1ed8cd6908a6945957c25a55cf88d686/docs/encrypt.png -------------------------------------------------------------------------------- /docs/rpc_proxy.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/helius-labs/helius-rpc-proxy/d99f25eb1ed8cd6908a6945957c25a55cf88d686/docs/rpc_proxy.png -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "private": true, 3 | "version": "0.0.0", 4 | "engines": { 5 | "node": ">=20.0.0" 6 | }, 7 | "scripts": { 8 | "deploy": "wrangler deploy src/index.ts", 9 | "dev": "wrangler dev src/index.ts --local", 10 | "test": "vitest", 11 | "start-stackblitz": "WRANGLER_SEND_METRICS=false wrangler dev src/index.ts --local" 12 | }, 13 | "devDependencies": { 14 | "@cloudflare/workers-types": "^4.20240208.0", 15 | "vitest": "^1.3.1", 16 | "wrangler": "^4.2.0" 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | interface Env { 2 | CORS_ALLOW_ORIGIN: string; 3 | HELIUS_API_KEY: string; 4 | } 5 | 6 | export default { 7 | async fetch(request: Request, env: Env) { 8 | 9 | // If the request is an OPTIONS request, return a 200 response with permissive CORS headers 10 | // This is required for the Helius RPC Proxy to work from the browser and arbitrary origins 11 | // If you wish to restrict the origins that can access your Helius RPC Proxy, you can do so by 12 | // changing the `*` in the `Access-Control-Allow-Origin` header to a specific origin. 13 | // For example, if you wanted to allow requests from `https://example.com`, you would change the 14 | // header to `https://example.com`. Multiple domains are supported by verifying that the request 15 | // originated from one of the domains in the `CORS_ALLOW_ORIGIN` environment variable. 16 | const supportedDomains = env.CORS_ALLOW_ORIGIN ? env.CORS_ALLOW_ORIGIN.split(',') : undefined; 17 | const corsHeaders: Record = { 18 | "Access-Control-Allow-Methods": "GET, HEAD, POST, PUT, OPTIONS", 19 | "Access-Control-Allow-Headers": "*", 20 | } 21 | if (supportedDomains) { 22 | const origin = request.headers.get('Origin') 23 | if (origin && supportedDomains.includes(origin)) { 24 | corsHeaders['Access-Control-Allow-Origin'] = origin 25 | } 26 | } else { 27 | corsHeaders['Access-Control-Allow-Origin'] = '*' 28 | } 29 | 30 | if (request.method === "OPTIONS") { 31 | return new Response(null, { 32 | status: 200, 33 | headers: corsHeaders, 34 | }); 35 | } 36 | 37 | const upgradeHeader = request.headers.get('Upgrade') 38 | 39 | if (upgradeHeader || upgradeHeader === 'websocket') { 40 | return await fetch(`https://mainnet.helius-rpc.com/?api-key=${env.HELIUS_API_KEY}`, request) 41 | } 42 | 43 | 44 | const { pathname, search } = new URL(request.url) 45 | const payload = await request.text(); 46 | const proxyRequest = new Request(`https://${pathname === '/' ? 'mainnet.helius-rpc.com' : 'api.helius.xyz'}${pathname}?api-key=${env.HELIUS_API_KEY}${search ? `&${search.slice(1)}` : ''}`, { 47 | method: request.method, 48 | body: payload || null, 49 | headers: { 50 | 'Content-Type': 'application/json', 51 | 'X-Helius-Cloudflare-Proxy': 'true', 52 | } 53 | }); 54 | 55 | return await fetch(proxyRequest).then(res => { 56 | return new Response(res.body, { 57 | status: res.status, 58 | headers: corsHeaders 59 | }); 60 | }); 61 | }, 62 | }; 63 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "noEmit": true, 4 | "module": "esnext", 5 | "target": "es2020", 6 | "lib": ["es2020"], 7 | "strict": true, 8 | "alwaysStrict": true, 9 | "preserveConstEnums": true, 10 | "moduleResolution": "node", 11 | "sourceMap": true, 12 | "types": ["@cloudflare/workers-types"] 13 | }, 14 | "include": ["src"], 15 | "exclude": ["node_modules", "dist"] 16 | } 17 | -------------------------------------------------------------------------------- /wrangler.toml: -------------------------------------------------------------------------------- 1 | name = "rpc-proxy" # todo 2 | main = "./src/index.ts" 3 | compatibility_date = "2022-05-03" 4 | --------------------------------------------------------------------------------