Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
79 changes: 79 additions & 0 deletions .github/workflows/rust.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
name: Rust

on:
push:
branches: [main]
pull_request:
workflow_dispatch:

permissions:
contents: read

jobs:
checks:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4

- name: Install Rust
uses: dtolnay/rust-toolchain@stable
with:
components: rustfmt, clippy
Comment thread
coderabbitai[bot] marked this conversation as resolved.

- name: Cache Rust dependencies
uses: Swatinem/rust-cache@v2

- name: Install protobuf compiler
run: sudo apt-get update && sudo apt-get install -y protobuf-compiler

- name: cargo fmt
run: cargo fmt --check

- name: cargo clippy
run: cargo clippy --all-features -- -D warnings

- name: cargo check default
run: cargo check

- name: cargo check no default features
run: cargo check --no-default-features

- name: cargo check native
run: cargo check --no-default-features --features native

- name: cargo check wallet
run: cargo check --no-default-features --features wallet

- name: cargo check evm
run: cargo check --no-default-features --features evm

- name: cargo check grpc
run: cargo check --no-default-features --features grpc

- name: cargo check bft
run: cargo check --no-default-features --features bft

- name: cargo check native,wallet
run: cargo check --no-default-features --features native,wallet

- name: cargo check evm,grpc
run: cargo check --no-default-features --features evm,grpc

- name: cargo check all features
run: cargo check --all-features

- name: cargo check examples all features
run: cargo check --examples --all-features

- name: cargo test wallet
run: cargo test --no-default-features --features wallet

- name: cargo test evm
run: cargo test --no-default-features --features evm

- name: cargo test all features
run: cargo test --all-features
Comment thread
coderabbitai[bot] marked this conversation as resolved.

- name: cargo doc all features
run: cargo doc --all-features --no-deps
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Comment on lines +78 to +79
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick | 🔵 Trivial | ⚡ Quick win

Consider enforcing documentation warnings.

The cargo doc command doesn't include -D warnings, so documentation issues (broken links, invalid syntax, missing docs on public items) will not fail the CI build. For consistency with the clippy step (line 34) and to maintain doc quality, consider treating doc warnings as errors.

📚 Suggested enhancement
       - name: cargo doc all features
-        run: cargo doc --all-features --no-deps
+        run: cargo doc --all-features --no-deps -- -D warnings
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
- name: cargo doc all features
run: cargo doc --all-features --no-deps
- name: cargo doc all features
run: cargo doc --all-features --no-deps -- -D warnings
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/rust.yml around lines 78 - 79, The CI step running the
cargo doc command should treat documentation warnings as errors; update the step
that runs "cargo doc --all-features --no-deps" to include "-D warnings" (i.e.
"cargo doc -D warnings --all-features --no-deps") so doc warnings fail the
build, matching the clippy enforcement used elsewhere.

2 changes: 1 addition & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

30 changes: 25 additions & 5 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "sentrix-chain"
version = "0.1.0-alpha.0"
version = "0.1.0-alpha.1"
edition = "2021"
license = "MIT"
description = "Official Rust SDK for Sentrix Chain — typed clients for native REST, EVM, gRPC, and secp256k1 wallet/signing."
Expand All @@ -19,17 +19,17 @@ default = ["native"]
network = []
native = ["dep:reqwest", "dep:tokio", "dep:serde", "dep:serde_json", "dep:thiserror"]
wallet = ["dep:secp256k1", "dep:sha2", "dep:tiny-keccak", "dep:hex", "dep:serde", "dep:serde_json", "dep:thiserror", "native"]
# evm and grpc are scaffolded behind features but not implemented yet —
# alpha.0 ships the network spec + native client foundation.
evm = ["dep:alloy", "dep:url", "network"]
# EVM/gRPC/BFT are alpha surfaces. Keep them opt-in because they pull
# heavier networking stacks than the default native REST client.
evm = ["dep:alloy", "dep:url", "dep:thiserror", "network"]
grpc = ["dep:tonic", "dep:sentrix-proto", "dep:tokio", "dep:futures", "dep:hex", "dep:thiserror", "network"]
bft = ["dep:tokio", "dep:tokio-tungstenite", "dep:futures", "dep:serde", "dep:serde_json", "dep:thiserror", "network"]

