1mod avatar;
79mod cid_v0_to_digest;
80pub mod config;
81mod core;
82mod runner;
83mod services;
84#[cfg(feature = "ws")]
85pub mod ws;
86pub use services::referrals::{
87 Referral, ReferralInfo, ReferralList, ReferralListMineOptions, ReferralPreview,
88 ReferralPreviewList, ReferralPublicListOptions, ReferralSession, ReferralStatus,
89 ReferralStoreInput, ReferralSyncStatus, Referrals, ReferralsError, StoreBatchError,
90 StoreBatchResult,
91};
92pub use services::registration;
93
94#[cfg(feature = "ws")]
95use alloy_json_rpc::RpcSend;
96use alloy_primitives::Address;
97pub use avatar::{BaseGroupAvatar, HumanAvatar, OrganisationAvatar};
98use circles_profiles::{Profile, Profiles};
99#[cfg(feature = "ws")]
100use circles_rpc::events::subscription::CirclesSubscription;
101use circles_rpc::{CirclesRpc, PagedQuery};
102#[cfg(feature = "ws")]
103use circles_types::CirclesEvent;
104use circles_types::{
105 AggregatedTrustRelation, AvatarInfo, AvatarType, CirclesConfig, GroupMembershipRow,
106 GroupTokenHolderRow, SortOrder, TokenBalanceResponse, TrustRelation,
107};
108use core::Core;
109pub use runner::{
110 BatchRun, ContractRunner, EoaContractRunner, PreparedSafeExecution, PreparedTransaction,
111 RunnerError, SafeContractRunner, SafeExecutionBuilder, SubmittedTx, call_to_tx,
112};
113#[cfg(feature = "ws")]
114use serde_json::to_value;
115use std::sync::Arc;
116use thiserror::Error;
117
118pub struct RegistrationResult<T> {
122 pub avatar: Option<T>,
124 pub txs: Vec<SubmittedTx>,
126}
127
128#[derive(Debug, Error)]
130pub enum SdkError {
131 #[error("circles rpc error: {0}")]
132 Rpc(#[from] circles_rpc::CirclesRpcError),
133 #[error("profiles error: {0}")]
134 Profiles(#[from] circles_profiles::ProfilesError),
135 #[error("referrals error: {0}")]
136 Referrals(#[from] services::referrals::ReferralsError),
137 #[error("transfers error: {0}")]
138 Transfers(#[from] circles_transfers::TransferError),
139 #[error("runner error: {0}")]
140 Runner(#[from] RunnerError),
141 #[error("cid error: {0}")]
142 Cid(#[from] cid_v0_to_digest::CidError),
143 #[error("contract call error: {0}")]
144 Contract(String),
145 #[error("operation failed: {0}")]
146 OperationFailed(String),
147 #[error("contract runner is required for this operation")]
148 MissingRunner,
149 #[error("sender address is required for this operation")]
150 MissingSender,
151 #[error("avatar not found for address {0:?}")]
152 AvatarNotFound(Address),
153 #[error("invalid registration input: {0}")]
154 InvalidRegistration(String),
155 #[error("websocket subscription failed after {attempts} attempts: {reason}")]
156 WsSubscribeFailed { attempts: usize, reason: String },
157}
158
159pub struct Sdk {
163 pub(crate) config: CirclesConfig,
164 pub(crate) rpc: Arc<CirclesRpc>,
165 pub(crate) profiles: Profiles,
166 pub(crate) referrals: Option<Referrals>,
167 pub(crate) core: Arc<Core>,
168 pub(crate) runner: Option<Arc<dyn ContractRunner>>,
169 pub(crate) sender_address: Option<Address>,
170}
171
172impl Sdk {
173 pub fn new(
175 config: CirclesConfig,
176 runner: Option<Arc<dyn ContractRunner>>,
177 ) -> Result<Self, SdkError> {
178 let sender_address = runner.as_ref().map(|r| r.sender_address());
179 let core = Arc::new(Core::new(config.clone()));
180 let rpc = Arc::new(CirclesRpc::try_from_http(&config.circles_rpc_url)?);
181 let profiles = Profiles::new(config.profile_service_url.clone())?;
182 let referrals = config
183 .referrals_service_url
184 .as_deref()
185 .map(|url| Referrals::new(url, core.clone()))
186 .transpose()?;
187 Ok(Self {
188 rpc,
189 profiles,
190 referrals,
191 config,
192 core,
193 runner,
194 sender_address,
195 })
196 }
197
198 pub fn rpc(&self) -> &CirclesRpc {
200 self.rpc.as_ref()
201 }
202
203 pub fn config(&self) -> &CirclesConfig {
205 &self.config
206 }
207
208 pub fn core(&self) -> &Arc<Core> {
210 &self.core
211 }
212
213 pub fn profiles(&self) -> &Profiles {
215 &self.profiles
216 }
217
218 pub fn referrals(&self) -> Option<&Referrals> {
220 self.referrals.as_ref()
221 }
222
223 pub fn runner(&self) -> Option<&Arc<dyn ContractRunner>> {
225 self.runner.as_ref()
226 }
227
228 pub fn sender_address(&self) -> Option<Address> {
230 self.sender_address
231 }
232
233 pub async fn create_profile(&self, profile: &Profile) -> Result<String, SdkError> {
237 Ok(self.profiles.create(profile).await?)
238 }
239
240 pub async fn get_profile(&self, cid: &str) -> Result<Option<Profile>, SdkError> {
242 Ok(self.profiles.get(cid).await?)
243 }
244
245 pub async fn data_avatar(&self, avatar: Address) -> Result<AvatarInfo, SdkError> {
247 Ok(self.rpc.avatar().get_avatar_info(avatar).await?)
248 }
249
250 pub async fn data_trust(&self, avatar: Address) -> Result<Vec<TrustRelation>, SdkError> {
252 Ok(self.rpc.trust().get_trust_relations(avatar).await?)
253 }
254
255 pub async fn data_trust_aggregated(
257 &self,
258 avatar: Address,
259 ) -> Result<Vec<AggregatedTrustRelation>, SdkError> {
260 Ok(self
261 .rpc
262 .trust()
263 .get_aggregated_trust_relations(avatar)
264 .await?)
265 }
266
267 pub async fn data_balances(
272 &self,
273 avatar: Address,
274 as_time_circles: bool,
275 use_v2: bool,
276 ) -> Result<Vec<TokenBalanceResponse>, SdkError> {
277 Ok(self
278 .rpc
279 .token()
280 .get_token_balances(avatar, as_time_circles, use_v2)
281 .await?)
282 }
283
284 pub fn group_members(
286 &self,
287 group: Address,
288 limit: u32,
289 sort_order: SortOrder,
290 ) -> PagedQuery<GroupMembershipRow> {
291 self.rpc.group().get_group_members(group, limit, sort_order)
292 }
293
294 pub async fn group_collateral(
296 &self,
297 group: Address,
298 ) -> Result<Vec<TokenBalanceResponse>, SdkError> {
299 let treasury = self
300 .core
301 .base_group(group)
302 .BASE_TREASURY()
303 .call()
304 .await
305 .map_err(|e| SdkError::Contract(e.to_string()))?;
306 Ok(self
307 .rpc
308 .token()
309 .get_token_balances(treasury, false, true)
310 .await?)
311 }
312
313 pub fn group_holders(&self, group: Address, limit: u32) -> PagedQuery<GroupTokenHolderRow> {
315 self.rpc.group().get_group_holders(group, limit)
316 }
317
318 pub async fn avatar_info(&self, avatar: Address) -> Result<AvatarInfo, SdkError> {
320 Ok(self.rpc.avatar().get_avatar_info(avatar).await?)
321 }
322
323 #[cfg(feature = "ws")]
325 pub async fn subscribe_events_ws<F>(
326 &self,
327 ws_url: &str,
328 filter: F,
329 ) -> Result<CirclesSubscription<CirclesEvent>, SdkError>
330 where
331 F: RpcSend + 'static,
332 {
333 let val = to_value(&filter).map_err(|e| SdkError::WsSubscribeFailed {
334 attempts: 0,
335 reason: e.to_string(),
336 })?;
337 self.subscribe_events_ws_with_retries(ws_url, val, None)
338 .await
339 }
340
341 #[cfg(feature = "ws")]
343 pub async fn subscribe_events_ws_with_retries(
344 &self,
345 ws_url: &str,
346 filter: serde_json::Value,
347 max_attempts: Option<usize>,
348 ) -> Result<CirclesSubscription<CirclesEvent>, SdkError> {
349 ws::subscribe_with_retries(ws_url, filter, max_attempts).await
350 }
351
352 #[cfg(feature = "ws")]
354 pub async fn subscribe_events_ws_with_catchup(
355 &self,
356 ws_url: &str,
357 filter: serde_json::Value,
358 max_attempts: Option<usize>,
359 catch_up_from_block: Option<u64>,
360 catch_up_filter: Option<Vec<circles_types::Filter>>,
361 ) -> Result<(Vec<CirclesEvent>, CirclesSubscription<CirclesEvent>), SdkError> {
362 ws::subscribe_with_catchup(
363 self.rpc.as_ref(),
364 ws_url,
365 filter,
366 max_attempts,
367 catch_up_from_block,
368 catch_up_filter,
369 None,
370 )
371 .await
372 }
373
374 pub async fn get_avatar(&self, avatar: Address) -> Result<Avatar, SdkError> {
379 let info = self.rpc.avatar().get_avatar_info(avatar).await?;
380 Ok(match info.avatar_type {
381 AvatarType::CrcV2RegisterGroup => Avatar::Group(BaseGroupAvatar::new(
382 avatar,
383 info,
384 self.core.clone(),
385 self.profiles.clone(),
386 self.rpc.clone(),
387 self.runner.clone(),
388 )),
389 AvatarType::CrcV2RegisterOrganization => Avatar::Organisation(OrganisationAvatar::new(
390 avatar,
391 info,
392 self.core.clone(),
393 self.profiles.clone(),
394 self.rpc.clone(),
395 self.runner.clone(),
396 )),
397 _ => Avatar::Human(HumanAvatar::new(
398 avatar,
399 info,
400 self.core.clone(),
401 self.profiles.clone(),
402 self.rpc.clone(),
403 self.runner.clone(),
404 )),
405 })
406 }
407
408 pub async fn register_human(
410 &self,
411 inviter: Address,
412 profile: &Profile,
413 ) -> Result<RegistrationResult<HumanAvatar>, SdkError> {
414 registration::register_human(self, inviter, profile).await
415 }
416
417 pub async fn register_organisation(
419 &self,
420 name: &str,
421 profile: &Profile,
422 ) -> Result<RegistrationResult<OrganisationAvatar>, SdkError> {
423 registration::register_organisation(self, name, profile).await
424 }
425
426 #[allow(clippy::too_many_arguments)]
428 pub async fn register_group(
429 &self,
430 owner: Address,
431 service: Address,
432 fee_collection: Address,
433 initial_conditions: &[Address],
434 name: &str,
435 symbol: &str,
436 profile: &Profile,
437 ) -> Result<RegistrationResult<BaseGroupAvatar>, SdkError> {
438 registration::register_group(
439 self,
440 owner,
441 service,
442 fee_collection,
443 initial_conditions,
444 name,
445 symbol,
446 profile,
447 )
448 .await
449 }
450}
451
452pub enum Avatar {
454 Human(HumanAvatar),
456 Organisation(OrganisationAvatar),
458 Group(BaseGroupAvatar),
460}