mod avatar;
mod cid_v0_to_digest;
pub mod config;
mod core;
mod runner;
mod services;
#[cfg(feature = "ws")]
pub mod ws;
pub use services::referrals::{
Referral, ReferralInfo, ReferralList, ReferralListMineOptions, ReferralPreview,
ReferralPreviewList, ReferralPublicListOptions, ReferralSession, ReferralStatus,
ReferralStoreInput, ReferralSyncStatus, Referrals, ReferralsError, StoreBatchError,
StoreBatchResult,
};
pub use services::registration;
#[cfg(feature = "ws")]
use alloy_json_rpc::RpcSend;
use alloy_primitives::Address;
pub use avatar::{BaseGroupAvatar, HumanAvatar, OrganisationAvatar};
use circles_profiles::{Profile, Profiles};
#[cfg(feature = "ws")]
use circles_rpc::events::subscription::CirclesSubscription;
use circles_rpc::{CirclesRpc, PagedQuery};
#[cfg(feature = "ws")]
use circles_types::CirclesEvent;
use circles_types::{
AggregatedTrustRelation, AvatarInfo, AvatarType, CirclesConfig, GroupMembershipRow,
GroupTokenHolderRow, SortOrder, TokenBalanceResponse, TrustRelation,
};
use core::Core;
pub use runner::{
BatchRun, ContractRunner, EoaContractRunner, PreparedSafeExecution, PreparedTransaction,
RunnerError, SafeContractRunner, SafeExecutionBuilder, SubmittedTx, call_to_tx,
};
#[cfg(feature = "ws")]
use serde_json::to_value;
use std::sync::Arc;
use thiserror::Error;
pub struct RegistrationResult<T> {
pub avatar: Option<T>,
pub txs: Vec<SubmittedTx>,
}
#[derive(Debug, Error)]
pub enum SdkError {
#[error("circles rpc error: {0}")]
Rpc(#[from] circles_rpc::CirclesRpcError),
#[error("profiles error: {0}")]
Profiles(#[from] circles_profiles::ProfilesError),
#[error("referrals error: {0}")]
Referrals(#[from] services::referrals::ReferralsError),
#[error("transfers error: {0}")]
Transfers(#[from] circles_transfers::TransferError),
#[error("runner error: {0}")]
Runner(#[from] RunnerError),
#[error("cid error: {0}")]
Cid(#[from] cid_v0_to_digest::CidError),
#[error("contract call error: {0}")]
Contract(String),
#[error("operation failed: {0}")]
OperationFailed(String),
#[error("contract runner is required for this operation")]
MissingRunner,
#[error("sender address is required for this operation")]
MissingSender,
#[error("avatar not found for address {0:?}")]
AvatarNotFound(Address),
#[error("invalid registration input: {0}")]
InvalidRegistration(String),
#[error("websocket subscription failed after {attempts} attempts: {reason}")]
WsSubscribeFailed { attempts: usize, reason: String },
}
pub struct Sdk {
pub(crate) config: CirclesConfig,
pub(crate) rpc: Arc<CirclesRpc>,
pub(crate) profiles: Profiles,
pub(crate) referrals: Option<Referrals>,
pub(crate) core: Arc<Core>,
pub(crate) runner: Option<Arc<dyn ContractRunner>>,
pub(crate) sender_address: Option<Address>,
}
impl Sdk {
pub fn new(
config: CirclesConfig,
runner: Option<Arc<dyn ContractRunner>>,
) -> Result<Self, SdkError> {
let sender_address = runner.as_ref().map(|r| r.sender_address());
let core = Arc::new(Core::new(config.clone()));
let rpc = Arc::new(CirclesRpc::try_from_http(&config.circles_rpc_url)?);
let profiles = Profiles::new(config.profile_service_url.clone())?;
let referrals = config
.referrals_service_url
.as_deref()
.map(|url| Referrals::new(url, core.clone()))
.transpose()?;
Ok(Self {
rpc,
profiles,
referrals,
config,
core,
runner,
sender_address,
})
}
pub fn rpc(&self) -> &CirclesRpc {
self.rpc.as_ref()
}
pub fn config(&self) -> &CirclesConfig {
&self.config
}
pub fn core(&self) -> &Arc<Core> {
&self.core
}
pub fn profiles(&self) -> &Profiles {
&self.profiles
}
pub fn referrals(&self) -> Option<&Referrals> {
self.referrals.as_ref()
}
pub fn runner(&self) -> Option<&Arc<dyn ContractRunner>> {
self.runner.as_ref()
}
pub fn sender_address(&self) -> Option<Address> {
self.sender_address
}
pub async fn create_profile(&self, profile: &Profile) -> Result<String, SdkError> {
Ok(self.profiles.create(profile).await?)
}
pub async fn get_profile(&self, cid: &str) -> Result<Option<Profile>, SdkError> {
Ok(self.profiles.get(cid).await?)
}
pub async fn data_avatar(&self, avatar: Address) -> Result<AvatarInfo, SdkError> {
Ok(self.rpc.avatar().get_avatar_info(avatar).await?)
}
pub async fn data_trust(&self, avatar: Address) -> Result<Vec<TrustRelation>, SdkError> {
Ok(self.rpc.trust().get_trust_relations(avatar).await?)
}
pub async fn data_trust_aggregated(
&self,
avatar: Address,
) -> Result<Vec<AggregatedTrustRelation>, SdkError> {
Ok(self
.rpc
.trust()
.get_aggregated_trust_relations(avatar)
.await?)
}
pub async fn data_balances(
&self,
avatar: Address,
as_time_circles: bool,
use_v2: bool,
) -> Result<Vec<TokenBalanceResponse>, SdkError> {
Ok(self
.rpc
.token()
.get_token_balances(avatar, as_time_circles, use_v2)
.await?)
}
pub fn group_members(
&self,
group: Address,
limit: u32,
sort_order: SortOrder,
) -> PagedQuery<GroupMembershipRow> {
self.rpc.group().get_group_members(group, limit, sort_order)
}
pub async fn group_collateral(
&self,
group: Address,
) -> Result<Vec<TokenBalanceResponse>, SdkError> {
let treasury = self
.core
.base_group(group)
.BASE_TREASURY()
.call()
.await
.map_err(|e| SdkError::Contract(e.to_string()))?;
Ok(self
.rpc
.token()
.get_token_balances(treasury, false, true)
.await?)
}
pub fn group_holders(&self, group: Address, limit: u32) -> PagedQuery<GroupTokenHolderRow> {
self.rpc.group().get_group_holders(group, limit)
}
pub async fn avatar_info(&self, avatar: Address) -> Result<AvatarInfo, SdkError> {
Ok(self.rpc.avatar().get_avatar_info(avatar).await?)
}
#[cfg(feature = "ws")]
pub async fn subscribe_events_ws<F>(
&self,
ws_url: &str,
filter: F,
) -> Result<CirclesSubscription<CirclesEvent>, SdkError>
where
F: RpcSend + 'static,
{
let val = to_value(&filter).map_err(|e| SdkError::WsSubscribeFailed {
attempts: 0,
reason: e.to_string(),
})?;
self.subscribe_events_ws_with_retries(ws_url, val, None)
.await
}
#[cfg(feature = "ws")]
pub async fn subscribe_events_ws_with_retries(
&self,
ws_url: &str,
filter: serde_json::Value,
max_attempts: Option<usize>,
) -> Result<CirclesSubscription<CirclesEvent>, SdkError> {
ws::subscribe_with_retries(ws_url, filter, max_attempts).await
}
#[cfg(feature = "ws")]
pub async fn subscribe_events_ws_with_catchup(
&self,
ws_url: &str,
filter: serde_json::Value,
max_attempts: Option<usize>,
catch_up_from_block: Option<u64>,
catch_up_filter: Option<Vec<circles_types::Filter>>,
) -> Result<(Vec<CirclesEvent>, CirclesSubscription<CirclesEvent>), SdkError> {
ws::subscribe_with_catchup(
self.rpc.as_ref(),
ws_url,
filter,
max_attempts,
catch_up_from_block,
catch_up_filter,
None,
)
.await
}
pub async fn get_avatar(&self, avatar: Address) -> Result<Avatar, SdkError> {
let info = self.rpc.avatar().get_avatar_info(avatar).await?;
Ok(match info.avatar_type {
AvatarType::CrcV2RegisterGroup => Avatar::Group(BaseGroupAvatar::new(
avatar,
info,
self.core.clone(),
self.profiles.clone(),
self.rpc.clone(),
self.runner.clone(),
)),
AvatarType::CrcV2RegisterOrganization => Avatar::Organisation(OrganisationAvatar::new(
avatar,
info,
self.core.clone(),
self.profiles.clone(),
self.rpc.clone(),
self.runner.clone(),
)),
_ => Avatar::Human(HumanAvatar::new(
avatar,
info,
self.core.clone(),
self.profiles.clone(),
self.rpc.clone(),
self.runner.clone(),
)),
})
}
pub async fn register_human(
&self,
inviter: Address,
profile: &Profile,
) -> Result<RegistrationResult<HumanAvatar>, SdkError> {
registration::register_human(self, inviter, profile).await
}
pub async fn register_organisation(
&self,
name: &str,
profile: &Profile,
) -> Result<RegistrationResult<OrganisationAvatar>, SdkError> {
registration::register_organisation(self, name, profile).await
}
#[allow(clippy::too_many_arguments)]
pub async fn register_group(
&self,
owner: Address,
service: Address,
fee_collection: Address,
initial_conditions: &[Address],
name: &str,
symbol: &str,
profile: &Profile,
) -> Result<RegistrationResult<BaseGroupAvatar>, SdkError> {
registration::register_group(
self,
owner,
service,
fee_collection,
initial_conditions,
name,
symbol,
profile,
)
.await
}
}
pub enum Avatar {
Human(HumanAvatar),
Organisation(OrganisationAvatar),
Group(BaseGroupAvatar),
}