Skip to content
Open
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
5 changes: 2 additions & 3 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ edition = "2021"
description = "Rust AI agent SDK for blockchain — chain-agnostic wallets, DEX interaction, MPP payments, on-chain state reading"
license = "MIT"
repository = "https://github.com/kcolbchain/arka"
keywords = ["blockchain", "ai-agents", "web3", "mpp", "defi"]
keywords = ["blockchain", "ai-agents", "web3", "mpp", "ark"]
categories = ["cryptography::cryptocurrencies"]

[dependencies]
Expand All @@ -16,8 +16,7 @@ serde_json = "1"
thiserror = "2"
tracing = "0.1"
tracing-subscriber = { version = "0.3", features = ["env-filter"] }
reqwest = { version = "0.12", features = ["json"] }
hex = "0.4"
reqwest = { version = "0.12", default-features = false, features = ["json", "rustls-tls"] }
rand = "0.8"
async-trait = "0.1"
url = "2"
Expand Down
153 changes: 153 additions & 0 deletions src/cr8/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
// arka::cr8 — CR8Client trait, builder, errors per create-protocol/cr8/specs/arka-cr8-client.md
use crate::error::ArkaError;
use crate::wallet::switchboard::SwitchboardWallet;
use crate::chain::Chain;
use std::collections::HashMap;

/// CR8 protocol error types (spec §3)
#[derive(Debug, thiserror::Error)]
pub enum CR8Error {
#[error("invalid argument: {0}")]
InvalidArgument(String),
#[error("contract call failed: {0}")]
ContractError(String),
#[error("insufficient balance: required {required}, available {available}")]
InsufficientBalance { required: u64, available: u64 },
#[error("recovery failed: {0}")]
RecoveryError(String),
#[error("chain error: {0}")]
ChainError(#[from] ArkaError),
#[error("unknown CR8 error")]
Unknown,
}

/// Core CR8 client trait (spec §2)
#[async_trait::async_trait]
pub trait CR8Client: Send + Sync {
async fn register(&self) -> Result<(), CR8Error>;
async fn deposit(&self, amount: u64) -> Result<(), CR8Error>;
async fn withdraw(&self, amount: u64) -> Result<(), CR8Error>;
async fn claim(&self) -> Result<u64, CR8Error>;
async fn complete(&self) -> Result<(), CR8Error>;
async fn balance(&self) -> Result<u64, CR8Error>;
async fn watch<F>(&self, callback: F) -> Result<(), CR8Error>
where
F: Fn(u64) + Send + Sync + 'static;
}

/// Recovery trait for CR8 clients (spec §2.5)
#[async_trait::async_trait]
pub trait CR8ClientRecovery: CR8Client {
async fn recover_state(&self) -> Result<HashMap<String, u64>, CR8Error>;
async fn force_complete(&self) -> Result<(), CR8Error>;
}

/// Builder for constructing CR8 clients (spec §2)
pub struct CR8ClientBuilder {
chain: Option<Chain>,
wallet: Option<SwitchboardWallet>,
contract_address: Option<String>,
}

impl CR8ClientBuilder {
pub fn new() -> Self {
Self {
chain: None,
wallet: None,
contract_address: None,
}
}

pub fn with_chain(mut self, chain: Chain) -> Self {
self.chain = Some(chain);
self
}

pub fn with_wallet(mut self, wallet: SwitchboardWallet) -> Self {
self.wallet = Some(wallet);
self
}

pub fn with_contract_address(mut self, address: &str) -> Self {
self.contract_address = Some(address.to_string());
self
}

/// Build a default CR8 client implementation
pub fn build(self) -> Result<DefaultCR8Client, CR8Error> {
let chain = self.chain.ok_or(CR8Error::InvalidArgument("chain required".into()))?;
let wallet = self.wallet.ok_or(CR8Error::InvalidArgument("wallet required".into()))?;
let contract_address = self.contract_address
.ok_or(CR8Error::InvalidArgument("contract address required".into()))?;

Ok(DefaultCR8Client {
chain,
wallet,
contract_address,
})
}
}

/// Default implementation of CR8Client
pub struct DefaultCR8Client {
chain: Chain,
wallet: SwitchboardWallet,
contract_address: String,
}

#[async_trait::async_trait]
impl CR8Client for DefaultCR8Client {
async fn register(&self) -> Result<(), CR8Error> {
// TODO: actual contract call
Ok(())
}

async fn deposit(&self, amount: u64) -> Result<(), CR8Error> {
// TODO: actual contract call
Ok(())
}

async fn withdraw(&self, amount: u64) -> Result<(), CR8Error> {
// TODO: actual contract call
Ok(())
}

async fn claim(&self) -> Result<u64, CR8Error> {
// TODO: actual contract call
Ok(0)
}

async fn complete(&self) -> Result<(), CR8Error> {
// TODO: actual contract call
Ok(())
}

async fn balance(&self) -> Result<u64, CR8Error> {
// TODO: actual contract call
Ok(0)
}

async fn watch<F>(&self, _callback: F) -> Result<(), CR8Error>
where
F: Fn(u64) + Send + Sync + 'static,
{
// TODO: event listener
Ok(())
}
}

#[async_trait::async_trait]
impl CR8ClientRecovery for DefaultCR8Client {
async fn recover_state(&self) -> Result<HashMap<String, u64>, CR8Error> {
// TODO: recovery logic
Ok(HashMap::new())
}

async fn force_complete(&self) -> Result<(), CR8Error> {
// TODO: force completion
Ok(())
}
}

#[cfg(test)]
mod tests;
24 changes: 24 additions & 0 deletions src/cr8/tests.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
use super::*;
use crate::chain::Chain;
use crate::wallet::switchboard::SwitchboardWallet;

#[tokio::test]
async fn test_builder_missing_chain() {
let result = CR8ClientBuilder::new()
.with_wallet(SwitchboardWallet::generate().unwrap())
.with_contract_address("0x123")
.build();
assert!(result.is_err());
}

#[tokio::test]
async fn test_builder_success() {
let wallet = SwitchboardWallet::generate().unwrap();
let client = CR8ClientBuilder::new()
.with_chain(Chain::ArbitrumSepolia)
.with_wallet(wallet)
.with_contract_address("0x123")
.build()
.unwrap();
assert_eq!(client.contract_address, "0x123");
}
1 change: 1 addition & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,3 +27,4 @@ pub mod prelude {
pub use crate::wallet::Wallet;
pub use alloy::primitives::{Address, U256};
}
pub mod cr8;