Skip to main content

polyester/services/
thin.rs

1//! Thin service shells that expose generated Connect clients for full surface parity.
2
3use super::ServiceContext;
4
5macro_rules! thin_service {
6    ($name:ident, $client:ty) => {
7        #[derive(Clone)]
8        pub struct $name {
9            pub(crate) ctx: ServiceContext,
10        }
11        impl $name {
12            pub fn new(ctx: ServiceContext) -> Self {
13                Self { ctx }
14            }
15            pub(crate) fn connect_client(&self) -> $client {
16                <$client>::new(
17                    self.ctx.factory.transport(),
18                    self.ctx.factory.connect_config(),
19                )
20            }
21        }
22    };
23}
24
25macro_rules! realtime_only_service {
26    ($name:ident) => {
27        #[derive(Clone)]
28        pub struct $name {
29            pub(crate) ctx: ServiceContext,
30        }
31        impl $name {
32            pub fn new(ctx: ServiceContext) -> Self {
33                Self { ctx }
34            }
35        }
36    };
37}
38
39thin_service!(
40    ChainAnalyticsService,
41    crate::connect::chain::analytics::v1::ChainAnalyticsServiceClient<
42        crate::transport::SharedTransport,
43    >
44);
45thin_service!(
46    LifecycleService,
47    crate::connect::chain::lifecycle::v1::LifecycleReadServiceClient<
48        crate::transport::SharedTransport,
49    >
50);
51thin_service!(
52    HeatmapService,
53    crate::connect::marketdata::v1::HeatmapServiceClient<crate::transport::SharedTransport>
54);
55realtime_only_service!(PoliciesService);
56thin_service!(
57    SubAccountsService,
58    crate::connect::auth::v1::SubaccountServiceClient<crate::transport::SharedTransport>
59);
60thin_service!(
61    AddressBookService,
62    crate::connect::auth::v1::AddressBookServiceClient<crate::transport::SharedTransport>
63);
64thin_service!(
65    SocialVerificationService,
66    crate::connect::auth::v1::SocialVerificationServiceClient<crate::transport::SharedTransport>
67);
68thin_service!(
69    WhiteboardService,
70    crate::connect::collab::v1::WhiteboardServiceClient<crate::transport::SharedTransport>
71);
72thin_service!(
73    PolychartService,
74    crate::connect::polychart::v1::PolychartServiceClient<crate::transport::SharedTransport>
75);
76thin_service!(
77    LayoutService,
78    crate::connect::layout::v1::LayoutServiceClient<crate::transport::SharedTransport>
79);
80thin_service!(
81    GuardSignerService,
82    crate::connect::chain::guard::v1::GuardSignerServiceClient<crate::transport::SharedTransport>
83);
84thin_service!(
85    InternalTransfersService,
86    crate::connect::transfer::v1::InternalTransferServiceClient<crate::transport::SharedTransport>
87);
88thin_service!(
89    TransfersService,
90    crate::connect::ledger::read::v1::LedgerReadServiceClient<crate::transport::SharedTransport>
91);
92
93impl HeatmapService {
94    /// Historical orderbook heatmap (Go `HeatmapService.Get` → `ApiData`).
95    pub async fn get(
96        &self,
97        symbol: &str,
98        interval: &str,
99        depth: u32,
100        limit: u32,
101        quantity_mode: &str,
102    ) -> crate::errors::Result<crate::models::ApiData> {
103        use crate::codecs::decode::api_data_from_proto;
104        use crate::codecs::heatmap::{
105            heatmap_depth_for_levels, resolve_heatmap_interval, resolve_heatmap_quantity_mode,
106        };
107        use crate::errors::Error;
108        use crate::proto::marketdata::v1::{GetOrderbookHeatmapRequest, HeatmapTimeRange};
109        use buffa_types::google::protobuf::Timestamp;
110        use std::time::{SystemTime, UNIX_EPOCH};
111
112        let symbol_id = self
113            .ctx
114            .catalogs
115            .symbol_id_for_symbol(symbol)
116            .ok_or_else(|| {
117                Error::validation(format!(
118                    "unknown symbol {symbol}; call hydrate_catalogs / get_spot_config first"
119                ))
120            })?;
121        let interval_enum = resolve_heatmap_interval(interval)?;
122        let qty_mode = resolve_heatmap_quantity_mode(quantity_mode)?;
123        let now = SystemTime::now()
124            .duration_since(UNIX_EPOCH)
125            .unwrap_or_default();
126        let end = Timestamp {
127            seconds: now.as_secs() as i64,
128            nanos: now.subsec_nanos() as i32,
129            ..Default::default()
130        };
131        let start = Timestamp {
132            seconds: end.seconds.saturating_sub(300),
133            nanos: end.nanos,
134            ..Default::default()
135        };
136        let req = GetOrderbookHeatmapRequest {
137            symbol_id,
138            interval: interval_enum.into(),
139            depth: heatmap_depth_for_levels(depth).into(),
140            time_range: HeatmapTimeRange {
141                start_time: start.into(),
142                end_time: end.into(),
143                ..Default::default()
144            }
145            .into(),
146            limit: if limit == 0 { 100 } else { limit },
147            quantity_mode: qty_mode.into(),
148            ..Default::default()
149        };
150        let client = self.connect_client();
151        let resp = super::unary::await_public(client.get_orderbook_heatmap(req))
152            .await?
153            .into_owned();
154        Ok(api_data_from_proto(&resp))
155    }
156
157    /// Subscribe to live heatmap buckets (requires `realtime` feature + hydrated catalogs).
158    pub async fn subscribe_live(
159        &self,
160        symbol: &str,
161        interval: &str,
162    ) -> crate::errors::Result<crate::realtime::TypedSubscription<crate::models::ApiData>> {
163        use crate::codecs::heatmap::heatmap_interval_channel_name;
164        use crate::errors::Error;
165        let symbol_id = self
166            .ctx
167            .catalogs
168            .symbol_id_for_symbol(symbol)
169            .ok_or_else(|| {
170                Error::validation(format!(
171                    "unknown symbol {symbol}; call hydrate_catalogs / get_spot_config first"
172                ))
173            })?;
174        // Validate interval aliases.
175        crate::codecs::heatmap::resolve_heatmap_interval(interval)?;
176        let interval_name = heatmap_interval_channel_name(interval);
177        let channel = format!("public:spot:market:heatmap:{interval_name}:{symbol_id}:proto");
178        self.ctx
179            .realtime
180            .subscribe_proto(
181                &channel,
182                crate::codecs::decode::heatmap_live_bucket_from_bytes,
183            )
184            .await
185    }
186}
187
188fn internal_transfer_amount_e18(
189    quantity: &crate::types::AssetAmount,
190    quantity_scale: Option<u32>,
191    asset_id: u32,
192) -> crate::errors::Result<crate::proto::polyester::r#type::v1::U128> {
193    use crate::codecs::scalars::{LEDGER_SCALE, i128_to_u128};
194    use crate::types::{QuantityDomain, resolve_asset_amount_scaled_with_input_scale};
195
196    let scaled = resolve_asset_amount_scaled_with_input_scale(
197        quantity,
198        quantity_scale,
199        LEDGER_SCALE,
200        QuantityDomain::LedgerE18,
201        Some(asset_id),
202    )?;
203    i128_to_u128(scaled)
204}
205
206impl InternalTransfersService {
207    /// Create an internal transfer. Quantity must be an [`crate::types::AssetAmount`].
208    pub async fn create(
209        &self,
210        params: crate::models::CreateInternalTransferParams,
211    ) -> crate::errors::Result<crate::models::InternalTransferResult> {
212        use super::scope;
213        use super::unary;
214        use crate::codecs::decode::internal_transfer_from_proto;
215        use crate::codecs::scalars::id_to_u64;
216        use crate::errors::Error;
217        use crate::proto::transfer::v1::{
218            CreateInternalTransferRequest, create_internal_transfer_request::Destination,
219        };
220
221        let has_account = params
222            .destination_account_id
223            .as_ref()
224            .is_some_and(|s| !s.trim().is_empty());
225        let has_sub = params
226            .destination_subaccount_id
227            .as_ref()
228            .is_some_and(|s| !s.trim().is_empty());
229        let has_smart = params
230            .destination_smart_account_address
231            .as_ref()
232            .is_some_and(|s| !s.trim().is_empty());
233        if usize::from(has_account) + usize::from(has_sub) + usize::from(has_smart) != 1 {
234            return Err(Error::validation(
235                "create requires exactly one of destination_account_id, destination_subaccount_id, or destination_smart_account_address",
236            ));
237        }
238        if params.idempotency_key.trim().is_empty() {
239            return Err(Error::validation(
240                "create requires a non-empty idempotency_key reused across retries",
241            ));
242        }
243
244        let amount_e18 =
245            internal_transfer_amount_e18(&params.quantity, params.quantity_scale, params.asset_id)?;
246        let mut req = CreateInternalTransferRequest {
247            asset_id: params.asset_id,
248            idempotency_key: params.idempotency_key,
249            subaccount_id: scope::optional_subaccount(&self.ctx, params.subaccount_id)?
250                .unwrap_or(0),
251            ..Default::default()
252        };
253        *req.amount_e18.get_or_insert_default() = amount_e18;
254        if has_account {
255            req.destination = Some(Destination::DestinationAccountId(id_to_u64(
256                params.destination_account_id.as_deref().unwrap(),
257                "destination_account_id",
258            )?));
259        } else if has_sub {
260            req.destination = Some(Destination::DestinationSubaccountId(id_to_u64(
261                params.destination_subaccount_id.as_deref().unwrap(),
262                "destination_subaccount_id",
263            )?));
264        } else {
265            req.destination = Some(Destination::DestinationSmartAccountAddress(
266                params.destination_smart_account_address.unwrap_or_default(),
267            ));
268        }
269
270        let client = self.connect_client();
271        let resp = unary::await_auth(
272            &self.ctx.factory,
273            "/transfer.v1.InternalTransferService/CreateInternalTransfer",
274            req,
275            |req, opts| client.create_internal_transfer_with_options(req, opts),
276        )
277        .await?
278        .into_owned();
279        internal_transfer_from_proto(&resp)
280    }
281}
282
283impl PoliciesService {
284    /// Subscribe to private subaccount policy updates (requires `realtime` feature).
285    pub async fn subscribe(
286        &self,
287        account_id: Option<&str>,
288    ) -> crate::errors::Result<crate::realtime::TypedSubscription<crate::models::SubaccountPolicy>>
289    {
290        self.subscribe_subaccount_policies(account_id).await
291    }
292
293    /// Subscribe to private subaccount policy updates (requires `realtime` feature).
294    pub async fn subscribe_subaccount_policies(
295        &self,
296        account_id: Option<&str>,
297    ) -> crate::errors::Result<crate::realtime::TypedSubscription<crate::models::SubaccountPolicy>>
298    {
299        let account = super::scope::resolve_account_id(&self.ctx, account_id)?;
300        let channel = format!("private:auth:subaccount-policies:{account}:proto");
301        self.ctx
302            .realtime
303            .subscribe_proto(
304                &channel,
305                crate::codecs::decode::subaccount_policy_from_bytes,
306            )
307            .await
308    }
309
310    /// Subscribe to private API-key policy updates (requires `realtime` feature).
311    pub async fn subscribe_api_policies(
312        &self,
313        account_id: Option<&str>,
314    ) -> crate::errors::Result<crate::realtime::TypedSubscription<crate::models::ApiPolicy>> {
315        let account = super::scope::resolve_account_id(&self.ctx, account_id)?;
316        let channel = format!("private:auth:api-policies:{account}:proto");
317        self.ctx
318            .realtime
319            .subscribe_proto(&channel, crate::codecs::decode::api_policy_from_bytes)
320            .await
321    }
322}
323
324#[derive(Debug, Clone, Default)]
325pub struct GetSubaccountOpts {
326    pub include_api_keys: bool,
327    pub include_members: bool,
328    pub include_invites: bool,
329    pub include_policy: bool,
330    pub include_balances: bool,
331    pub invites_direction: String,
332}
333
334impl SubAccountsService {
335    /// Subscribe to private subaccount updates (requires `realtime` feature).
336    pub async fn subscribe(
337        &self,
338        account_id: Option<&str>,
339    ) -> crate::errors::Result<crate::realtime::TypedSubscription<crate::models::SubAccount>> {
340        let account = super::scope::resolve_account_id(&self.ctx, account_id)?;
341        let channel = format!("private:auth:subaccounts:{account}:proto");
342        self.ctx
343            .realtime
344            .subscribe_proto(&channel, crate::codecs::decode::subaccount_from_bytes)
345            .await
346    }
347
348    /// Subscribe to private API key updates for an account (requires `realtime` feature).
349    pub async fn subscribe_api_keys(
350        &self,
351        account_id: Option<&str>,
352    ) -> crate::errors::Result<crate::realtime::TypedSubscription<crate::models::ApiKeySummary>>
353    {
354        let account = super::scope::resolve_account_id(&self.ctx, account_id)?;
355        let channel = format!("private:auth:api-keys:{account}:proto");
356        self.ctx
357            .realtime
358            .subscribe_proto(&channel, crate::codecs::decode::api_key_from_bytes)
359            .await
360    }
361
362    pub async fn list(
363        &self,
364        req: crate::proto::auth::v1::ListSubaccountsRequest,
365    ) -> crate::errors::Result<crate::models::SubAccountsList> {
366        use crate::codecs::decode::subaccounts_list_from_proto;
367        let client = self.connect_client();
368        let resp = {
369            use super::unary;
370            unary::await_auth(
371                &self.ctx.factory,
372                "/auth.v1.SubaccountService/ListSubaccounts",
373                req,
374                |req, opts| client.list_subaccounts_with_options(req, opts),
375            )
376            .await?
377            .into_owned()
378        };
379        Ok(subaccounts_list_from_proto(&resp))
380    }
381
382    pub async fn get(
383        &self,
384        subaccount_id: u64,
385        opts: GetSubaccountOpts,
386    ) -> crate::errors::Result<crate::models::GetSubaccountResult> {
387        use crate::codecs::decode::get_subaccount_from_proto;
388        use crate::proto::auth::v1::GetSubaccountRequest;
389        let req = GetSubaccountRequest {
390            subaccount_id,
391            include_api_keys: opts.include_api_keys,
392            include_members: opts.include_members,
393            include_invites: opts.include_invites,
394            include_policy: opts.include_policy,
395            include_balances: opts.include_balances,
396            invites_direction: opts.invites_direction,
397            ..Default::default()
398        };
399        let client = crate::connect::auth::v1::SubaccountViewServiceClient::new(
400            self.ctx.factory.transport(),
401            self.ctx.factory.connect_config(),
402        );
403        let resp = {
404            use super::unary;
405            unary::await_auth(
406                &self.ctx.factory,
407                "/auth.v1.SubaccountViewService/GetSubaccount",
408                req,
409                |req, opts| client.get_subaccount_with_options(req, opts),
410            )
411            .await?
412            .into_owned()
413        };
414        Ok(get_subaccount_from_proto(&resp))
415    }
416
417    pub async fn list_members(
418        &self,
419        req: crate::proto::auth::v1::ListSubaccountMembersRequest,
420    ) -> crate::errors::Result<crate::models::SubAccountMembersList> {
421        use crate::codecs::decode::subaccount_members_list_from_proto;
422        let client = self.connect_client();
423        let resp = {
424            use super::unary;
425            unary::await_auth(
426                &self.ctx.factory,
427                "/auth.v1.SubaccountService/ListSubaccountMembers",
428                req,
429                |req, opts| client.list_subaccount_members_with_options(req, opts),
430            )
431            .await?
432            .into_owned()
433        };
434        Ok(subaccount_members_list_from_proto(&resp))
435    }
436
437    pub async fn list_invites(
438        &self,
439        req: crate::proto::auth::v1::ListSubaccountInvitesRequest,
440    ) -> crate::errors::Result<crate::models::SubAccountInvitesList> {
441        use crate::codecs::decode::subaccount_invites_list_from_proto;
442        let client = self.connect_client();
443        let resp = {
444            use super::unary;
445            unary::await_auth(
446                &self.ctx.factory,
447                "/auth.v1.SubaccountService/ListSubaccountInvites",
448                req,
449                |req, opts| client.list_subaccount_invites_with_options(req, opts),
450            )
451            .await?
452            .into_owned()
453        };
454        Ok(subaccount_invites_list_from_proto(&resp))
455    }
456
457    pub async fn list_activity(
458        &self,
459        req: crate::proto::auth::v1::ListSubaccountEventsRequest,
460    ) -> crate::errors::Result<crate::models::SubAccountActivityList> {
461        use crate::codecs::decode::subaccount_activity_list_from_proto;
462        let client = crate::connect::auth::v1::SubaccountViewServiceClient::new(
463            self.ctx.factory.transport(),
464            self.ctx.factory.connect_config(),
465        );
466        let resp = {
467            use super::unary;
468            unary::await_auth(
469                &self.ctx.factory,
470                "/auth.v1.SubaccountViewService/ListSubaccountActivity",
471                req,
472                |req, opts| client.list_subaccount_activity_with_options(req, opts),
473            )
474            .await?
475            .into_owned()
476        };
477        Ok(subaccount_activity_list_from_proto(&resp))
478    }
479}
480
481impl AddressBookService {
482    /// Subscribe to address-book view invalidations (requires `realtime` feature).
483    pub async fn subscribe(
484        &self,
485        account_id: Option<&str>,
486    ) -> crate::errors::Result<
487        crate::realtime::TypedSubscription<crate::models::AddressBookViewInvalidation>,
488    > {
489        let account = super::scope::resolve_account_id(&self.ctx, account_id)?;
490        let channel = format!("private:auth:address-books:{account}:proto");
491        self.ctx
492            .realtime
493            .subscribe_proto(
494                &channel,
495                crate::codecs::decode::address_book_invalidation_from_bytes,
496            )
497            .await
498    }
499
500    /// Alias for [`Self::subscribe`] (Go `SubscribeViewInvalidations` parity).
501    pub async fn subscribe_view_invalidations(
502        &self,
503        root_account_public_id: Option<&str>,
504    ) -> crate::errors::Result<
505        crate::realtime::TypedSubscription<crate::models::AddressBookViewInvalidation>,
506    > {
507        self.subscribe(root_account_public_id).await
508    }
509
510    pub async fn list_books(
511        &self,
512        req: crate::proto::auth::v1::ListAddressBooksRequest,
513    ) -> crate::errors::Result<crate::models::AddressBooksList> {
514        use crate::codecs::decode::list_books_from_proto;
515        let client = self.connect_client();
516        let resp = {
517            use super::unary;
518            unary::await_auth(
519                &self.ctx.factory,
520                "/auth.v1.AddressBookService/ListAddressBooks",
521                req,
522                |req, opts| client.list_address_books_with_options(req, opts),
523            )
524            .await?
525            .into_owned()
526        };
527        Ok(list_books_from_proto(&resp))
528    }
529
530    pub async fn list_entries(
531        &self,
532        req: crate::proto::auth::v1::ListAddressBookEntriesRequest,
533    ) -> crate::errors::Result<crate::models::AddressBookEntriesList> {
534        use crate::codecs::decode::list_entries_from_proto;
535        let client = self.connect_client();
536        let resp = {
537            use super::unary;
538            unary::await_auth(
539                &self.ctx.factory,
540                "/auth.v1.AddressBookService/ListAddressBookEntries",
541                req,
542                |req, opts| client.list_address_book_entries_with_options(req, opts),
543            )
544            .await?
545            .into_owned()
546        };
547        Ok(list_entries_from_proto(&resp))
548    }
549
550    pub async fn list_transfer_counterparties(
551        &self,
552        req: crate::proto::auth::v1::ListTransferCounterpartiesRequest,
553    ) -> crate::errors::Result<crate::models::ApiData> {
554        use crate::codecs::decode::api_data_from_proto;
555        let client = self.connect_client();
556        let resp = {
557            use super::unary;
558            unary::await_auth(
559                &self.ctx.factory,
560                "/auth.v1.AddressBookService/ListTransferCounterparties",
561                req,
562                |req, opts| client.list_transfer_counterparties_with_options(req, opts),
563            )
564            .await?
565            .into_owned()
566        };
567        Ok(api_data_from_proto(&resp))
568    }
569
570    pub async fn list_transfer_destinations(
571        &self,
572        req: crate::proto::auth::v1::ListTransferDestinationsRequest,
573    ) -> crate::errors::Result<crate::models::ApiData> {
574        use crate::codecs::decode::api_data_from_proto;
575        let client = self.connect_client();
576        let resp = {
577            use super::unary;
578            unary::await_auth(
579                &self.ctx.factory,
580                "/auth.v1.AddressBookService/ListTransferDestinations",
581                req,
582                |req, opts| client.list_transfer_destinations_with_options(req, opts),
583            )
584            .await?
585            .into_owned()
586        };
587        Ok(api_data_from_proto(&resp))
588    }
589
590    pub async fn list_internal_transfer_whitelist_entries(
591        &self,
592        req: crate::proto::auth::v1::ListInternalTransferWhitelistEntriesRequest,
593    ) -> crate::errors::Result<crate::models::ApiData> {
594        use crate::codecs::decode::api_data_from_proto;
595        let client = self.connect_client();
596        let resp = {
597            use super::unary;
598            unary::await_auth(
599                &self.ctx.factory,
600                "/auth.v1.AddressBookService/ListInternalTransferWhitelistEntries",
601                req,
602                |req, opts| client.list_internal_transfer_whitelist_entries_with_options(req, opts),
603            )
604            .await?
605            .into_owned()
606        };
607        Ok(api_data_from_proto(&resp))
608    }
609
610    pub async fn get_withdraw_whitelist_view(
611        &self,
612        req: crate::proto::auth::v1::GetWithdrawWhitelistViewRequest,
613    ) -> crate::errors::Result<crate::models::ApiData> {
614        use crate::codecs::decode::api_data_from_proto;
615        let client = self.connect_client();
616        let resp = {
617            use super::unary;
618            unary::await_auth(
619                &self.ctx.factory,
620                "/auth.v1.AddressBookService/GetWithdrawWhitelistView",
621                req,
622                |req, opts| client.get_withdraw_whitelist_view_with_options(req, opts),
623            )
624            .await?
625            .into_owned()
626        };
627        Ok(api_data_from_proto(&resp))
628    }
629
630    pub async fn get_view(
631        &self,
632        req: crate::proto::auth::v1::GetAddressBookViewRequest,
633    ) -> crate::errors::Result<crate::models::ApiData> {
634        use crate::codecs::decode::api_data_from_proto;
635        let client = self.connect_client();
636        let resp = {
637            use super::unary;
638            unary::await_auth(
639                &self.ctx.factory,
640                "/auth.v1.AddressBookService/GetAddressBookView",
641                req,
642                |req, opts| client.get_address_book_view_with_options(req, opts),
643            )
644            .await?
645            .into_owned()
646        };
647        Ok(api_data_from_proto(&resp))
648    }
649}
650
651impl TransfersService {
652    pub async fn list(
653        &self,
654        req: crate::proto::ledger::read::v1::ListTransfersRequest,
655    ) -> crate::errors::Result<crate::models::TransfersList> {
656        use crate::codecs::decode::transfers_list_from_proto;
657        let client = self.connect_client();
658        let resp = {
659            use super::unary;
660            unary::await_auth(
661                &self.ctx.factory,
662                "/ledger.read.v1.LedgerReadService/ListTransfers",
663                req,
664                |req, opts| client.list_transfers_with_options(req, opts),
665            )
666            .await?
667            .into_owned()
668        };
669        Ok(transfers_list_from_proto(&resp))
670    }
671
672    /// Subscribe to private transfer updates (requires `realtime` feature).
673    pub async fn subscribe(
674        &self,
675        account_id: Option<&str>,
676    ) -> crate::errors::Result<crate::realtime::TypedSubscription<crate::models::LedgerTransfer>>
677    {
678        let account = super::scope::resolve_account_id(&self.ctx, account_id)?;
679        let channel = format!("private:ledger:transfers:{account}:proto");
680        self.ctx
681            .realtime
682            .subscribe_proto(&channel, crate::codecs::decode::ledger_transfer_from_bytes)
683            .await
684    }
685}
686
687impl ChainAnalyticsService {
688    pub async fn get_zipped_asset_supply(
689        &self,
690        zipped_asset_id: u32,
691        range_key: &str,
692        bucket: &str,
693        start_ts_sec: u32,
694        end_ts_sec: u32,
695    ) -> crate::errors::Result<crate::models::ApiData> {
696        use crate::codecs::analytics::resolve_analytics_range;
697        use crate::codecs::decode::api_data_from_proto;
698        use crate::proto::chain::analytics::v1::GetZippedAssetSupplyRequest;
699        let req = GetZippedAssetSupplyRequest {
700            zipped_asset_id,
701            range: resolve_analytics_range(range_key)?.into(),
702            bucket: bucket.to_owned(),
703            start_ts_sec,
704            end_ts_sec,
705            ..Default::default()
706        };
707        let client = self.connect_client();
708        let resp = super::unary::await_public(client.get_zipped_asset_supply(req))
709            .await?
710            .into_owned();
711        Ok(api_data_from_proto(&resp))
712    }
713
714    pub async fn get_zipped_asset_supply_group(
715        &self,
716        group_id: &str,
717        range_key: &str,
718        bucket: &str,
719        start_ts_sec: u32,
720        end_ts_sec: u32,
721    ) -> crate::errors::Result<crate::models::ApiData> {
722        use crate::codecs::analytics::resolve_analytics_range;
723        use crate::codecs::decode::api_data_from_proto;
724        use crate::proto::chain::analytics::v1::GetZippedAssetSupplyGroupRequest;
725        let req = GetZippedAssetSupplyGroupRequest {
726            group_id: group_id.to_owned(),
727            range: resolve_analytics_range(range_key)?.into(),
728            bucket: bucket.to_owned(),
729            start_ts_sec,
730            end_ts_sec,
731            ..Default::default()
732        };
733        let client = self.connect_client();
734        let resp = super::unary::await_public(client.get_zipped_asset_supply_group(req))
735            .await?
736            .into_owned();
737        Ok(api_data_from_proto(&resp))
738    }
739
740    pub async fn get_unified_asset_balances(
741        &self,
742        asset_id: u32,
743        range_key: &str,
744        bucket: &str,
745        start_ts_sec: u32,
746        end_ts_sec: u32,
747    ) -> crate::errors::Result<crate::models::ApiData> {
748        use crate::codecs::analytics::resolve_analytics_range;
749        use crate::codecs::decode::api_data_from_proto;
750        use crate::errors::Error;
751        use crate::proto::chain::analytics::v1::GetUnifiedAssetBalancesRequest;
752        if asset_id == 0 {
753            return Err(Error::validation("asset_id must be positive"));
754        }
755        let req = GetUnifiedAssetBalancesRequest {
756            asset_id,
757            range: resolve_analytics_range(range_key)?.into(),
758            bucket: bucket.to_owned(),
759            start_ts_sec,
760            end_ts_sec,
761            ..Default::default()
762        };
763        let client = self.connect_client();
764        let resp = super::unary::await_public(client.get_unified_asset_balances(req))
765            .await?
766            .into_owned();
767        Ok(api_data_from_proto(&resp))
768    }
769}
770
771impl LifecycleService {
772    pub async fn list_flows(
773        &self,
774        req: crate::proto::chain::lifecycle::v1::ListFlowsRequest,
775    ) -> crate::errors::Result<crate::models::LifecycleFlowsList> {
776        use crate::codecs::decode::flows_list_from_proto;
777        let client = self.connect_client();
778        let resp = {
779            use super::unary;
780            unary::await_auth(
781                &self.ctx.factory,
782                "/chain.lifecycle.v1.LifecycleReadService/ListFlows",
783                req,
784                |req, opts| client.list_flows_with_options(req, opts),
785            )
786            .await?
787            .into_owned()
788        };
789        Ok(flows_list_from_proto(&resp))
790    }
791
792    pub async fn get_flow(
793        &self,
794        flow_id: &str,
795    ) -> crate::errors::Result<crate::models::LifecycleFlowSummary> {
796        use crate::codecs::decode::flow_from_get_response;
797        use crate::errors::Error;
798        use crate::proto::chain::lifecycle::v1::GetFlowByIdRequest;
799        if flow_id.trim().is_empty() {
800            return Err(Error::validation("flow_id or intent_id is required"));
801        }
802        let req = GetFlowByIdRequest {
803            flow_id: flow_id.to_owned(),
804            ..Default::default()
805        };
806        let client = self.connect_client();
807        let resp = {
808            use super::unary;
809            unary::await_auth(
810                &self.ctx.factory,
811                "/chain.lifecycle.v1.LifecycleReadService/GetFlowById",
812                req,
813                |req, opts| client.get_flow_by_id_with_options(req, opts),
814            )
815            .await?
816            .into_owned()
817        };
818        flow_from_get_response(&resp)
819    }
820
821    pub async fn list_flows_by_tx(
822        &self,
823        req: crate::proto::chain::lifecycle::v1::ListFlowsByTxRequest,
824    ) -> crate::errors::Result<crate::models::LifecycleFlowsList> {
825        use crate::codecs::decode::flows_by_tx_list_from_proto;
826        let client = self.connect_client();
827        let resp = {
828            use super::unary;
829            unary::await_auth(
830                &self.ctx.factory,
831                "/chain.lifecycle.v1.LifecycleReadService/ListFlowsByTx",
832                req,
833                |req, opts| client.list_flows_by_tx_with_options(req, opts),
834            )
835            .await?
836            .into_owned()
837        };
838        Ok(flows_by_tx_list_from_proto(&resp))
839    }
840
841    pub async fn get_flow_by_tx(
842        &self,
843        req: crate::proto::chain::lifecycle::v1::ListFlowsByTxRequest,
844    ) -> crate::errors::Result<crate::models::LifecycleFlowSummary> {
845        use crate::codecs::decode::flow_from_get_by_tx_response;
846        let client = self.connect_client();
847        let resp = {
848            use super::unary;
849            unary::await_auth(
850                &self.ctx.factory,
851                "/chain.lifecycle.v1.LifecycleReadService/ListFlowsByTx",
852                req,
853                |req, opts| client.list_flows_by_tx_with_options(req, opts),
854            )
855            .await?
856            .into_owned()
857        };
858        flow_from_get_by_tx_response(&resp)
859    }
860
861    /// Subscribe to open lifecycle flow summaries.
862    ///
863    /// When `account_id` is `Some`, uses the private account channel; otherwise
864    /// the public open-flows channel (Go `SubscribeOpenFlows` parity).
865    pub async fn subscribe_open_flows(
866        &self,
867        account_id: Option<&str>,
868    ) -> crate::errors::Result<
869        crate::realtime::TypedSubscription<crate::models::LifecycleFlowSummary>,
870    > {
871        if let Some(account) = account_id.filter(|s| !s.trim().is_empty()) {
872            let channel = format!("private:chain:lifecycle:flows:{account}:proto");
873            self.ctx
874                .realtime
875                .subscribe_proto(&channel, crate::codecs::decode::flow_summary_from_bytes)
876                .await
877        } else {
878            self.ctx
879                .realtime
880                .subscribe_proto(
881                    "public:chain:lifecycle:flows:proto",
882                    crate::codecs::decode::flow_summary_from_bytes,
883                )
884                .await
885        }
886    }
887
888    /// Subscribe to a single flow's detail updates (requires `realtime` feature).
889    pub async fn subscribe_flow_detail(
890        &self,
891        flow_id: &str,
892    ) -> crate::errors::Result<
893        crate::realtime::TypedSubscription<crate::models::LifecycleFlowSummary>,
894    > {
895        let channel = format!("public:chain:lifecycle:flow:{flow_id}:proto");
896        self.ctx
897            .realtime
898            .subscribe_proto(&channel, crate::codecs::decode::flow_detail_from_bytes)
899            .await
900    }
901}
902
903impl GuardSignerService {
904    pub async fn get_status(
905        &self,
906        req: crate::proto::chain::guard::v1::GetGuardSignerStatusRequest,
907    ) -> crate::errors::Result<Option<crate::models::GuardSignerStatus>> {
908        use crate::codecs::decode::status_from_proto;
909        let client = self.connect_client();
910        let resp = {
911            use super::unary;
912            unary::await_auth(
913                &self.ctx.factory,
914                "/chain.guard.v1.GuardSignerService/GetGuardSignerStatus",
915                req,
916                |req, opts| client.get_guard_signer_status_with_options(req, opts),
917            )
918            .await?
919            .into_owned()
920        };
921        Ok(status_from_proto(&resp))
922    }
923
924    pub async fn create_wallet(
925        &self,
926        req: crate::proto::chain::guard::v1::CreateGuardSignerWalletRequest,
927    ) -> crate::errors::Result<crate::models::CreateGuardSignerWalletResult> {
928        use crate::codecs::decode::create_wallet_from_proto;
929        let client = self.connect_client();
930        let resp = {
931            use super::unary;
932            unary::await_auth(
933                &self.ctx.factory,
934                "/chain.guard.v1.GuardSignerService/CreateGuardSignerWallet",
935                req,
936                |req, opts| client.create_guard_signer_wallet_with_options(req, opts),
937            )
938            .await?
939            .into_owned()
940        };
941        Ok(create_wallet_from_proto(&resp))
942    }
943
944    pub async fn sign_protected_action(
945        &self,
946        req: crate::proto::chain::guard::v1::SignProtectedActionRequest,
947    ) -> crate::errors::Result<Option<crate::models::GuardApproval>> {
948        use crate::codecs::decode::sign_protected_action_from_proto;
949        let client = self.connect_client();
950        let resp = {
951            use super::unary;
952            unary::await_auth(
953                &self.ctx.factory,
954                "/chain.guard.v1.GuardSignerService/SignProtectedAction",
955                req,
956                |req, opts| client.sign_protected_action_with_options(req, opts),
957            )
958            .await?
959            .into_owned()
960        };
961        Ok(sign_protected_action_from_proto(&resp))
962    }
963
964    pub async fn batch_sign_protected_actions(
965        &self,
966        req: crate::proto::chain::guard::v1::BatchSignProtectedActionsRequest,
967    ) -> crate::errors::Result<crate::models::BatchSignProtectedActionsResult> {
968        use crate::codecs::decode::batch_sign_from_proto;
969        let client = self.connect_client();
970        let resp = {
971            use super::unary;
972            unary::await_auth(
973                &self.ctx.factory,
974                "/chain.guard.v1.GuardSignerService/BatchSignProtectedActions",
975                req,
976                |req, opts| client.batch_sign_protected_actions_with_options(req, opts),
977            )
978            .await?
979            .into_owned()
980        };
981        Ok(batch_sign_from_proto(&resp))
982    }
983
984    pub async fn rotate_wallet(
985        &self,
986        req: crate::proto::chain::guard::v1::RotateGuardSignerWalletRequest,
987    ) -> crate::errors::Result<crate::models::RotateGuardSignerWalletResult> {
988        use crate::codecs::decode::rotate_wallet_from_proto;
989        let client = self.connect_client();
990        let resp = {
991            use super::unary;
992            unary::await_auth(
993                &self.ctx.factory,
994                "/chain.guard.v1.GuardSignerService/RotateGuardSignerWallet",
995                req,
996                |req, opts| client.rotate_guard_signer_wallet_with_options(req, opts),
997            )
998            .await?
999            .into_owned()
1000        };
1001        Ok(rotate_wallet_from_proto(&resp))
1002    }
1003
1004    pub async fn export_wallet(
1005        &self,
1006        req: crate::proto::chain::guard::v1::ExportGuardSignerWalletRequest,
1007    ) -> crate::errors::Result<crate::models::ExportGuardSignerWalletResult> {
1008        use crate::codecs::decode::export_wallet_from_proto;
1009        let client = self.connect_client();
1010        let resp = {
1011            use super::unary;
1012            unary::await_auth(
1013                &self.ctx.factory,
1014                "/chain.guard.v1.GuardSignerService/ExportGuardSignerWallet",
1015                req,
1016                |req, opts| client.export_guard_signer_wallet_with_options(req, opts),
1017            )
1018            .await?
1019            .into_owned()
1020        };
1021        Ok(export_wallet_from_proto(&resp))
1022    }
1023}
1024
1025impl SocialVerificationService {
1026    pub async fn start(
1027        &self,
1028        provider: &str,
1029        method: &str,
1030        handle: &str,
1031    ) -> crate::errors::Result<crate::models::ApiData> {
1032        use crate::codecs::decode::api_data_from_proto;
1033        use crate::proto::auth::v1::StartSocialVerificationRequest;
1034        let req = StartSocialVerificationRequest {
1035            provider: social_provider_enum(provider).into(),
1036            method: social_method_enum(method).into(),
1037            handle: handle.to_owned(),
1038            ..Default::default()
1039        };
1040        let client = self.connect_client();
1041        let resp = {
1042            use super::unary;
1043            unary::await_auth(
1044                &self.ctx.factory,
1045                "/auth.v1.SocialVerificationService/StartSocialVerification",
1046                req,
1047                |req, opts| client.start_social_verification_with_options(req, opts),
1048            )
1049            .await?
1050            .into_owned()
1051        };
1052        Ok(api_data_from_proto(&resp))
1053    }
1054
1055    pub async fn mark_ready(
1056        &self,
1057        provider: &str,
1058    ) -> crate::errors::Result<crate::models::ApiData> {
1059        use crate::codecs::decode::api_data_from_proto;
1060        use crate::proto::auth::v1::SocialVerificationReadyRequest;
1061        let req = SocialVerificationReadyRequest {
1062            provider: social_provider_enum(provider).into(),
1063            ..Default::default()
1064        };
1065        let client = self.connect_client();
1066        let resp = {
1067            use super::unary;
1068            unary::await_auth(
1069                &self.ctx.factory,
1070                "/auth.v1.SocialVerificationService/SocialVerificationReady",
1071                req,
1072                |req, opts| client.social_verification_ready_with_options(req, opts),
1073            )
1074            .await?
1075            .into_owned()
1076        };
1077        Ok(api_data_from_proto(&resp))
1078    }
1079
1080    pub async fn get(&self, provider: &str) -> crate::errors::Result<crate::models::ApiData> {
1081        use crate::codecs::decode::api_data_from_proto;
1082        use crate::proto::auth::v1::GetSocialVerificationRequest;
1083        let req = GetSocialVerificationRequest {
1084            provider: social_provider_enum(provider).into(),
1085            ..Default::default()
1086        };
1087        let client = self.connect_client();
1088        let resp = {
1089            use super::unary;
1090            unary::await_auth(
1091                &self.ctx.factory,
1092                "/auth.v1.SocialVerificationService/GetSocialVerification",
1093                req,
1094                |req, opts| client.get_social_verification_with_options(req, opts),
1095            )
1096            .await?
1097            .into_owned()
1098        };
1099        Ok(api_data_from_proto(&resp))
1100    }
1101}
1102
1103fn social_provider_enum(v: &str) -> crate::proto::auth::v1::SocialProvider {
1104    use crate::proto::auth::v1::SocialProvider;
1105    match v.trim().to_ascii_lowercase().as_str() {
1106        "twitter" => SocialProvider::TWITTER,
1107        "discord" => SocialProvider::DISCORD,
1108        _ => SocialProvider::PROVIDER_UNSPECIFIED,
1109    }
1110}
1111
1112fn social_method_enum(v: &str) -> crate::proto::auth::v1::SocialVerificationMethod {
1113    use crate::proto::auth::v1::SocialVerificationMethod;
1114    match v.trim().to_ascii_lowercase().as_str() {
1115        "profile" => SocialVerificationMethod::METHOD_PROFILE,
1116        "channel" => SocialVerificationMethod::METHOD_CHANNEL,
1117        "dm" => SocialVerificationMethod::METHOD_DM,
1118        _ => SocialVerificationMethod::METHOD_UNSPECIFIED,
1119    }
1120}
1121
1122impl LayoutService {
1123    pub async fn get_layouts(
1124        &self,
1125        req: crate::proto::layout::v1::GetLayoutsRequest,
1126    ) -> crate::errors::Result<crate::models::ApiData> {
1127        use crate::codecs::decode::api_data_from_proto;
1128        let client = self.connect_client();
1129        let resp = {
1130            use super::unary;
1131            unary::await_auth(
1132                &self.ctx.factory,
1133                "/layout.v1.LayoutService/GetLayouts",
1134                req,
1135                |req, opts| client.get_layouts_with_options(req, opts),
1136            )
1137            .await?
1138            .into_owned()
1139        };
1140        Ok(api_data_from_proto(&resp))
1141    }
1142
1143    pub async fn get_layout(
1144        &self,
1145        req: crate::proto::layout::v1::GetLayoutRequest,
1146    ) -> crate::errors::Result<crate::models::ApiData> {
1147        use crate::codecs::decode::api_data_from_proto;
1148        let client = self.connect_client();
1149        let resp = {
1150            use super::unary;
1151            unary::await_auth(
1152                &self.ctx.factory,
1153                "/layout.v1.LayoutService/GetLayout",
1154                req,
1155                |req, opts| client.get_layout_with_options(req, opts),
1156            )
1157            .await?
1158            .into_owned()
1159        };
1160        Ok(api_data_from_proto(&resp))
1161    }
1162
1163    pub async fn upsert_layout(
1164        &self,
1165        req: crate::proto::layout::v1::UpsertLayoutRequest,
1166    ) -> crate::errors::Result<crate::models::ApiData> {
1167        use crate::codecs::decode::api_data_from_proto;
1168        let client = self.connect_client();
1169        let resp = {
1170            use super::unary;
1171            unary::await_auth(
1172                &self.ctx.factory,
1173                "/layout.v1.LayoutService/UpsertLayout",
1174                req,
1175                |req, opts| client.upsert_layout_with_options(req, opts),
1176            )
1177            .await?
1178            .into_owned()
1179        };
1180        Ok(api_data_from_proto(&resp))
1181    }
1182
1183    pub async fn delete_layout(
1184        &self,
1185        req: crate::proto::layout::v1::DeleteLayoutRequest,
1186    ) -> crate::errors::Result<crate::models::ApiData> {
1187        use crate::codecs::decode::api_data_from_proto;
1188        let client = self.connect_client();
1189        let resp = {
1190            use super::unary;
1191            unary::await_auth(
1192                &self.ctx.factory,
1193                "/layout.v1.LayoutService/DeleteLayout",
1194                req,
1195                |req, opts| client.delete_layout_with_options(req, opts),
1196            )
1197            .await?
1198            .into_owned()
1199        };
1200        Ok(api_data_from_proto(&resp))
1201    }
1202
1203    pub async fn resolve_layout_share_token(
1204        &self,
1205        req: crate::proto::layout::v1::ResolveLayoutShareTokenRequest,
1206    ) -> crate::errors::Result<crate::models::ApiData> {
1207        use crate::codecs::decode::api_data_from_proto;
1208        let client = self.connect_client();
1209        let resp = {
1210            use super::unary;
1211            unary::await_auth(
1212                &self.ctx.factory,
1213                "/layout.v1.LayoutService/ResolveLayoutShareToken",
1214                req,
1215                |req, opts| client.resolve_layout_share_token_with_options(req, opts),
1216            )
1217            .await?
1218            .into_owned()
1219        };
1220        Ok(api_data_from_proto(&resp))
1221    }
1222
1223    pub async fn create_layout_share_link(
1224        &self,
1225        req: crate::proto::layout::v1::CreateLayoutShareLinkRequest,
1226    ) -> crate::errors::Result<crate::models::ApiData> {
1227        use crate::codecs::decode::api_data_from_proto;
1228        let client = self.connect_client();
1229        let resp = {
1230            use super::unary;
1231            unary::await_auth(
1232                &self.ctx.factory,
1233                "/layout.v1.LayoutService/CreateLayoutShareLink",
1234                req,
1235                |req, opts| client.create_layout_share_link_with_options(req, opts),
1236            )
1237            .await?
1238            .into_owned()
1239        };
1240        Ok(api_data_from_proto(&resp))
1241    }
1242
1243    pub async fn revoke_layout_share_link(
1244        &self,
1245        req: crate::proto::layout::v1::RevokeLayoutShareLinkRequest,
1246    ) -> crate::errors::Result<crate::models::ApiData> {
1247        use crate::codecs::decode::api_data_from_proto;
1248        let client = self.connect_client();
1249        let resp = {
1250            use super::unary;
1251            unary::await_auth(
1252                &self.ctx.factory,
1253                "/layout.v1.LayoutService/RevokeLayoutShareLink",
1254                req,
1255                |req, opts| client.revoke_layout_share_link_with_options(req, opts),
1256            )
1257            .await?
1258            .into_owned()
1259        };
1260        Ok(api_data_from_proto(&resp))
1261    }
1262
1263    pub async fn list_owner_published_layouts(
1264        &self,
1265        req: crate::proto::layout::v1::ListOwnerPublishedLayoutsRequest,
1266    ) -> crate::errors::Result<crate::models::ApiData> {
1267        use crate::codecs::decode::api_data_from_proto;
1268        let client = self.connect_client();
1269        let resp = {
1270            use super::unary;
1271            unary::await_auth(
1272                &self.ctx.factory,
1273                "/layout.v1.LayoutService/ListOwnerPublishedLayouts",
1274                req,
1275                |req, opts| client.list_owner_published_layouts_with_options(req, opts),
1276            )
1277            .await?
1278            .into_owned()
1279        };
1280        Ok(api_data_from_proto(&resp))
1281    }
1282
1283    pub async fn publish_layout(
1284        &self,
1285        req: crate::proto::layout::v1::PublishLayoutRequest,
1286    ) -> crate::errors::Result<crate::models::ApiData> {
1287        use crate::codecs::decode::api_data_from_proto;
1288        let client = self.connect_client();
1289        let resp = {
1290            use super::unary;
1291            unary::await_auth(
1292                &self.ctx.factory,
1293                "/layout.v1.LayoutService/PublishLayout",
1294                req,
1295                |req, opts| client.publish_layout_with_options(req, opts),
1296            )
1297            .await?
1298            .into_owned()
1299        };
1300        Ok(api_data_from_proto(&resp))
1301    }
1302
1303    pub async fn unpublish_layout(
1304        &self,
1305        req: crate::proto::layout::v1::UnpublishLayoutRequest,
1306    ) -> crate::errors::Result<crate::models::ApiData> {
1307        use crate::codecs::decode::api_data_from_proto;
1308        let client = self.connect_client();
1309        let resp = {
1310            use super::unary;
1311            unary::await_auth(
1312                &self.ctx.factory,
1313                "/layout.v1.LayoutService/UnpublishLayout",
1314                req,
1315                |req, opts| client.unpublish_layout_with_options(req, opts),
1316            )
1317            .await?
1318            .into_owned()
1319        };
1320        Ok(api_data_from_proto(&resp))
1321    }
1322
1323    pub async fn list_layout_template_versions(
1324        &self,
1325        req: crate::proto::layout::v1::ListLayoutTemplateVersionsRequest,
1326    ) -> crate::errors::Result<crate::models::ApiData> {
1327        use crate::codecs::decode::api_data_from_proto;
1328        let client = self.connect_client();
1329        let resp = {
1330            use super::unary;
1331            unary::await_auth(
1332                &self.ctx.factory,
1333                "/layout.v1.LayoutService/ListLayoutTemplateVersions",
1334                req,
1335                |req, opts| client.list_layout_template_versions_with_options(req, opts),
1336            )
1337            .await?
1338            .into_owned()
1339        };
1340        Ok(api_data_from_proto(&resp))
1341    }
1342
1343    pub async fn get_layout_template_version(
1344        &self,
1345        req: crate::proto::layout::v1::GetLayoutTemplateVersionRequest,
1346    ) -> crate::errors::Result<crate::models::ApiData> {
1347        use crate::codecs::decode::api_data_from_proto;
1348        let client = self.connect_client();
1349        let resp = {
1350            use super::unary;
1351            unary::await_auth(
1352                &self.ctx.factory,
1353                "/layout.v1.LayoutService/GetLayoutTemplateVersion",
1354                req,
1355                |req, opts| client.get_layout_template_version_with_options(req, opts),
1356            )
1357            .await?
1358            .into_owned()
1359        };
1360        Ok(api_data_from_proto(&resp))
1361    }
1362
1363    pub async fn set_layout_template_subscription(
1364        &self,
1365        req: crate::proto::layout::v1::SetLayoutTemplateSubscriptionRequest,
1366    ) -> crate::errors::Result<crate::models::ApiData> {
1367        use crate::codecs::decode::api_data_from_proto;
1368        let client = self.connect_client();
1369        let resp = {
1370            use super::unary;
1371            unary::await_auth(
1372                &self.ctx.factory,
1373                "/layout.v1.LayoutService/SetLayoutTemplateSubscription",
1374                req,
1375                |req, opts| client.set_layout_template_subscription_with_options(req, opts),
1376            )
1377            .await?
1378            .into_owned()
1379        };
1380        Ok(api_data_from_proto(&resp))
1381    }
1382
1383    pub async fn delete_layout_template_subscription(
1384        &self,
1385        req: crate::proto::layout::v1::DeleteLayoutTemplateSubscriptionRequest,
1386    ) -> crate::errors::Result<crate::models::ApiData> {
1387        use crate::codecs::decode::api_data_from_proto;
1388        let client = self.connect_client();
1389        let resp = {
1390            use super::unary;
1391            unary::await_auth(
1392                &self.ctx.factory,
1393                "/layout.v1.LayoutService/DeleteLayoutTemplateSubscription",
1394                req,
1395                |req, opts| client.delete_layout_template_subscription_with_options(req, opts),
1396            )
1397            .await?
1398            .into_owned()
1399        };
1400        Ok(api_data_from_proto(&resp))
1401    }
1402
1403    pub async fn list_my_layout_template_subscriptions(
1404        &self,
1405        req: crate::proto::layout::v1::ListMyLayoutTemplateSubscriptionsRequest,
1406    ) -> crate::errors::Result<crate::models::ApiData> {
1407        use crate::codecs::decode::api_data_from_proto;
1408        let client = self.connect_client();
1409        let resp = {
1410            use super::unary;
1411            unary::await_auth(
1412                &self.ctx.factory,
1413                "/layout.v1.LayoutService/ListMyLayoutTemplateSubscriptions",
1414                req,
1415                |req, opts| client.list_my_layout_template_subscriptions_with_options(req, opts),
1416            )
1417            .await?
1418            .into_owned()
1419        };
1420        Ok(api_data_from_proto(&resp))
1421    }
1422}
1423
1424impl PolychartService {
1425    pub async fn get_market_layers(
1426        &self,
1427        req: crate::proto::polychart::v1::GetMarketLayersRequest,
1428    ) -> crate::errors::Result<crate::models::ApiData> {
1429        use crate::codecs::decode::api_data_from_proto;
1430        let client = self.connect_client();
1431        let resp = {
1432            use super::unary;
1433            unary::await_auth(
1434                &self.ctx.factory,
1435                "/polychart.v1.PolychartService/GetMarketLayers",
1436                req,
1437                |req, opts| client.get_market_layers_with_options(req, opts),
1438            )
1439            .await?
1440            .into_owned()
1441        };
1442        Ok(api_data_from_proto(&resp))
1443    }
1444
1445    pub async fn list_inbox_market_layers(
1446        &self,
1447        req: crate::proto::polychart::v1::ListInboxMarketLayersRequest,
1448    ) -> crate::errors::Result<crate::models::ApiData> {
1449        use crate::codecs::decode::api_data_from_proto;
1450        let client = self.connect_client();
1451        let resp = {
1452            use super::unary;
1453            unary::await_auth(
1454                &self.ctx.factory,
1455                "/polychart.v1.PolychartService/ListInboxMarketLayers",
1456                req,
1457                |req, opts| client.list_inbox_market_layers_with_options(req, opts),
1458            )
1459            .await?
1460            .into_owned()
1461        };
1462        Ok(api_data_from_proto(&resp))
1463    }
1464
1465    pub async fn get_layer_snapshot(
1466        &self,
1467        req: crate::proto::polychart::v1::GetLayerSnapshotRequest,
1468    ) -> crate::errors::Result<crate::models::ApiData> {
1469        use crate::codecs::decode::api_data_from_proto;
1470        let client = self.connect_client();
1471        let resp = {
1472            use super::unary;
1473            unary::await_auth(
1474                &self.ctx.factory,
1475                "/polychart.v1.PolychartService/GetLayerSnapshot",
1476                req,
1477                |req, opts| client.get_layer_snapshot_with_options(req, opts),
1478            )
1479            .await?
1480            .into_owned()
1481        };
1482        Ok(api_data_from_proto(&resp))
1483    }
1484
1485    pub async fn get_layer_subscribe_tokens(
1486        &self,
1487        req: crate::proto::polychart::v1::GetLayerSubscribeTokensRequest,
1488    ) -> crate::errors::Result<crate::models::ApiData> {
1489        use crate::codecs::decode::api_data_from_proto;
1490        let client = self.connect_client();
1491        let resp = {
1492            use super::unary;
1493            unary::await_auth(
1494                &self.ctx.factory,
1495                "/polychart.v1.PolychartService/GetLayerSubscribeTokens",
1496                req,
1497                |req, opts| client.get_layer_subscribe_tokens_with_options(req, opts),
1498            )
1499            .await?
1500            .into_owned()
1501        };
1502        Ok(api_data_from_proto(&resp))
1503    }
1504
1505    pub async fn resolve_layer_share_token(
1506        &self,
1507        req: crate::proto::polychart::v1::ResolveLayerShareTokenRequest,
1508    ) -> crate::errors::Result<crate::models::ApiData> {
1509        use crate::codecs::decode::api_data_from_proto;
1510        let client = self.connect_client();
1511        let resp = {
1512            use super::unary;
1513            unary::await_auth(
1514                &self.ctx.factory,
1515                "/polychart.v1.PolychartService/ResolveLayerShareToken",
1516                req,
1517                |req, opts| client.resolve_layer_share_token_with_options(req, opts),
1518            )
1519            .await?
1520            .into_owned()
1521        };
1522        Ok(api_data_from_proto(&resp))
1523    }
1524
1525    pub async fn create_layer_share_link(
1526        &self,
1527        req: crate::proto::polychart::v1::CreateLayerShareLinkRequest,
1528    ) -> crate::errors::Result<crate::models::ApiData> {
1529        use crate::codecs::decode::api_data_from_proto;
1530        let client = self.connect_client();
1531        let resp = {
1532            use super::unary;
1533            unary::await_auth(
1534                &self.ctx.factory,
1535                "/polychart.v1.PolychartService/CreateLayerShareLink",
1536                req,
1537                |req, opts| client.create_layer_share_link_with_options(req, opts),
1538            )
1539            .await?
1540            .into_owned()
1541        };
1542        Ok(api_data_from_proto(&resp))
1543    }
1544
1545    pub async fn revoke_layer_share_link(
1546        &self,
1547        req: crate::proto::polychart::v1::RevokeLayerShareLinkRequest,
1548    ) -> crate::errors::Result<crate::models::ApiData> {
1549        use crate::codecs::decode::api_data_from_proto;
1550        let client = self.connect_client();
1551        let resp = {
1552            use super::unary;
1553            unary::await_auth(
1554                &self.ctx.factory,
1555                "/polychart.v1.PolychartService/RevokeLayerShareLink",
1556                req,
1557                |req, opts| client.revoke_layer_share_link_with_options(req, opts),
1558            )
1559            .await?
1560            .into_owned()
1561        };
1562        Ok(api_data_from_proto(&resp))
1563    }
1564
1565    pub async fn list_owner_published_layers(
1566        &self,
1567        req: crate::proto::polychart::v1::ListOwnerPublishedLayersRequest,
1568    ) -> crate::errors::Result<crate::models::ApiData> {
1569        use crate::codecs::decode::api_data_from_proto;
1570        let client = self.connect_client();
1571        let resp = {
1572            use super::unary;
1573            unary::await_auth(
1574                &self.ctx.factory,
1575                "/polychart.v1.PolychartService/ListOwnerPublishedLayers",
1576                req,
1577                |req, opts| client.list_owner_published_layers_with_options(req, opts),
1578            )
1579            .await?
1580            .into_owned()
1581        };
1582        Ok(api_data_from_proto(&resp))
1583    }
1584
1585    pub async fn publish_layer(
1586        &self,
1587        req: crate::proto::polychart::v1::PublishLayerRequest,
1588    ) -> crate::errors::Result<crate::models::ApiData> {
1589        use crate::codecs::decode::api_data_from_proto;
1590        let client = self.connect_client();
1591        let resp = {
1592            use super::unary;
1593            unary::await_auth(
1594                &self.ctx.factory,
1595                "/polychart.v1.PolychartService/PublishLayer",
1596                req,
1597                |req, opts| client.publish_layer_with_options(req, opts),
1598            )
1599            .await?
1600            .into_owned()
1601        };
1602        Ok(api_data_from_proto(&resp))
1603    }
1604
1605    pub async fn unpublish_layer(
1606        &self,
1607        req: crate::proto::polychart::v1::UnpublishLayerRequest,
1608    ) -> crate::errors::Result<crate::models::ApiData> {
1609        use crate::codecs::decode::api_data_from_proto;
1610        let client = self.connect_client();
1611        let resp = {
1612            use super::unary;
1613            unary::await_auth(
1614                &self.ctx.factory,
1615                "/polychart.v1.PolychartService/UnpublishLayer",
1616                req,
1617                |req, opts| client.unpublish_layer_with_options(req, opts),
1618            )
1619            .await?
1620            .into_owned()
1621        };
1622        Ok(api_data_from_proto(&resp))
1623    }
1624
1625    pub async fn upsert_layer(
1626        &self,
1627        req: crate::proto::polychart::v1::UpsertLayerRequest,
1628    ) -> crate::errors::Result<crate::models::ApiData> {
1629        use crate::codecs::decode::api_data_from_proto;
1630        let client = self.connect_client();
1631        let resp = {
1632            use super::unary;
1633            unary::await_auth(
1634                &self.ctx.factory,
1635                "/polychart.v1.PolychartService/UpsertLayer",
1636                req,
1637                |req, opts| client.upsert_layer_with_options(req, opts),
1638            )
1639            .await?
1640            .into_owned()
1641        };
1642        Ok(api_data_from_proto(&resp))
1643    }
1644
1645    pub async fn delete_layer(
1646        &self,
1647        req: crate::proto::polychart::v1::DeleteLayerRequest,
1648    ) -> crate::errors::Result<crate::models::ApiData> {
1649        use crate::codecs::decode::api_data_from_proto;
1650        let client = self.connect_client();
1651        let resp = {
1652            use super::unary;
1653            unary::await_auth(
1654                &self.ctx.factory,
1655                "/polychart.v1.PolychartService/DeleteLayer",
1656                req,
1657                |req, opts| client.delete_layer_with_options(req, opts),
1658            )
1659            .await?
1660            .into_owned()
1661        };
1662        Ok(api_data_from_proto(&resp))
1663    }
1664
1665    pub async fn upsert_drawing(
1666        &self,
1667        req: crate::proto::polychart::v1::UpsertDrawingRequest,
1668    ) -> crate::errors::Result<crate::models::ApiData> {
1669        use crate::codecs::decode::api_data_from_proto;
1670        let client = self.connect_client();
1671        let resp = {
1672            use super::unary;
1673            unary::await_auth(
1674                &self.ctx.factory,
1675                "/polychart.v1.PolychartService/UpsertDrawing",
1676                req,
1677                |req, opts| client.upsert_drawing_with_options(req, opts),
1678            )
1679            .await?
1680            .into_owned()
1681        };
1682        Ok(api_data_from_proto(&resp))
1683    }
1684
1685    pub async fn delete_drawing(
1686        &self,
1687        req: crate::proto::polychart::v1::DeleteDrawingRequest,
1688    ) -> crate::errors::Result<crate::models::ApiData> {
1689        use crate::codecs::decode::api_data_from_proto;
1690        let client = self.connect_client();
1691        let resp = {
1692            use super::unary;
1693            unary::await_auth(
1694                &self.ctx.factory,
1695                "/polychart.v1.PolychartService/DeleteDrawing",
1696                req,
1697                |req, opts| client.delete_drawing_with_options(req, opts),
1698            )
1699            .await?
1700            .into_owned()
1701        };
1702        Ok(api_data_from_proto(&resp))
1703    }
1704
1705    pub async fn set_layer_subscriptions(
1706        &self,
1707        req: crate::proto::polychart::v1::SetLayerSubscriptionsRequest,
1708    ) -> crate::errors::Result<crate::models::ApiData> {
1709        use crate::codecs::decode::api_data_from_proto;
1710        let client = self.connect_client();
1711        let resp = {
1712            use super::unary;
1713            unary::await_auth(
1714                &self.ctx.factory,
1715                "/polychart.v1.PolychartService/SetLayerSubscriptions",
1716                req,
1717                |req, opts| client.set_layer_subscriptions_with_options(req, opts),
1718            )
1719            .await?
1720            .into_owned()
1721        };
1722        Ok(api_data_from_proto(&resp))
1723    }
1724}
1725
1726impl WhiteboardService {
1727    pub async fn create_board(
1728        &self,
1729        req: crate::proto::collab::v1::CreateBoardRequest,
1730    ) -> crate::errors::Result<crate::models::ApiData> {
1731        use crate::codecs::decode::api_data_from_proto;
1732        let client = self.connect_client();
1733        let resp = {
1734            use super::unary;
1735            unary::await_auth(
1736                &self.ctx.factory,
1737                "/collab.v1.WhiteboardService/CreateBoard",
1738                req,
1739                |req, opts| client.create_board_with_options(req, opts),
1740            )
1741            .await?
1742            .into_owned()
1743        };
1744        Ok(api_data_from_proto(&resp))
1745    }
1746
1747    pub async fn get_board(
1748        &self,
1749        req: crate::proto::collab::v1::GetBoardRequest,
1750    ) -> crate::errors::Result<crate::models::ApiData> {
1751        use crate::codecs::decode::api_data_from_proto;
1752        let client = self.connect_client();
1753        let resp = {
1754            use super::unary;
1755            unary::await_auth(
1756                &self.ctx.factory,
1757                "/collab.v1.WhiteboardService/GetBoard",
1758                req,
1759                |req, opts| client.get_board_with_options(req, opts),
1760            )
1761            .await?
1762            .into_owned()
1763        };
1764        Ok(api_data_from_proto(&resp))
1765    }
1766
1767    pub async fn list_boards(
1768        &self,
1769        req: crate::proto::collab::v1::ListBoardsRequest,
1770    ) -> crate::errors::Result<crate::models::ApiData> {
1771        use crate::codecs::decode::api_data_from_proto;
1772        let client = self.connect_client();
1773        let resp = {
1774            use super::unary;
1775            unary::await_auth(
1776                &self.ctx.factory,
1777                "/collab.v1.WhiteboardService/ListBoards",
1778                req,
1779                |req, opts| client.list_boards_with_options(req, opts),
1780            )
1781            .await?
1782            .into_owned()
1783        };
1784        Ok(api_data_from_proto(&resp))
1785    }
1786
1787    pub async fn update_board(
1788        &self,
1789        req: crate::proto::collab::v1::UpdateBoardRequest,
1790    ) -> crate::errors::Result<crate::models::ApiData> {
1791        use crate::codecs::decode::api_data_from_proto;
1792        let client = self.connect_client();
1793        let resp = {
1794            use super::unary;
1795            unary::await_auth(
1796                &self.ctx.factory,
1797                "/collab.v1.WhiteboardService/UpdateBoard",
1798                req,
1799                |req, opts| client.update_board_with_options(req, opts),
1800            )
1801            .await?
1802            .into_owned()
1803        };
1804        Ok(api_data_from_proto(&resp))
1805    }
1806
1807    pub async fn update_board_acl(
1808        &self,
1809        req: crate::proto::collab::v1::UpdateBoardAclRequest,
1810    ) -> crate::errors::Result<crate::models::ApiData> {
1811        use crate::codecs::decode::api_data_from_proto;
1812        let client = self.connect_client();
1813        let resp = {
1814            use super::unary;
1815            unary::await_auth(
1816                &self.ctx.factory,
1817                "/collab.v1.WhiteboardService/UpdateBoardAcl",
1818                req,
1819                |req, opts| client.update_board_acl_with_options(req, opts),
1820            )
1821            .await?
1822            .into_owned()
1823        };
1824        Ok(api_data_from_proto(&resp))
1825    }
1826
1827    pub async fn archive_board(
1828        &self,
1829        req: crate::proto::collab::v1::ArchiveBoardRequest,
1830    ) -> crate::errors::Result<crate::models::ApiData> {
1831        use crate::codecs::decode::api_data_from_proto;
1832        let client = self.connect_client();
1833        let resp = {
1834            use super::unary;
1835            unary::await_auth(
1836                &self.ctx.factory,
1837                "/collab.v1.WhiteboardService/ArchiveBoard",
1838                req,
1839                |req, opts| client.archive_board_with_options(req, opts),
1840            )
1841            .await?
1842            .into_owned()
1843        };
1844        Ok(api_data_from_proto(&resp))
1845    }
1846
1847    pub async fn mint_join_token(
1848        &self,
1849        req: crate::proto::collab::v1::MintJoinTokenRequest,
1850    ) -> crate::errors::Result<crate::models::ApiData> {
1851        use crate::codecs::decode::api_data_from_proto;
1852        let client = self.connect_client();
1853        let resp = {
1854            use super::unary;
1855            unary::await_auth(
1856                &self.ctx.factory,
1857                "/collab.v1.WhiteboardService/MintJoinToken",
1858                req,
1859                |req, opts| client.mint_join_token_with_options(req, opts),
1860            )
1861            .await?
1862            .into_owned()
1863        };
1864        Ok(api_data_from_proto(&resp))
1865    }
1866}
1867
1868#[cfg(test)]
1869mod tests {
1870    use super::internal_transfer_amount_e18;
1871    use crate::models::CreateInternalTransferParams;
1872    use crate::types::{AssetAmount, QuantityDomain};
1873
1874    #[test]
1875    fn internal_transfer_amount_is_always_exact_e18() {
1876        let amount =
1877            AssetAmount::from_scaled(125, Some(2), QuantityDomain::LedgerE18, Some(7)).unwrap();
1878        let wire = internal_transfer_amount_e18(&amount, Some(2), 7).unwrap();
1879        assert_eq!(
1880            (u128::from(wire.hi) << 64) | u128::from(wire.lo),
1881            1_250_000_000_000_000_000
1882        );
1883
1884        let inexact =
1885            AssetAmount::from_scaled(126, Some(19), QuantityDomain::LedgerE18, Some(7)).unwrap();
1886        assert!(internal_transfer_amount_e18(&inexact, Some(19), 7).is_err());
1887    }
1888
1889    #[test]
1890    fn internal_transfer_rejects_missing_amount_scale_before_transport() {
1891        let amount = AssetAmount::from_scaled(1, None, QuantityDomain::LedgerE18, Some(7)).unwrap();
1892        let err = internal_transfer_amount_e18(&amount, None, 7)
1893            .expect_err("missing scale must not silently mean e18");
1894        assert!(err.to_string().contains("amount scale is required"));
1895    }
1896
1897    #[tokio::test]
1898    async fn internal_transfer_requires_destination_before_transport() {
1899        let client = crate::Client::new(crate::Config {
1900            hydrate_catalogs: false,
1901            ..Default::default()
1902        })
1903        .unwrap();
1904        let params = CreateInternalTransferParams {
1905            asset_id: 7,
1906            quantity: AssetAmount::from_scaled(100, Some(18), QuantityDomain::LedgerE18, Some(7))
1907                .unwrap(),
1908            idempotency_key: "missing-destination".into(),
1909            subaccount_id: None,
1910            destination_account_id: None,
1911            destination_subaccount_id: None,
1912            destination_smart_account_address: None,
1913            quantity_scale: Some(18),
1914        };
1915
1916        let err = client
1917            .internal_transfers
1918            .create(params.clone())
1919            .await
1920            .unwrap_err();
1921        assert!(err.to_string().contains("requires exactly one"));
1922
1923        let multiple = CreateInternalTransferParams {
1924            destination_account_id: Some("2".into()),
1925            destination_subaccount_id: Some("3".into()),
1926            idempotency_key: "multiple-destinations".into(),
1927            ..params.clone()
1928        };
1929        let err = client
1930            .internal_transfers
1931            .create(multiple)
1932            .await
1933            .unwrap_err();
1934        assert!(err.to_string().contains("requires exactly one"));
1935
1936        let empty_key = CreateInternalTransferParams {
1937            destination_account_id: Some("2".into()),
1938            idempotency_key: " ".into(),
1939            ..params.clone()
1940        };
1941        let err = client
1942            .internal_transfers
1943            .create(empty_key)
1944            .await
1945            .unwrap_err();
1946        assert!(err.to_string().contains("non-empty idempotency_key"));
1947
1948        let whitespace_destination = CreateInternalTransferParams {
1949            destination_smart_account_address: Some("   ".into()),
1950            ..params
1951        };
1952        let err = client
1953            .internal_transfers
1954            .create(whitespace_destination)
1955            .await
1956            .unwrap_err();
1957        assert!(err.to_string().contains("requires exactly one"));
1958    }
1959}