Skip to content

feat: algokit_utils (Rust impl only) #166

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 55 commits into from
Jun 25, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
55 commits
Select commit Hold shift + click to select a range
8b891fe
wip: utils crate with composer
joe-p Jun 3, 2025
37608d4
wip: working utils py (with some modifications)
joe-p Jun 3, 2025
5552911
wip: working utils ts
joe-p Jun 3, 2025
326ed8e
wip: get uniffi compiling again
joe-p Jun 3, 2025
a7f3776
wip: use Mutex for WASM, toString, and transactions getter
joe-p Jun 4, 2025
5f1a167
wip: valueOf and proper toString via JSON.stringify
joe-p Jun 4, 2025
bbe4e6e
wip: toJSON
joe-p Jun 4, 2025
84f8e5e
wip: test error
joe-p Jun 4, 2025
4fca1ae
wip: fetch
joe-p Jun 4, 2025
ba89749
wip: blocking HTTPClient
joe-p Jun 6, 2025
6b30bc6
async http client
joe-p Jun 6, 2025
726c301
wip: AlgodClient
joe-p Jun 6, 2025
8f261ed
wip: so close
joe-p Jun 6, 2025
2cf84d9
wip: AlgodClient class and async extern, still same error
joe-p Jun 6, 2025
00b784e
wip: working
joe-p Jun 6, 2025
44e9f34
wip: split out wasm mod
joe-p Jun 6, 2025
2f5eb2e
wip: almost working, just need to split out httpclient trait
joe-p Jun 6, 2025
44b9bf4
wip: split out http trait
joe-p Jun 7, 2025
07a666f
wip: working utils py without http test
joe-p Jun 7, 2025
84e2f01
wip: passing pytest w/ http call!
joe-p Jun 7, 2025
249d275
wip: working TS!
joe-p Jun 7, 2025
e011f45
wip: unified wasm and uniffi
joe-p Jun 7, 2025
7ffb4a9
wip: split out wasm file
joe-p Jun 7, 2025
874a6b4
wip: move wasm externs into http crate
joe-p Jun 7, 2025
b900e3d
wip: split out unified mutex
joe-p Jun 7, 2025
bff57fe
wip: rename FfiMutex
joe-p Jun 7, 2025
a1c1f53
wip: HTTP -> Http
joe-p Jun 7, 2025
b7a6641
wip: add some comments
joe-p Jun 7, 2025
2b2eba2
wip: add default client to http crate
joe-p Jun 7, 2025
aaff444
change main to node.cjs
joe-p Jun 8, 2025
fc8f7bc
wip: add_payment in rust utils
joe-p Jun 17, 2025
cdf4c91
wip: algod_api crate
joe-p Jun 17, 2025
21d516a
wip: proper transactions/params deserialization
joe-p Jun 17, 2025
13a69c7
wip: build() function
joe-p Jun 17, 2025
5bd2a2d
Merge branch 'main' into spike/utils_crate
joe-p Jun 17, 2025
d5214d8
wip: assign group
joe-p Jun 17, 2025
4ee2034
wip: return an already build group
joe-p Jun 17, 2025
a17eb49
wip: gather signatures
joe-p Jun 17, 2025
0c40e9c
wip: use traits for signer/signer getter
joe-p Jun 18, 2025
10a7aa6
wip: rm utils_ffi
joe-p Jun 20, 2025
c97d362
wip: fix __init__.py
joe-p Jun 20, 2025
4e70e67
wip: rm unused variables
joe-p Jun 20, 2025
7e3b7fb
wip: fix http trait wasm feat
joe-p Jun 20, 2025
b1da130
wip: rm utils packages
joe-p Jun 20, 2025
d4c330a
wip: make signer traits async
joe-p Jun 20, 2025
fcfa5b5
chore: fix typo
joe-p Jun 25, 2025
c9b26d9
Merge branch 'main' into feat/rust_utils
joe-p Jun 25, 2025
9d4e137
feat(ffi_mutex): set ffi_uniffi as the default trait
joe-p Jun 25, 2025
9db75dd
chore: fmt/clippy
joe-p Jun 25, 2025
d8ee065
fix(utils): http client trait with ffi_wasm
joe-p Jun 25, 2025
e4b3b2c
chore: fix typo
joe-p Jun 25, 2025
cc8526a
chore: rename algokit_http_client crate
joe-p Jun 25, 2025
4324c93
fix(utils): use appropriate error types
joe-p Jun 25, 2025
380e44e
fix(http_client): avoid unwrap
joe-p Jun 25, 2025
afb55ca
chore: add docstring
joe-p Jun 25, 2025
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
1,592 changes: 1,069 additions & 523 deletions Cargo.lock