[dependencies]
# Network spec — always available, no feature gate.
# Native REST stack
reqwest = { version = "0.12", optional = true, default-features = false, features = ["json", "rustls-tls"] }
tokio = { version = "1", optional = true, features = ["macros", "rt-multi-thread"] }
tokio = { version = "1", optional = true, features = ["macros", "rt-multi-thread", "sync", "time"] }
serde = { version = "1", optional = true, features = ["derive"] }
serde_json = { version = "1", optional = true }
thiserror = { version = "2", optional = true }
Expand All @@ -56,5 +56,25 @@ tokio-tungstenite = { version = "0.29", optional = true, features = ["rustls-tls
[dev-dependencies]
tokio = { version = "1", features = ["macros", "rt-multi-thread"] }

[[example]]
name = "chain_info"
required-features = ["native"]

[[example]]
name = "evm_block_number"
required-features = ["evm"]

[[example]]
name = "grpc_latest_block"
required-features = ["grpc"]

[[example]]
name = "websocket_subscribe"
required-features = ["bft"]

[[example]]
name = "sign_native_transfer"
required-features = ["wallet"]

[lints.rust]
unsafe_code = "forbid"
61 changes: 44 additions & 17 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,35 +6,66 @@

Official Rust SDK for **Sentrix Chain** (chain ID `7119` mainnet, `7120` testnet).

Mirror of [`@sentrix/chain`](https://github.com/Sentriscloud/sdk-ts) on the TypeScript side — same network spec, same canonical addresses, same tx signing semantics. Use this crate for Rust services (validators, indexers, bridges, monitoring agents) that need to talk to Sentrix without spinning up a Node process.
Mirror of [`@sentrix/chain`](https://github.com/Sentriscloud/sdk-ts) on the TypeScript side: same network spec, same canonical addresses, same tx signing semantics. Use this crate for Rust services, indexers, bridges, and monitoring agents that need to talk to Sentrix without spinning up a Node process.

The `0.1.x` line is alpha. APIs are intended for integration testing and early developer use, but may still change before 1.0.

## Surface

| Module | Feature flag | Status | What it does |
|---|---|---|---|
| `network` | _always on_ | ✅ stable | Chain spec types + `MAINNET_SPEC` / `TESTNET_SPEC` constants. Single source of truth for chain ID, RPC / REST / WS / gRPC URLs, explorer, faucet. |
| `native` | `native` (default) | alpha | Typed REST client over `reqwest` for `/chain/info`, `/staking/validators`, `/accounts/<addr>/nonce`, `POST /transactions`. |
| `wallet` | `wallet` | alpha | secp256k1 keypair + Ethereum-style address derivation + native tx signing. |
| `evm` | `evm` | alpha | alloy-based EVM JSON-RPC client (Provider factory; reach for alloy directly for signing / contract bindings / event filters). |
| `grpc` | `grpc` | alpha | tonic client over `sentrix.v1.Sentrix` — getBlock / getBalance / getValidatorSet / getSupply / getMempool / streamEvents. Generated proto types come from the published [`sentrix-proto`](https://crates.io/crates/sentrix-proto) crate (single source of truth, shared with the chain server). Consumers building from source need `protoc` installed (`apt install protobuf-compiler` or equivalent). |
| `bft` | `bft` | alpha | WebSocket subscription manager for the 9 channels (newHeads, logs, sentrix_finalized, sentrix_jail, …) over tokio-tungstenite. Multiplexes everything on one socket; pings every 30 s + force-reconnects on 90 s stale; auto re-subscribes after reconnect. Mirror of `@sentrix/chain/bft`. |
| `network` | _always on_ | alpha, low churn | Chain spec types + `MAINNET_SPEC` / `TESTNET_SPEC` constants. Single source of truth for chain ID, RPC / REST / WS / gRPC URLs, explorer, faucet. |
| `native` | `native` (default) | alpha | Typed REST client over `reqwest` for `/chain/info`, `/staking/validators`, `/accounts/<addr>/nonce`, `POST /transactions`. |
| `wallet` | `wallet` | alpha | secp256k1 keypair + Ethereum-style address derivation + native tx signing. Applications remain responsible for secret storage. |
| `evm` | `evm` | alpha | alloy-based EVM HTTP provider factory using Sentrix mainnet/testnet RPC config. Reach for alloy directly for signing / contract bindings / event filters. |
| `grpc` | `grpc` | alpha | tonic client over `sentrix.v1.Sentrix` — getBlock / getBalance / getValidatorSet / getSupply / getMempool / streamEvents. Proto types come from the published [`sentrix-proto`](https://crates.io/crates/sentrix-proto) crate. Consumers building from source may need `protoc` installed (`apt install protobuf-compiler` or equivalent). |
| `bft` | `bft` | alpha | WebSocket subscription manager for EVM and Sentrix-specific subscription channels over tokio-tungstenite. Runtime behavior depends on the configured WS endpoint. |

Trim what you actually use:

```toml
[dependencies]
sentrix-chain = { version = "0.1.0-alpha.0", default-features = false, features = ["native", "wallet"] }
sentrix-chain = { version = "0.1.0-alpha.1", default-features = false, features = ["native", "wallet"] }
```

## Quick start

## Unit warning

Native REST/native ledger amounts use **sentri**, the 8-decimal SRX
unit: `1 SRX = 100_000_000 sentri`.

EVM JSON-RPC uses **wei-style 18-decimal units** for Ethereum tooling
compatibility. Do not mix native and EVM amounts directly; convert
explicitly at API boundaries.

## Examples

Run examples against mainnet by default, or set `SENTRIX_NETWORK=testnet`.

```bash
cargo run --example chain_info
cargo run --no-default-features --features evm --example evm_block_number
cargo run --no-default-features --features grpc --example grpc_latest_block
cargo run --no-default-features --features bft --example websocket_subscribe
cargo run --no-default-features --features wallet --example sign_native_transfer
```

`sign_native_transfer` reads secrets from the environment and only
prints the signed transaction envelope; it does not broadcast.

A native balance example is not included in this alpha because
`NativeClient` does not yet expose a documented native balance endpoint.
Use the `grpc` surface for balance reads when that endpoint is available
for your deployment.

### Read chain stats

```rust
use sentrix_chain::{Network, NativeClient};

#[tokio::main]
async fn main() -> anyhow::Result<()> {
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let client = NativeClient::new(Network::Mainnet);
let info = client.chain_info().await?;
println!(
Expand All @@ -55,7 +86,7 @@ async fn main() -> anyhow::Result<()> {
use sentrix_chain::{Network, NativeClient, SentrixWallet};

#[tokio::main]
async fn main() -> anyhow::Result<()> {
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let w = SentrixWallet::from_private_key_hex(&std::env::var("PRIVATE_KEY")?)?;
let client = NativeClient::new(Network::Mainnet);

Expand Down Expand Up @@ -146,11 +177,11 @@ println!("{}: {}", MAINNET.name, MAINNET.rpc_url);

## Status

`v0.1.0-alpha.0` on crates.io. All six doors (`network`, `native`, `wallet`, `evm`, `grpc`, `bft`) compile and have working client paths against the public RPC + gRPC endpoints. Surface is alpha expect breaking changes before 1.0 stabilises.
`v0.1.0-alpha.1` is the release candidate prepared for feature-flag hardening, examples, docs, and publish readiness. All six surfaces (`network`, `native`, `wallet`, `evm`, `grpc`, `bft`) are intended to compile behind their feature flags. Live endpoint compatibility is still alpha, so expect breaking changes before 1.0 stabilises.

## Roadmap

All six doors landed for v0.1.0-alpha.0:
All six surfaces are present in v0.1.0-alpha.1:

- [x] `network` — chain spec, mainnet + testnet constants
- [x] `native` — REST read + tx broadcast
Expand All @@ -159,11 +190,7 @@ All six doors landed for v0.1.0-alpha.0:
- [x] `grpc` — tonic client over `sentrix.v1.Sentrix` (consumes [`sentrix-proto`](https://crates.io/crates/sentrix-proto) for the schema)
- [x] `bft` — WebSocket subscription manager (multiplex + keepalive ping + auto-reconnect, port of `@sentrix/chain/bft`)

Next: surface stabilisation toward 1.0 — naming review, error-type cleanup, optional `EvmClient` wrapper around alloy.

## Decimals

Sentrix's underlying ledger is **8-decimal** native (1 SRX = 100,000,000 sentri). The EVM tooling sees an **18-decimal** view because `eth_getBalance` returns wei-scaled values for compatibility with MetaMask / ethers / viem. When you use `NativeClient::balance(...)` you get sentri (8-decimal); when the planned `EvmClient` ships you'll get wei (18-decimal). Don't mix the units across surfaces.
Next: surface stabilisation toward 1.0 — naming review, error-type cleanup, native balance support if the REST endpoint is documented, and optional `EvmClient` wrappers around alloy.

## License

Expand Down
25 changes: 25 additions & 0 deletions examples/chain_info.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
use sentrix_chain::{NativeClient, Network};

fn network_from_env() -> Network {
match std::env::var("SENTRIX_NETWORK").as_deref() {
Ok("testnet") => Network::Testnet,
_ => Network::Mainnet,
}
}

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let network = network_from_env();
let client = NativeClient::new(network);
let info = client.chain_info().await?;

println!("network={}", client.spec().name);
println!("chain_id={}", client.spec().chain_id);
println!("height={}", info.height);
println!("active_validators={}", info.active_validators);
println!("mempool_size={}", info.mempool_size);
println!("total_minted_srx={}", info.total_minted_srx);
println!("total_burned_srx={}", info.total_burned_srx);

Ok(())
}
20 changes: 20 additions & 0 deletions examples/evm_block_number.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
use alloy::providers::Provider;
use sentrix_chain::{evm, Network};

fn network_from_env() -> Network {
match std::env::var("SENTRIX_NETWORK").as_deref() {
Ok("testnet") => Network::Testnet,
_ => Network::Mainnet,
}
}

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let network = network_from_env();
let provider = evm::http_provider(network)?;
let block_number = provider.get_block_number().await?;

println!("latest_evm_block={block_number}");

Ok(())
}
20 changes: 20 additions & 0 deletions examples/grpc_latest_block.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
use sentrix_chain::{grpc::SentrixGrpcClient, Network};

fn network_from_env() -> Network {
match std::env::var("SENTRIX_NETWORK").as_deref() {
Ok("testnet") => Network::Testnet,
_ => Network::Mainnet,
}
}

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let network = network_from_env();
let mut client = SentrixGrpcClient::connect(network).await?;
let block = client.get_latest_block().await?;

println!("latest_block_height={}", block.index);
println!("transactions={}", block.transactions.len());

Ok(())
}
54 changes: 54 additions & 0 deletions examples/sign_native_transfer.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
use sentrix_chain::{NativeClient, Network, SentrixWallet};

fn network_from_env() -> Network {
match std::env::var("SENTRIX_NETWORK").as_deref() {
Ok("testnet") => Network::Testnet,
_ => Network::Mainnet,
}
}

fn parse_u64_env(name: &str, default: u64) -> Result<u64, Box<dyn std::error::Error>> {
match std::env::var(name) {
Ok(value) => Ok(value.parse()?),
Err(_) => Ok(default),
}
}

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let private_key = match std::env::var("SENTRIX_PRIVATE_KEY") {
Ok(value) => value,
Err(_) => {
println!("set SENTRIX_PRIVATE_KEY and SENTRIX_TO to build a signed transfer");
return Ok(());
}
};
let to = match std::env::var("SENTRIX_TO") {
Ok(value) => value,
Err(_) => {
println!("set SENTRIX_TO to the 0x recipient address");
return Ok(());
}
};

let network = network_from_env();
let client = NativeClient::new(network);
let wallet = SentrixWallet::from_private_key_hex(&private_key)?;
let nonce = match std::env::var("SENTRIX_NONCE") {
Ok(value) => value.parse()?,
Err(_) => client.next_nonce(&wallet.address).await?,
};

let tx = wallet.build_and_sign_transfer(
&to,
parse_u64_env("SENTRIX_AMOUNT_SENTRI", 100_000_000)?,
parse_u64_env("SENTRIX_FEE_SENTRI", 10_000)?,
nonce,
client.spec().chain_id,
)?;

println!("{}", serde_json::to_string_pretty(&tx)?);
println!("not broadcast; submit with NativeClient::broadcast after review");

Ok(())
}
Loading
Loading