Hello Cloudflare Github
Here's how we release
Rust
as Wasm
to Cloudflare
via Github
integration.
Setup (once)
rustup target add wasm32-unknown-unknown
cargo install cargo-generate
Setup new project (optional)
# From template
cargo generate cloudflare/workers-rs
# Or
npx wrangler init
Or from existing source
git clone https://github.com/gist-rs/hello-world-cloudflare
cd hello-world-cloudflare
Dev local
npx wrangler dev
Deploy local
npx wrangler login
npx wrangler deploy
Deploy command (Github
Integration)
💡 Set this in
Cloudflare
build setting via Cloudflare Dashboard ref DOCS
npx wrangler deploy -e production
Config
name = "hello-world-cloudflare"
main = "build/worker/shim.mjs"
compatibility_date = "2025-05-10"
# Default build command (will be used by 'wrangler dev' and for 'dev' environment)
[build]
command = "cargo install -q worker-build && worker-build --release"
# Configuration for the 'production' environment
[env.production]
# Production-specific build command
[env.production.build]
command = "curl https://sh.rustup.rs -sSf | sh -s -- -y && . $HOME/.cargo/env && cargo install -q worker-build && worker-build --release"
# You can still keep an [env.dev] section if you have other dev-specific settings
# that are not build-related, or if you want to be explicit.
[env.dev]
# For example, if you wanted dev to have a different compatibility date or main entry point.
# If [env.dev.build] is not specified, it will inherit from the top-level [build].
[package]
name = "hello-world-cloudflare"
version = "0.1.0"
edition = "2021"
authors = [ "katopz <[email protected]>" ]
[package.metadata.release]
release = false
# https://github.com/rustwasm/wasm-pack/issues/1247
[package.metadata.wasm-pack.profile.release]
wasm-opt = false
[lib]
crate-type = ["cdylib"]
[dependencies]
worker = { version="0.5.0", features=['http'] }
worker-macros = { version="0.5.0", features=['http'] }
tower-service = "0.3.2"
console_error_panic_hook = { version = "0.1.1" }
serde = "1.0.219"
reqwest = { version = "0.12", features = ["json"] }
currency_rs = "1.3.0"
rand = "0.8"
anyhow = "1.0"
getrandom = { version="0.2", features = ["js"] }
serde_json = "1.0.140"
Code
mod solana; use currency_rs::CurrencyOpts; use rand::Rng; use serde::Serialize; use worker::*; #[derive(Serialize)] struct BalanceResponse { wallet_address: String, balance: String, } async fn handle_balance_request(req: Request, _ctx: RouteContext<()>) -> Result<Response> { // Extract wallet_address from query parameters let url = req.url()?; let mut wallet_address_opt: Option<String> = None; for (key, value) in url.query_pairs() { if key == "wallet_address" { wallet_address_opt = Some(value.into_owned()); break; } } match wallet_address_opt { Some(wallet_address) => { let options = solana::GetBalanceOptions { rpc_url: "https://api.mainnet-beta.solana.com", id: rand::thread_rng().gen_range(0u32..u32::MAX), currency_opts: Some( CurrencyOpts::new() .set_precision(2) .set_symbol("") .set_separator(",") .set_decimal("."), ), }; // solana::get_balance returns anyhow::Result<solana::UiBalance> // We need to map this to worker::Result<worker::Response> match solana::get_balance(wallet_address.clone(), options).await { Ok(balance_info) => { let response_data = BalanceResponse { wallet_address, balance: balance_info.ui_lamports, }; Response::from_json(&response_data) } Err(e) => { console_error!( "Error fetching balance from Solana for wallet {}: {}", wallet_address, e.to_string() ); // Return a user-friendly error response Response::error(format!("Failed to get balance: {}", e), 500) } } } None => Response::ok( "Please provide a wallet_address query parameter, e.g., /?wallet_address=YOUR_ADDRESS", ), } } #[event(start)] pub fn start() { console_error_panic_hook::set_once(); } #[event(fetch)] pub async fn main(req: Request, env: Env, _ctx: Context) -> Result<Response> { let router = Router::new(); router .get_async("/", handle_balance_request) .run(req, env) .await }
use anyhow::{anyhow, Result}; use currency_rs::{Currency, CurrencyOpts}; use serde::{Deserialize, Serialize}; use worker::{console_error, console_log}; pub struct GetBalanceOptions { pub rpc_url: &'static str, pub id: u32, pub currency_opts: Option<CurrencyOpts>, } #[derive(Debug, Serialize, Deserialize)] pub struct RpcPayload { pub jsonrpc: String, pub id: u32, pub method: String, pub params: Vec<String>, } #[derive(Debug, Serialize, Deserialize)] pub struct GetBalanceResponse { pub jsonrpc: String, pub id: u32, pub result: GetBalanceResponseResult, } #[derive(Debug, Serialize, Deserialize)] pub struct GetBalanceResponseResult { pub value: u64, } #[derive(Debug)] #[allow(dead_code)] pub struct UiBalance { pub lamports: f64, pub ui_lamports: String, } pub async fn get_balance(pubkey: String, options: GetBalanceOptions) -> Result<UiBalance> { let client = reqwest::Client::new(); let json_payload = RpcPayload { jsonrpc: "2.0".to_owned(), id: options.id, method: "getBalance".to_owned(), params: vec![pubkey], }; let http_response = client .post(options.rpc_url) .json(&json_payload) .send() .await?; if !http_response.status().is_success() { let status = http_response.status(); let error_text = http_response .text() .await .unwrap_or_else(|e| format!("Failed to read error body: {}", e)); console_error!( "RPC request to {} failed with status: {} and body: {}", options.rpc_url, status, error_text ); return Err(anyhow!( "RPC request failed with status: {} and body: {}", status, error_text )); } let response_text = http_response.text().await?; console_log!( "Raw RPC response from {}: {}", options.rpc_url, response_text ); let balance_response: GetBalanceResponse = serde_json::from_str(&response_text).map_err(|e| { console_error!( "Failed to deserialize RPC response: {}. Response text from {} was: {}", e, options.rpc_url, response_text ); anyhow!( "Error decoding RPC response body: {}. Raw response from {} was: {}", e, options.rpc_url, response_text ) })?; let lamports = balance_response.result.value as f64 / 10u64.pow(9) as f64; let ui_lamports = Currency::new_float(lamports, options.currency_opts).format(); Ok(UiBalance { lamports, ui_lamports, }) }