Large diffs are not rendered by default.

6 changes: 3 additions & 3 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
[workspace]
resolver = "2"
members = [
members = [ "crates/algod_api", "crates/algokit_http_client",
"crates/algokit_transact",
"crates/algokit_transact_ffi",
"crates/algokit_transact_ffi", "crates/algokit_utils",
"crates/ffi_macros", "crates/ffi_mutex",
"tools/build_pkgs",
"crates/ffi_macros",
"crates/uniffi-bindgen",
"docs",
"tools/cargo-bin",
Expand Down
14 changes: 14 additions & 0 deletions crates/algod_api/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
[package]
name = "algod_api"
version = "0.1.0"
edition = "2024"

[features]
default = ["default_http_client"]
default_http_client = ["algokit_http_client/default_client"]


[dependencies]
algokit_http_client = { version = "0.1.0", path = "../algokit_http_client" }
serde = { version = "1.0.219", features = ["derive"] }
serde_json = "1.0.140"
47 changes: 47 additions & 0 deletions crates/algod_api/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
use std::sync::Arc;

use algokit_http_client::{HttpClient, HttpError};

#[cfg(feature = "default_http_client")]
use algokit_http_client::DefaultHttpClient;

use serde::{Deserialize, Serialize};

pub struct AlgodClient {
http_client: Arc<dyn HttpClient>,
}

#[derive(Serialize, Deserialize)]
#[serde(rename_all = "kebab-case")]
pub struct TransactionParams {
pub consensus_version: String,
pub fee: u64,
pub last_round: u64,
pub genesis_id: String,
pub genesis_hash: String,
pub min_fee: u64,
}

/// A temporary AlgodClient until the proper client is generated.
/// The exepectation is that this client will use a HttpClient to make requests to the Algorand API
Copy link
Preview

Copilot AI Jun 25, 2025

Choose a reason for hiding this comment

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

Fix spelling: change "exepectation" to "expectation" in the documentation comment.

Suggested change
/// The exepectation is that this client will use a HttpClient to make requests to the Algorand API
/// The expectation is that this client will use a HttpClient to make requests to the Algorand API

Copilot uses AI. Check for mistakes.

impl AlgodClient {
pub fn new(http_client: Arc<dyn HttpClient>) -> Self {
AlgodClient { http_client }
}

#[cfg(feature = "default_http_client")]
pub fn testnet() -> Self {
AlgodClient {
http_client: Arc::new(DefaultHttpClient::new(
"https://testnet-api.4160.nodely.dev",
)),
}
}

pub async fn transaction_params(&self) -> Result<TransactionParams, HttpError> {
let path = "/v2/transactions/params".to_string();
let response = self.http_client.get(path).await?;

serde_json::from_slice(&response).map_err(|e| HttpError::HttpError(e.to_string()))
}
}
19 changes: 19 additions & 0 deletions crates/algokit_http_client/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
[package]
name = "algokit_http_client"
version = "0.1.0"
edition = "2024"

[features]
default = ["default_client"]
ffi_uniffi = ["dep:uniffi"]
ffi_wasm = ["dep:wasm-bindgen", "dep:js-sys"]
default_client = ["dep:reqwest"]

[dependencies]
async-trait = "0.1.88"
js-sys = { workspace = true, optional = true }
reqwest = { version = "0.12.19", optional = true }
thiserror.workspace = true
uniffi = { workspace = true, optional = true }
wasm-bindgen = { workspace = true, optional = true }
wasm-bindgen-futures = "0.4.50"
98 changes: 98 additions & 0 deletions crates/algokit_http_client/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
use async_trait::async_trait;

#[cfg(feature = "ffi_uniffi")]
uniffi::setup_scaffolding!();

#[derive(Debug, thiserror::Error)]
#[cfg_attr(feature = "ffi_uniffi", derive(uniffi::Error))]
pub enum HttpError {
#[error("HttpError: {0}")]
HttpError(String),
}

#[cfg(not(feature = "ffi_wasm"))]
#[cfg_attr(feature = "ffi_uniffi", uniffi::export(with_foreign))]
#[async_trait]
/// This trait must be implemented by any HTTP client that is used by our Rust crates.
/// It is assumed the implementing type will provide the hostname, port, headers, etc. as needed for each request.
///
/// By default, this trait requires the implementing type to be `Send + Sync`.
/// For WASM targets, enable the `ffi_wasm` feature to use a different implementation that is compatible with WASM.
///
/// With the `ffi_uniffi` feature enabled, this is exported as a foreign trait, meaning it is implemented natively in the foreign language.
///
pub trait HttpClient: Send + Sync {
async fn get(&self, path: String) -> Result<Vec<u8>, HttpError>;
}

#[cfg(feature = "default_client")]
pub struct DefaultHttpClient {
host: String,
}

#[cfg(feature = "default_client")]
impl DefaultHttpClient {
pub fn new(host: &str) -> Self {
DefaultHttpClient {
host: host.to_string(),
}
}
}

#[cfg(feature = "default_client")]
#[cfg_attr(feature = "ffi_wasm", async_trait(?Send))]
#[cfg_attr(not(feature = "ffi_wasm"), async_trait)]
impl HttpClient for DefaultHttpClient {
async fn get(&self, path: String) -> Result<Vec<u8>, HttpError> {
let response = reqwest::get(self.host.clone() + &path)
.await
.map_err(|e| HttpError::HttpError(e.to_string()))?
.bytes()
.await
.map_err(|e| HttpError::HttpError(e.to_string()))?
.to_vec();

Ok(response)
}
}

#[cfg(feature = "ffi_wasm")]
use wasm_bindgen::prelude::*;

#[cfg(feature = "ffi_wasm")]
use js_sys::Uint8Array;

#[cfg(feature = "ffi_wasm")]
#[async_trait(?Send)]
pub trait HttpClient {
async fn get(&self, path: String) -> Result<Vec<u8>, HttpError>;
}

#[wasm_bindgen]
#[cfg(feature = "ffi_wasm")]
extern "C" {
/// The interface for the JavaScript-based HTTP client that will be used in WASM environments.
///
/// This mirrors the `HttpClient` trait, but wasm-bindgen doesn't support foreign traits so we define it separately.
pub type WasmHttpClient;

#[wasm_bindgen(method, catch)]
async fn get(this: &WasmHttpClient, path: &str) -> Result<Uint8Array, JsValue>;
}

#[cfg(feature = "ffi_wasm")]
#[async_trait(?Send)]
impl HttpClient for WasmHttpClient {
async fn get(&self, path: String) -> Result<Vec<u8>, HttpError> {
let result = self.get(&path).await.map_err(|e| {
HttpError::HttpError(
e.as_string().unwrap_or(
"A HTTP error ocurred in JavaScript, but it cannot be converted to a string"
Copy link
Preview

Copilot AI Jun 25, 2025

Choose a reason for hiding this comment

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

Fix spelling: change "ocurred" to "occurred" in the error message.

Suggested change
"A HTTP error ocurred in JavaScript, but it cannot be converted to a string"
"A HTTP error occurred in JavaScript, but it cannot be converted to a string"

Copilot uses AI. Check for mistakes.

.to_string(),
),
)
})?;

Ok(result.to_vec())
}
}
2 changes: 1 addition & 1 deletion crates/algokit_transact_ffi/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ version = "0.1.0"
edition = "2021"

[lib]
crate-type = ["cdylib", "staticlib"]
crate-type = ["lib", "cdylib", "staticlib"]

[features]
default = ["ffi_uniffi"]
Expand Down
21 changes: 21 additions & 0 deletions crates/algokit_utils/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
[package]
name = "algokit_utils"
version = "0.1.0"
edition = "2024"

[features]
default = ["default_http_client"]
default_http_client = ["dep:reqwest", "algokit_http_client/default_client"]

[dependencies]
algod_api = { version = "0.1.0", path = "../algod_api" }
algokit_http_client = { version = "0.1.0", path = "../algokit_http_client", default-features = false }
algokit_transact = { version = "0.1.0", path = "../algokit_transact", features = ["test_utils"] }
async-trait = { version = "0.1.88" }
base64 = "0.22.1"
derive_more = { version = "2.0.1", features = ["full"] }
reqwest = { version = "0.12.19", features = ["blocking"], optional = true }
thiserror.workspace = true

[dev-dependencies]
tokio = { version = "1.45.1", features = ["full", "test-util"] }
Loading