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        mut 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        if req.limit == 0 {
827            req.limit = 50;
828        }
829        let client = self.connect_client();
830        let resp = {
831            use super::unary;
832            unary::await_auth(
833                &self.ctx.factory,
834                "/chain.lifecycle.v1.LifecycleReadService/ListFlowsByTx",
835                req,
836                |req, opts| client.list_flows_by_tx_with_options(req, opts),
837            )
838            .await?
839            .into_owned()
840        };
841        Ok(flows_by_tx_list_from_proto(&resp))
842    }
843
844    /// Return all matches in the requested page for a transaction lookup.
845    ///
846    /// A chain transaction can contain multiple bundled lifecycle flows.
847    /// Follow `next_page_token` or call [`Self::list_flows_by_tx`] to paginate.
848    pub async fn get_flow_by_tx(
849        &self,
850        mut req: crate::proto::chain::lifecycle::v1::ListFlowsByTxRequest,
851    ) -> crate::errors::Result<crate::models::LifecycleFlowsList> {
852        use crate::codecs::decode::flow_from_get_by_tx_response;
853        if req.limit == 0 {
854            req.limit = 50;
855        }
856        let client = self.connect_client();
857        let resp = {
858            use super::unary;
859            unary::await_auth(
860                &self.ctx.factory,
861                "/chain.lifecycle.v1.LifecycleReadService/ListFlowsByTx",
862                req,
863                |req, opts| client.list_flows_by_tx_with_options(req, opts),
864            )
865            .await?
866            .into_owned()
867        };
868        Ok(flow_from_get_by_tx_response(&resp))
869    }
870
871    /// Subscribe to open lifecycle flow summaries.
872    ///
873    /// When `account_id` is `Some`, uses the private account channel; otherwise
874    /// the public open-flows channel (Go `SubscribeOpenFlows` parity).
875    pub async fn subscribe_open_flows(
876        &self,
877        account_id: Option<&str>,
878    ) -> crate::errors::Result<
879        crate::realtime::TypedSubscription<crate::models::LifecycleFlowSummary>,
880    > {
881        if let Some(account) = account_id.filter(|s| !s.trim().is_empty()) {
882            let channel = format!("private:chain:lifecycle:flows:{account}:proto");
883            self.ctx
884                .realtime
885                .subscribe_proto(&channel, crate::codecs::decode::flow_summary_from_bytes)
886                .await
887        } else {
888            self.ctx
889                .realtime
890                .subscribe_proto(
891                    "public:chain:lifecycle:flows:proto",
892                    crate::codecs::decode::flow_summary_from_bytes,
893                )
894                .await
895        }
896    }
897
898    /// Subscribe to a single flow's detail updates (requires `realtime` feature).
899    pub async fn subscribe_flow_detail(
900        &self,
901        flow_id: &str,
902    ) -> crate::errors::Result<
903        crate::realtime::TypedSubscription<crate::models::LifecycleFlowSummary>,
904    > {
905        let channel = format!("public:chain:lifecycle:flow:{flow_id}:proto");
906        self.ctx
907            .realtime
908            .subscribe_proto(&channel, crate::codecs::decode::flow_detail_from_bytes)
909            .await
910    }
911}
912
913impl GuardSignerService {
914    pub async fn get_status(
915        &self,
916        req: crate::proto::chain::guard::v1::GetGuardSignerStatusRequest,
917    ) -> crate::errors::Result<Option<crate::models::GuardSignerStatus>> {
918        use crate::codecs::decode::status_from_proto;
919        let client = self.connect_client();
920        let resp = {
921            use super::unary;
922            unary::await_auth(
923                &self.ctx.factory,
924                "/chain.guard.v1.GuardSignerService/GetGuardSignerStatus",
925                req,
926                |req, opts| client.get_guard_signer_status_with_options(req, opts),
927            )
928            .await?
929            .into_owned()
930        };
931        Ok(status_from_proto(&resp))
932    }
933
934    pub async fn create_wallet(
935        &self,
936        req: crate::proto::chain::guard::v1::CreateGuardSignerWalletRequest,
937    ) -> crate::errors::Result<crate::models::CreateGuardSignerWalletResult> {
938        use crate::codecs::decode::create_wallet_from_proto;
939        let client = self.connect_client();
940        let resp = {
941            use super::unary;
942            unary::await_auth(
943                &self.ctx.factory,
944                "/chain.guard.v1.GuardSignerService/CreateGuardSignerWallet",
945                req,
946                |req, opts| client.create_guard_signer_wallet_with_options(req, opts),
947            )
948            .await?
949            .into_owned()
950        };
951        Ok(create_wallet_from_proto(&resp))
952    }
953
954    pub async fn sign_protected_action(
955        &self,
956        req: crate::proto::chain::guard::v1::SignProtectedActionRequest,
957    ) -> crate::errors::Result<Option<crate::models::GuardApproval>> {
958        use crate::codecs::decode::sign_protected_action_from_proto;
959        let client = self.connect_client();
960        let resp = {
961            use super::unary;
962            unary::await_auth(
963                &self.ctx.factory,
964                "/chain.guard.v1.GuardSignerService/SignProtectedAction",
965                req,
966                |req, opts| client.sign_protected_action_with_options(req, opts),
967            )
968            .await?
969            .into_owned()
970        };
971        Ok(sign_protected_action_from_proto(&resp))
972    }
973
974    pub async fn batch_sign_protected_actions(
975        &self,
976        req: crate::proto::chain::guard::v1::BatchSignProtectedActionsRequest,
977    ) -> crate::errors::Result<crate::models::BatchSignProtectedActionsResult> {
978        use crate::codecs::decode::batch_sign_from_proto;
979        let client = self.connect_client();
980        let resp = {
981            use super::unary;
982            unary::await_auth(
983                &self.ctx.factory,
984                "/chain.guard.v1.GuardSignerService/BatchSignProtectedActions",
985                req,
986                |req, opts| client.batch_sign_protected_actions_with_options(req, opts),
987            )
988            .await?
989            .into_owned()
990        };
991        Ok(batch_sign_from_proto(&resp))
992    }
993
994    pub async fn rotate_wallet(
995        &self,
996        req: crate::proto::chain::guard::v1::RotateGuardSignerWalletRequest,
997    ) -> crate::errors::Result<crate::models::RotateGuardSignerWalletResult> {
998        use crate::codecs::decode::rotate_wallet_from_proto;
999        let client = self.connect_client();
1000        let resp = {
1001            use super::unary;
1002            unary::await_auth(
1003                &self.ctx.factory,
1004                "/chain.guard.v1.GuardSignerService/RotateGuardSignerWallet",
1005                req,
1006                |req, opts| client.rotate_guard_signer_wallet_with_options(req, opts),
1007            )
1008            .await?
1009            .into_owned()
1010        };
1011        Ok(rotate_wallet_from_proto(&resp))
1012    }
1013
1014    pub async fn export_wallet(
1015        &self,
1016        req: crate::proto::chain::guard::v1::ExportGuardSignerWalletRequest,
1017    ) -> crate::errors::Result<crate::models::ExportGuardSignerWalletResult> {
1018        use crate::codecs::decode::export_wallet_from_proto;
1019        let client = self.connect_client();
1020        let resp = {
1021            use super::unary;
1022            unary::await_auth(
1023                &self.ctx.factory,
1024                "/chain.guard.v1.GuardSignerService/ExportGuardSignerWallet",
1025                req,
1026                |req, opts| client.export_guard_signer_wallet_with_options(req, opts),
1027            )
1028            .await?
1029            .into_owned()
1030        };
1031        Ok(export_wallet_from_proto(&resp))
1032    }
1033}
1034
1035impl SocialVerificationService {
1036    pub async fn start(
1037        &self,
1038        provider: &str,
1039        method: &str,
1040        handle: &str,
1041    ) -> crate::errors::Result<crate::models::ApiData> {
1042        use crate::codecs::decode::api_data_from_proto;
1043        use crate::proto::auth::v1::StartSocialVerificationRequest;
1044        let req = StartSocialVerificationRequest {
1045            provider: social_provider_enum(provider).into(),
1046            method: social_method_enum(method).into(),
1047            handle: handle.to_owned(),
1048            ..Default::default()
1049        };
1050        let client = self.connect_client();
1051        let resp = {
1052            use super::unary;
1053            unary::await_auth(
1054                &self.ctx.factory,
1055                "/auth.v1.SocialVerificationService/StartSocialVerification",
1056                req,
1057                |req, opts| client.start_social_verification_with_options(req, opts),
1058            )
1059            .await?
1060            .into_owned()
1061        };
1062        Ok(api_data_from_proto(&resp))
1063    }
1064
1065    pub async fn mark_ready(
1066        &self,
1067        provider: &str,
1068    ) -> crate::errors::Result<crate::models::ApiData> {
1069        use crate::codecs::decode::api_data_from_proto;
1070        use crate::proto::auth::v1::SocialVerificationReadyRequest;
1071        let req = SocialVerificationReadyRequest {
1072            provider: social_provider_enum(provider).into(),
1073            ..Default::default()
1074        };
1075        let client = self.connect_client();
1076        let resp = {
1077            use super::unary;
1078            unary::await_auth(
1079                &self.ctx.factory,
1080                "/auth.v1.SocialVerificationService/SocialVerificationReady",
1081                req,
1082                |req, opts| client.social_verification_ready_with_options(req, opts),
1083            )
1084            .await?
1085            .into_owned()
1086        };
1087        Ok(api_data_from_proto(&resp))
1088    }
1089
1090    pub async fn get(&self, provider: &str) -> crate::errors::Result<crate::models::ApiData> {
1091        use crate::codecs::decode::api_data_from_proto;
1092        use crate::proto::auth::v1::GetSocialVerificationRequest;
1093        let req = GetSocialVerificationRequest {
1094            provider: social_provider_enum(provider).into(),
1095            ..Default::default()
1096        };
1097        let client = self.connect_client();
1098        let resp = {
1099            use super::unary;
1100            unary::await_auth(
1101                &self.ctx.factory,
1102                "/auth.v1.SocialVerificationService/GetSocialVerification",
1103                req,
1104                |req, opts| client.get_social_verification_with_options(req, opts),
1105            )
1106            .await?
1107            .into_owned()
1108        };
1109        Ok(api_data_from_proto(&resp))
1110    }
1111}
1112
1113fn social_provider_enum(v: &str) -> crate::proto::auth::v1::SocialProvider {
1114    use crate::proto::auth::v1::SocialProvider;
1115    match v.trim().to_ascii_lowercase().as_str() {
1116        "twitter" => SocialProvider::TWITTER,
1117        "discord" => SocialProvider::DISCORD,
1118        _ => SocialProvider::PROVIDER_UNSPECIFIED,
1119    }
1120}
1121
1122fn social_method_enum(v: &str) -> crate::proto::auth::v1::SocialVerificationMethod {
1123    use crate::proto::auth::v1::SocialVerificationMethod;
1124    match v.trim().to_ascii_lowercase().as_str() {
1125        "profile" => SocialVerificationMethod::METHOD_PROFILE,
1126        "channel" => SocialVerificationMethod::METHOD_CHANNEL,
1127        "dm" => SocialVerificationMethod::METHOD_DM,
1128        _ => SocialVerificationMethod::METHOD_UNSPECIFIED,
1129    }
1130}
1131
1132impl LayoutService {
1133    pub async fn get_layouts(
1134        &self,
1135        req: crate::proto::layout::v1::GetLayoutsRequest,
1136    ) -> crate::errors::Result<crate::models::ApiData> {
1137        use crate::codecs::decode::api_data_from_proto;
1138        let client = self.connect_client();
1139        let resp = {
1140            use super::unary;
1141            unary::await_auth(
1142                &self.ctx.factory,
1143                "/layout.v1.LayoutService/GetLayouts",
1144                req,
1145                |req, opts| client.get_layouts_with_options(req, opts),
1146            )
1147            .await?
1148            .into_owned()
1149        };
1150        Ok(api_data_from_proto(&resp))
1151    }
1152
1153    pub async fn get_layout(
1154        &self,
1155        req: crate::proto::layout::v1::GetLayoutRequest,
1156    ) -> crate::errors::Result<crate::models::ApiData> {
1157        use crate::codecs::decode::api_data_from_proto;
1158        let client = self.connect_client();
1159        let resp = {
1160            use super::unary;
1161            unary::await_auth(
1162                &self.ctx.factory,
1163                "/layout.v1.LayoutService/GetLayout",
1164                req,
1165                |req, opts| client.get_layout_with_options(req, opts),
1166            )
1167            .await?
1168            .into_owned()
1169        };
1170        Ok(api_data_from_proto(&resp))
1171    }
1172
1173    pub async fn upsert_layout(
1174        &self,
1175        req: crate::proto::layout::v1::UpsertLayoutRequest,
1176    ) -> crate::errors::Result<crate::models::ApiData> {
1177        use crate::codecs::decode::api_data_from_proto;
1178        let client = self.connect_client();
1179        let resp = {
1180            use super::unary;
1181            unary::await_auth(
1182                &self.ctx.factory,
1183                "/layout.v1.LayoutService/UpsertLayout",
1184                req,
1185                |req, opts| client.upsert_layout_with_options(req, opts),
1186            )
1187            .await?
1188            .into_owned()
1189        };
1190        Ok(api_data_from_proto(&resp))
1191    }
1192
1193    pub async fn delete_layout(
1194        &self,
1195        req: crate::proto::layout::v1::DeleteLayoutRequest,
1196    ) -> crate::errors::Result<crate::models::ApiData> {
1197        use crate::codecs::decode::api_data_from_proto;
1198        let client = self.connect_client();
1199        let resp = {
1200            use super::unary;
1201            unary::await_auth(
1202                &self.ctx.factory,
1203                "/layout.v1.LayoutService/DeleteLayout",
1204                req,
1205                |req, opts| client.delete_layout_with_options(req, opts),
1206            )
1207            .await?
1208            .into_owned()
1209        };
1210        Ok(api_data_from_proto(&resp))
1211    }
1212
1213    pub async fn resolve_layout_share_token(
1214        &self,
1215        req: crate::proto::layout::v1::ResolveLayoutShareTokenRequest,
1216    ) -> crate::errors::Result<crate::models::ApiData> {
1217        use crate::codecs::decode::api_data_from_proto;
1218        let client = self.connect_client();
1219        let resp = {
1220            use super::unary;
1221            unary::await_auth(
1222                &self.ctx.factory,
1223                "/layout.v1.LayoutService/ResolveLayoutShareToken",
1224                req,
1225                |req, opts| client.resolve_layout_share_token_with_options(req, opts),
1226            )
1227            .await?
1228            .into_owned()
1229        };
1230        Ok(api_data_from_proto(&resp))
1231    }
1232
1233    pub async fn create_layout_share_link(
1234        &self,
1235        req: crate::proto::layout::v1::CreateLayoutShareLinkRequest,
1236    ) -> crate::errors::Result<crate::models::ApiData> {
1237        use crate::codecs::decode::api_data_from_proto;
1238        let client = self.connect_client();
1239        let resp = {
1240            use super::unary;
1241            unary::await_auth(
1242                &self.ctx.factory,
1243                "/layout.v1.LayoutService/CreateLayoutShareLink",
1244                req,
1245                |req, opts| client.create_layout_share_link_with_options(req, opts),
1246            )
1247            .await?
1248            .into_owned()
1249        };
1250        Ok(api_data_from_proto(&resp))
1251    }
1252
1253    pub async fn revoke_layout_share_link(
1254        &self,
1255        req: crate::proto::layout::v1::RevokeLayoutShareLinkRequest,
1256    ) -> crate::errors::Result<crate::models::ApiData> {
1257        use crate::codecs::decode::api_data_from_proto;
1258        let client = self.connect_client();
1259        let resp = {
1260            use super::unary;
1261            unary::await_auth(
1262                &self.ctx.factory,
1263                "/layout.v1.LayoutService/RevokeLayoutShareLink",
1264                req,
1265                |req, opts| client.revoke_layout_share_link_with_options(req, opts),
1266            )
1267            .await?
1268            .into_owned()
1269        };
1270        Ok(api_data_from_proto(&resp))
1271    }
1272
1273    pub async fn list_owner_published_layouts(
1274        &self,
1275        req: crate::proto::layout::v1::ListOwnerPublishedLayoutsRequest,
1276    ) -> crate::errors::Result<crate::models::ApiData> {
1277        use crate::codecs::decode::api_data_from_proto;
1278        let client = self.connect_client();
1279        let resp = {
1280            use super::unary;
1281            unary::await_auth(
1282                &self.ctx.factory,
1283                "/layout.v1.LayoutService/ListOwnerPublishedLayouts",
1284                req,
1285                |req, opts| client.list_owner_published_layouts_with_options(req, opts),
1286            )
1287            .await?
1288            .into_owned()
1289        };
1290        Ok(api_data_from_proto(&resp))
1291    }
1292
1293    pub async fn publish_layout(
1294        &self,
1295        req: crate::proto::layout::v1::PublishLayoutRequest,
1296    ) -> crate::errors::Result<crate::models::ApiData> {
1297        use crate::codecs::decode::api_data_from_proto;
1298        let client = self.connect_client();
1299        let resp = {
1300            use super::unary;
1301            unary::await_auth(
1302                &self.ctx.factory,
1303                "/layout.v1.LayoutService/PublishLayout",
1304                req,
1305                |req, opts| client.publish_layout_with_options(req, opts),
1306            )
1307            .await?
1308            .into_owned()
1309        };
1310        Ok(api_data_from_proto(&resp))
1311    }
1312
1313    pub async fn unpublish_layout(
1314        &self,
1315        req: crate::proto::layout::v1::UnpublishLayoutRequest,
1316    ) -> crate::errors::Result<crate::models::ApiData> {
1317        use crate::codecs::decode::api_data_from_proto;
1318        let client = self.connect_client();
1319        let resp = {
1320            use super::unary;
1321            unary::await_auth(
1322                &self.ctx.factory,
1323                "/layout.v1.LayoutService/UnpublishLayout",
1324                req,
1325                |req, opts| client.unpublish_layout_with_options(req, opts),
1326            )
1327            .await?
1328            .into_owned()
1329        };
1330        Ok(api_data_from_proto(&resp))
1331    }
1332
1333    pub async fn list_layout_template_versions(
1334        &self,
1335        req: crate::proto::layout::v1::ListLayoutTemplateVersionsRequest,
1336    ) -> crate::errors::Result<crate::models::ApiData> {
1337        use crate::codecs::decode::api_data_from_proto;
1338        let client = self.connect_client();
1339        let resp = {
1340            use super::unary;
1341            unary::await_auth(
1342                &self.ctx.factory,
1343                "/layout.v1.LayoutService/ListLayoutTemplateVersions",
1344                req,
1345                |req, opts| client.list_layout_template_versions_with_options(req, opts),
1346            )
1347            .await?
1348            .into_owned()
1349        };
1350        Ok(api_data_from_proto(&resp))
1351    }
1352
1353    pub async fn get_layout_template_version(
1354        &self,
1355        req: crate::proto::layout::v1::GetLayoutTemplateVersionRequest,
1356    ) -> crate::errors::Result<crate::models::ApiData> {
1357        use crate::codecs::decode::api_data_from_proto;
1358        let client = self.connect_client();
1359        let resp = {
1360            use super::unary;
1361            unary::await_auth(
1362                &self.ctx.factory,
1363                "/layout.v1.LayoutService/GetLayoutTemplateVersion",
1364                req,
1365                |req, opts| client.get_layout_template_version_with_options(req, opts),
1366            )
1367            .await?
1368            .into_owned()
1369        };
1370        Ok(api_data_from_proto(&resp))
1371    }
1372
1373    pub async fn set_layout_template_subscription(
1374        &self,
1375        req: crate::proto::layout::v1::SetLayoutTemplateSubscriptionRequest,
1376    ) -> crate::errors::Result<crate::models::ApiData> {
1377        use crate::codecs::decode::api_data_from_proto;
1378        let client = self.connect_client();
1379        let resp = {
1380            use super::unary;
1381            unary::await_auth(
1382                &self.ctx.factory,
1383                "/layout.v1.LayoutService/SetLayoutTemplateSubscription",
1384                req,
1385                |req, opts| client.set_layout_template_subscription_with_options(req, opts),
1386            )
1387            .await?
1388            .into_owned()
1389        };
1390        Ok(api_data_from_proto(&resp))
1391    }
1392
1393    pub async fn delete_layout_template_subscription(
1394        &self,
1395        req: crate::proto::layout::v1::DeleteLayoutTemplateSubscriptionRequest,
1396    ) -> crate::errors::Result<crate::models::ApiData> {
1397        use crate::codecs::decode::api_data_from_proto;
1398        let client = self.connect_client();
1399        let resp = {
1400            use super::unary;
1401            unary::await_auth(
1402                &self.ctx.factory,
1403                "/layout.v1.LayoutService/DeleteLayoutTemplateSubscription",
1404                req,
1405                |req, opts| client.delete_layout_template_subscription_with_options(req, opts),
1406            )
1407            .await?
1408            .into_owned()
1409        };
1410        Ok(api_data_from_proto(&resp))
1411    }
1412
1413    pub async fn list_my_layout_template_subscriptions(
1414        &self,
1415        req: crate::proto::layout::v1::ListMyLayoutTemplateSubscriptionsRequest,
1416    ) -> crate::errors::Result<crate::models::ApiData> {
1417        use crate::codecs::decode::api_data_from_proto;
1418        let client = self.connect_client();
1419        let resp = {
1420            use super::unary;
1421            unary::await_auth(
1422                &self.ctx.factory,
1423                "/layout.v1.LayoutService/ListMyLayoutTemplateSubscriptions",
1424                req,
1425                |req, opts| client.list_my_layout_template_subscriptions_with_options(req, opts),
1426            )
1427            .await?
1428            .into_owned()
1429        };
1430        Ok(api_data_from_proto(&resp))
1431    }
1432}
1433
1434impl PolychartService {
1435    pub async fn get_market_layers(
1436        &self,
1437        req: crate::proto::polychart::v1::GetMarketLayersRequest,
1438    ) -> crate::errors::Result<crate::models::ApiData> {
1439        use crate::codecs::decode::api_data_from_proto;
1440        let client = self.connect_client();
1441        let resp = {
1442            use super::unary;
1443            unary::await_auth(
1444                &self.ctx.factory,
1445                "/polychart.v1.PolychartService/GetMarketLayers",
1446                req,
1447                |req, opts| client.get_market_layers_with_options(req, opts),
1448            )
1449            .await?
1450            .into_owned()
1451        };
1452        Ok(api_data_from_proto(&resp))
1453    }
1454
1455    pub async fn list_inbox_market_layers(
1456        &self,
1457        req: crate::proto::polychart::v1::ListInboxMarketLayersRequest,
1458    ) -> crate::errors::Result<crate::models::ApiData> {
1459        use crate::codecs::decode::api_data_from_proto;
1460        let client = self.connect_client();
1461        let resp = {
1462            use super::unary;
1463            unary::await_auth(
1464                &self.ctx.factory,
1465                "/polychart.v1.PolychartService/ListInboxMarketLayers",
1466                req,
1467                |req, opts| client.list_inbox_market_layers_with_options(req, opts),
1468            )
1469            .await?
1470            .into_owned()
1471        };
1472        Ok(api_data_from_proto(&resp))
1473    }
1474
1475    pub async fn get_layer_snapshot(
1476        &self,
1477        req: crate::proto::polychart::v1::GetLayerSnapshotRequest,
1478    ) -> crate::errors::Result<crate::models::ApiData> {
1479        use crate::codecs::decode::api_data_from_proto;
1480        let client = self.connect_client();
1481        let resp = {
1482            use super::unary;
1483            unary::await_auth(
1484                &self.ctx.factory,
1485                "/polychart.v1.PolychartService/GetLayerSnapshot",
1486                req,
1487                |req, opts| client.get_layer_snapshot_with_options(req, opts),
1488            )
1489            .await?
1490            .into_owned()
1491        };
1492        Ok(api_data_from_proto(&resp))
1493    }
1494
1495    pub async fn get_layer_subscribe_tokens(
1496        &self,
1497        req: crate::proto::polychart::v1::GetLayerSubscribeTokensRequest,
1498    ) -> crate::errors::Result<crate::models::ApiData> {
1499        use crate::codecs::decode::api_data_from_proto;
1500        let client = self.connect_client();
1501        let resp = {
1502            use super::unary;
1503            unary::await_auth(
1504                &self.ctx.factory,
1505                "/polychart.v1.PolychartService/GetLayerSubscribeTokens",
1506                req,
1507                |req, opts| client.get_layer_subscribe_tokens_with_options(req, opts),
1508            )
1509            .await?
1510            .into_owned()
1511        };
1512        Ok(api_data_from_proto(&resp))
1513    }
1514
1515    pub async fn resolve_layer_share_token(
1516        &self,
1517        req: crate::proto::polychart::v1::ResolveLayerShareTokenRequest,
1518    ) -> crate::errors::Result<crate::models::ApiData> {
1519        use crate::codecs::decode::api_data_from_proto;
1520        let client = self.connect_client();
1521        let resp = {
1522            use super::unary;
1523            unary::await_auth(
1524                &self.ctx.factory,
1525                "/polychart.v1.PolychartService/ResolveLayerShareToken",
1526                req,
1527                |req, opts| client.resolve_layer_share_token_with_options(req, opts),
1528            )
1529            .await?
1530            .into_owned()
1531        };
1532        Ok(api_data_from_proto(&resp))
1533    }
1534
1535    pub async fn create_layer_share_link(
1536        &self,
1537        req: crate::proto::polychart::v1::CreateLayerShareLinkRequest,
1538    ) -> crate::errors::Result<crate::models::ApiData> {
1539        use crate::codecs::decode::api_data_from_proto;
1540        let client = self.connect_client();
1541        let resp = {
1542            use super::unary;
1543            unary::await_auth(
1544                &self.ctx.factory,
1545                "/polychart.v1.PolychartService/CreateLayerShareLink",
1546                req,
1547                |req, opts| client.create_layer_share_link_with_options(req, opts),
1548            )
1549            .await?
1550            .into_owned()
1551        };
1552        Ok(api_data_from_proto(&resp))
1553    }
1554
1555    pub async fn revoke_layer_share_link(
1556        &self,
1557        req: crate::proto::polychart::v1::RevokeLayerShareLinkRequest,
1558    ) -> crate::errors::Result<crate::models::ApiData> {
1559        use crate::codecs::decode::api_data_from_proto;
1560        let client = self.connect_client();
1561        let resp = {
1562            use super::unary;
1563            unary::await_auth(
1564                &self.ctx.factory,
1565                "/polychart.v1.PolychartService/RevokeLayerShareLink",
1566                req,
1567                |req, opts| client.revoke_layer_share_link_with_options(req, opts),
1568            )
1569            .await?
1570            .into_owned()
1571        };
1572        Ok(api_data_from_proto(&resp))
1573    }
1574
1575    pub async fn list_owner_published_layers(
1576        &self,
1577        req: crate::proto::polychart::v1::ListOwnerPublishedLayersRequest,
1578    ) -> crate::errors::Result<crate::models::ApiData> {
1579        use crate::codecs::decode::api_data_from_proto;
1580        let client = self.connect_client();
1581        let resp = {
1582            use super::unary;
1583            unary::await_auth(
1584                &self.ctx.factory,
1585                "/polychart.v1.PolychartService/ListOwnerPublishedLayers",
1586                req,
1587                |req, opts| client.list_owner_published_layers_with_options(req, opts),
1588            )
1589            .await?
1590            .into_owned()
1591        };
1592        Ok(api_data_from_proto(&resp))
1593    }
1594
1595    pub async fn publish_layer(
1596        &self,
1597        req: crate::proto::polychart::v1::PublishLayerRequest,
1598    ) -> crate::errors::Result<crate::models::ApiData> {
1599        use crate::codecs::decode::api_data_from_proto;
1600        let client = self.connect_client();
1601        let resp = {
1602            use super::unary;
1603            unary::await_auth(
1604                &self.ctx.factory,
1605                "/polychart.v1.PolychartService/PublishLayer",
1606                req,
1607                |req, opts| client.publish_layer_with_options(req, opts),
1608            )
1609            .await?
1610            .into_owned()
1611        };
1612        Ok(api_data_from_proto(&resp))
1613    }
1614
1615    pub async fn unpublish_layer(
1616        &self,
1617        req: crate::proto::polychart::v1::UnpublishLayerRequest,
1618    ) -> crate::errors::Result<crate::models::ApiData> {
1619        use crate::codecs::decode::api_data_from_proto;
1620        let client = self.connect_client();
1621        let resp = {
1622            use super::unary;
1623            unary::await_auth(
1624                &self.ctx.factory,
1625                "/polychart.v1.PolychartService/UnpublishLayer",
1626                req,
1627                |req, opts| client.unpublish_layer_with_options(req, opts),
1628            )
1629            .await?
1630            .into_owned()
1631        };
1632        Ok(api_data_from_proto(&resp))
1633    }
1634
1635    pub async fn upsert_layer(
1636        &self,
1637        req: crate::proto::polychart::v1::UpsertLayerRequest,
1638    ) -> crate::errors::Result<crate::models::ApiData> {
1639        use crate::codecs::decode::api_data_from_proto;
1640        let client = self.connect_client();
1641        let resp = {
1642            use super::unary;
1643            unary::await_auth(
1644                &self.ctx.factory,
1645                "/polychart.v1.PolychartService/UpsertLayer",
1646                req,
1647                |req, opts| client.upsert_layer_with_options(req, opts),
1648            )
1649            .await?
1650            .into_owned()
1651        };
1652        Ok(api_data_from_proto(&resp))
1653    }
1654
1655    pub async fn delete_layer(
1656        &self,
1657        req: crate::proto::polychart::v1::DeleteLayerRequest,
1658    ) -> crate::errors::Result<crate::models::ApiData> {
1659        use crate::codecs::decode::api_data_from_proto;
1660        let client = self.connect_client();
1661        let resp = {
1662            use super::unary;
1663            unary::await_auth(
1664                &self.ctx.factory,
1665                "/polychart.v1.PolychartService/DeleteLayer",
1666                req,
1667                |req, opts| client.delete_layer_with_options(req, opts),
1668            )
1669            .await?
1670            .into_owned()
1671        };
1672        Ok(api_data_from_proto(&resp))
1673    }
1674
1675    pub async fn upsert_drawing(
1676        &self,
1677        req: crate::proto::polychart::v1::UpsertDrawingRequest,
1678    ) -> crate::errors::Result<crate::models::ApiData> {
1679        use crate::codecs::decode::api_data_from_proto;
1680        let client = self.connect_client();
1681        let resp = {
1682            use super::unary;
1683            unary::await_auth(
1684                &self.ctx.factory,
1685                "/polychart.v1.PolychartService/UpsertDrawing",
1686                req,
1687                |req, opts| client.upsert_drawing_with_options(req, opts),
1688            )
1689            .await?
1690            .into_owned()
1691        };
1692        Ok(api_data_from_proto(&resp))
1693    }
1694
1695    pub async fn delete_drawing(
1696        &self,
1697        req: crate::proto::polychart::v1::DeleteDrawingRequest,
1698    ) -> crate::errors::Result<crate::models::ApiData> {
1699        use crate::codecs::decode::api_data_from_proto;
1700        let client = self.connect_client();
1701        let resp = {
1702            use super::unary;
1703            unary::await_auth(
1704                &self.ctx.factory,
1705                "/polychart.v1.PolychartService/DeleteDrawing",
1706                req,
1707                |req, opts| client.delete_drawing_with_options(req, opts),
1708            )
1709            .await?
1710            .into_owned()
1711        };
1712        Ok(api_data_from_proto(&resp))
1713    }
1714
1715    pub async fn set_layer_subscriptions(
1716        &self,
1717        req: crate::proto::polychart::v1::SetLayerSubscriptionsRequest,
1718    ) -> crate::errors::Result<crate::models::ApiData> {
1719        use crate::codecs::decode::api_data_from_proto;
1720        let client = self.connect_client();
1721        let resp = {
1722            use super::unary;
1723            unary::await_auth(
1724                &self.ctx.factory,
1725                "/polychart.v1.PolychartService/SetLayerSubscriptions",
1726                req,
1727                |req, opts| client.set_layer_subscriptions_with_options(req, opts),
1728            )
1729            .await?
1730            .into_owned()
1731        };
1732        Ok(api_data_from_proto(&resp))
1733    }
1734}
1735
1736impl WhiteboardService {
1737    pub async fn create_board(
1738        &self,
1739        req: crate::proto::collab::v1::CreateBoardRequest,
1740    ) -> crate::errors::Result<crate::models::ApiData> {
1741        use crate::codecs::decode::api_data_from_proto;
1742        let client = self.connect_client();
1743        let resp = {
1744            use super::unary;
1745            unary::await_auth(
1746                &self.ctx.factory,
1747                "/collab.v1.WhiteboardService/CreateBoard",
1748                req,
1749                |req, opts| client.create_board_with_options(req, opts),
1750            )
1751            .await?
1752            .into_owned()
1753        };
1754        Ok(api_data_from_proto(&resp))
1755    }
1756
1757    pub async fn get_board(
1758        &self,
1759        req: crate::proto::collab::v1::GetBoardRequest,
1760    ) -> crate::errors::Result<crate::models::ApiData> {
1761        use crate::codecs::decode::api_data_from_proto;
1762        let client = self.connect_client();
1763        let resp = {
1764            use super::unary;
1765            unary::await_auth(
1766                &self.ctx.factory,
1767                "/collab.v1.WhiteboardService/GetBoard",
1768                req,
1769                |req, opts| client.get_board_with_options(req, opts),
1770            )
1771            .await?
1772            .into_owned()
1773        };
1774        Ok(api_data_from_proto(&resp))
1775    }
1776
1777    pub async fn list_boards(
1778        &self,
1779        req: crate::proto::collab::v1::ListBoardsRequest,
1780    ) -> crate::errors::Result<crate::models::ApiData> {
1781        use crate::codecs::decode::api_data_from_proto;
1782        let client = self.connect_client();
1783        let resp = {
1784            use super::unary;
1785            unary::await_auth(
1786                &self.ctx.factory,
1787                "/collab.v1.WhiteboardService/ListBoards",
1788                req,
1789                |req, opts| client.list_boards_with_options(req, opts),
1790            )
1791            .await?
1792            .into_owned()
1793        };
1794        Ok(api_data_from_proto(&resp))
1795    }
1796
1797    pub async fn update_board(
1798        &self,
1799        req: crate::proto::collab::v1::UpdateBoardRequest,
1800    ) -> crate::errors::Result<crate::models::ApiData> {
1801        use crate::codecs::decode::api_data_from_proto;
1802        let client = self.connect_client();
1803        let resp = {
1804            use super::unary;
1805            unary::await_auth(
1806                &self.ctx.factory,
1807                "/collab.v1.WhiteboardService/UpdateBoard",
1808                req,
1809                |req, opts| client.update_board_with_options(req, opts),
1810            )
1811            .await?
1812            .into_owned()
1813        };
1814        Ok(api_data_from_proto(&resp))
1815    }
1816
1817    pub async fn update_board_acl(
1818        &self,
1819        req: crate::proto::collab::v1::UpdateBoardAclRequest,
1820    ) -> crate::errors::Result<crate::models::ApiData> {
1821        use crate::codecs::decode::api_data_from_proto;
1822        let client = self.connect_client();
1823        let resp = {
1824            use super::unary;
1825            unary::await_auth(
1826                &self.ctx.factory,
1827                "/collab.v1.WhiteboardService/UpdateBoardAcl",
1828                req,
1829                |req, opts| client.update_board_acl_with_options(req, opts),
1830            )
1831            .await?
1832            .into_owned()
1833        };
1834        Ok(api_data_from_proto(&resp))
1835    }
1836
1837    pub async fn archive_board(
1838        &self,
1839        req: crate::proto::collab::v1::ArchiveBoardRequest,
1840    ) -> crate::errors::Result<crate::models::ApiData> {
1841        use crate::codecs::decode::api_data_from_proto;
1842        let client = self.connect_client();
1843        let resp = {
1844            use super::unary;
1845            unary::await_auth(
1846                &self.ctx.factory,
1847                "/collab.v1.WhiteboardService/ArchiveBoard",
1848                req,
1849                |req, opts| client.archive_board_with_options(req, opts),
1850            )
1851            .await?
1852            .into_owned()
1853        };
1854        Ok(api_data_from_proto(&resp))
1855    }
1856
1857    pub async fn mint_join_token(
1858        &self,
1859        req: crate::proto::collab::v1::MintJoinTokenRequest,
1860    ) -> crate::errors::Result<crate::models::ApiData> {
1861        use crate::codecs::decode::api_data_from_proto;
1862        let client = self.connect_client();
1863        let resp = {
1864            use super::unary;
1865            unary::await_auth(
1866                &self.ctx.factory,
1867                "/collab.v1.WhiteboardService/MintJoinToken",
1868                req,
1869                |req, opts| client.mint_join_token_with_options(req, opts),
1870            )
1871            .await?
1872            .into_owned()
1873        };
1874        Ok(api_data_from_proto(&resp))
1875    }
1876}
1877
1878#[cfg(test)]
1879mod tests {
1880    use super::internal_transfer_amount_e18;
1881    use crate::models::CreateInternalTransferParams;
1882    use crate::types::{AssetAmount, QuantityDomain};
1883
1884    #[test]
1885    fn internal_transfer_amount_is_always_exact_e18() {
1886        let amount =
1887            AssetAmount::from_scaled(125, Some(2), QuantityDomain::LedgerE18, Some(7)).unwrap();
1888        let wire = internal_transfer_amount_e18(&amount, Some(2), 7).unwrap();
1889        assert_eq!(
1890            (u128::from(wire.hi) << 64) | u128::from(wire.lo),
1891            1_250_000_000_000_000_000
1892        );
1893
1894        let inexact =
1895            AssetAmount::from_scaled(126, Some(19), QuantityDomain::LedgerE18, Some(7)).unwrap();
1896        assert!(internal_transfer_amount_e18(&inexact, Some(19), 7).is_err());
1897    }
1898
1899    #[test]
1900    fn internal_transfer_rejects_missing_amount_scale_before_transport() {
1901        let amount = AssetAmount::from_scaled(1, None, QuantityDomain::LedgerE18, Some(7)).unwrap();
1902        let err = internal_transfer_amount_e18(&amount, None, 7)
1903            .expect_err("missing scale must not silently mean e18");
1904        assert!(err.to_string().contains("amount scale is required"));
1905    }
1906
1907    #[tokio::test]
1908    async fn internal_transfer_requires_destination_before_transport() {
1909        let client = crate::Client::new(crate::Config {
1910            hydrate_catalogs: false,
1911            ..Default::default()
1912        })
1913        .unwrap();
1914        let params = CreateInternalTransferParams {
1915            asset_id: 7,
1916            quantity: AssetAmount::from_scaled(100, Some(18), QuantityDomain::LedgerE18, Some(7))
1917                .unwrap(),
1918            idempotency_key: "missing-destination".into(),
1919            subaccount_id: None,
1920            destination_account_id: None,
1921            destination_subaccount_id: None,
1922            destination_smart_account_address: None,
1923            quantity_scale: Some(18),
1924        };
1925
1926        let err = client
1927            .internal_transfers
1928            .create(params.clone())
1929            .await
1930            .unwrap_err();
1931        assert!(err.to_string().contains("requires exactly one"));
1932
1933        let multiple = CreateInternalTransferParams {
1934            destination_account_id: Some("2".into()),
1935            destination_subaccount_id: Some("3".into()),
1936            idempotency_key: "multiple-destinations".into(),
1937            ..params.clone()
1938        };
1939        let err = client
1940            .internal_transfers
1941            .create(multiple)
1942            .await
1943            .unwrap_err();
1944        assert!(err.to_string().contains("requires exactly one"));
1945
1946        let empty_key = CreateInternalTransferParams {
1947            destination_account_id: Some("2".into()),
1948            idempotency_key: " ".into(),
1949            ..params.clone()
1950        };
1951        let err = client
1952            .internal_transfers
1953            .create(empty_key)
1954            .await
1955            .unwrap_err();
1956        assert!(err.to_string().contains("non-empty idempotency_key"));
1957
1958        let whitespace_destination = CreateInternalTransferParams {
1959            destination_smart_account_address: Some("   ".into()),
1960            ..params
1961        };
1962        let err = client
1963            .internal_transfers
1964            .create(whitespace_destination)
1965            .await
1966            .unwrap_err();
1967        assert!(err.to_string().contains("requires exactly one"));
1968    }
1969}