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,
    })
}