1extern crate alloc;
6
7use alloc::format;
8use alloc::string::{String, ToString};
9use alloc::vec;
10use alloy_network::Ethereum;
11use alloy_primitives::{Address, B256, Bytes, TxKind, U256, keccak256};
12use alloy_provider::{DynProvider, Provider, ProviderBuilder};
13use alloy_rpc_types::{
14 Block, BlockNumberOrTag, Filter, Log, TransactionReceipt,
15 transaction::{TransactionInput, TransactionRequest},
16};
17use alloy_sol_types::SolType;
18use blueprint_client_core::{BlueprintServicesClient, OperatorSet};
19use blueprint_crypto::k256::K256Ecdsa;
20use blueprint_keystore::Keystore;
21use blueprint_keystore::backends::Backend;
22use blueprint_std::collections::BTreeMap;
23use blueprint_std::sync::Arc;
24use blueprint_std::vec::Vec;
25use core::fmt;
26use core::time::Duration;
27use k256::elliptic_curve::sec1::ToEncodedPoint;
28use tokio::sync::Mutex;
29
30use crate::config::TangleClientConfig;
31use crate::contracts::{
32 IBlueprintServiceManager, IMultiAssetDelegation, IMultiAssetDelegationTypes,
33 IOperatorStatusRegistry, ITangle, ITangleTypes,
34};
35use crate::error::{Error, Result};
36use crate::services::ServiceRequestParams;
37use IMultiAssetDelegation::IMultiAssetDelegationInstance;
38use IOperatorStatusRegistry::IOperatorStatusRegistryInstance;
39use ITangle::ITangleInstance;
40
41#[allow(missing_docs)]
42mod erc20 {
43 alloy_sol_types::sol! {
44 #[sol(rpc)]
45 interface IERC20 {
46 function approve(address spender, uint256 amount) external returns (bool);
47 function allowance(address owner, address spender) external view returns (uint256);
48 function balanceOf(address owner) external view returns (uint256);
49 }
50 }
51}
52
53use erc20::IERC20;
54
55pub type TangleProvider = DynProvider<Ethereum>;
57
58pub type EcdsaPublicKey = [u8; 65];
60
61pub type CompressedEcdsaPublicKey = [u8; 33];
63
64#[derive(Debug, Clone)]
66pub struct RestakingMetadata {
67 pub stake: U256,
69 pub delegation_count: u32,
71 pub status: RestakingStatus,
73 pub leaving_round: u64,
75}
76
77#[derive(Debug, Clone, Copy, PartialEq, Eq)]
79pub enum RestakingStatus {
80 Active,
82 Inactive,
84 Leaving,
86 Unknown(u8),
88}
89
90impl From<u8> for RestakingStatus {
91 fn from(value: u8) -> Self {
92 match value {
93 0 => RestakingStatus::Active,
94 1 => RestakingStatus::Inactive,
95 2 => RestakingStatus::Leaving,
96 other => RestakingStatus::Unknown(other),
97 }
98 }
99}
100
101#[derive(Debug, Clone, Copy, PartialEq, Eq)]
105pub enum DelegationMode {
106 Disabled,
108 Whitelist,
110 Open,
112 Unknown(u8),
114}
115
116impl From<u8> for DelegationMode {
117 fn from(value: u8) -> Self {
118 match value {
119 0 => DelegationMode::Disabled,
120 1 => DelegationMode::Whitelist,
121 2 => DelegationMode::Open,
122 other => DelegationMode::Unknown(other),
123 }
124 }
125}
126
127impl fmt::Display for DelegationMode {
128 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
129 match self {
130 DelegationMode::Disabled => write!(f, "disabled"),
131 DelegationMode::Whitelist => write!(f, "whitelist"),
132 DelegationMode::Open => write!(f, "open"),
133 DelegationMode::Unknown(value) => write!(f, "unknown({value})"),
134 }
135 }
136}
137
138#[derive(Debug, Clone, Copy, PartialEq, Eq)]
140pub enum AssetKind {
141 Native,
143 Erc20,
145 Unknown(u8),
147}
148
149impl From<u8> for AssetKind {
150 fn from(value: u8) -> Self {
151 match value {
152 0 => AssetKind::Native,
153 1 => AssetKind::Erc20,
154 other => AssetKind::Unknown(other),
155 }
156 }
157}
158
159impl fmt::Display for AssetKind {
160 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
161 match self {
162 AssetKind::Native => write!(f, "native"),
163 AssetKind::Erc20 => write!(f, "erc20"),
164 AssetKind::Unknown(value) => write!(f, "unknown({value})"),
165 }
166 }
167}
168
169#[derive(Debug, Clone, Copy, PartialEq, Eq)]
171pub enum BlueprintSelectionMode {
172 All,
174 Fixed,
176 Unknown(u8),
178}
179
180impl From<u8> for BlueprintSelectionMode {
181 fn from(value: u8) -> Self {
182 match value {
183 0 => BlueprintSelectionMode::All,
184 1 => BlueprintSelectionMode::Fixed,
185 other => BlueprintSelectionMode::Unknown(other),
186 }
187 }
188}
189
190impl fmt::Display for BlueprintSelectionMode {
191 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
192 match self {
193 BlueprintSelectionMode::All => write!(f, "all"),
194 BlueprintSelectionMode::Fixed => write!(f, "fixed"),
195 BlueprintSelectionMode::Unknown(value) => write!(f, "unknown({value})"),
196 }
197 }
198}
199
200#[derive(Debug, Clone, Copy, PartialEq, Eq)]
202pub enum LockMultiplier {
203 None,
205 OneMonth,
207 TwoMonths,
209 ThreeMonths,
211 SixMonths,
213 Unknown(u8),
215}
216
217impl From<u8> for LockMultiplier {
218 fn from(value: u8) -> Self {
219 match value {
220 0 => LockMultiplier::None,
221 1 => LockMultiplier::OneMonth,
222 2 => LockMultiplier::TwoMonths,
223 3 => LockMultiplier::ThreeMonths,
224 4 => LockMultiplier::SixMonths,
225 other => LockMultiplier::Unknown(other),
226 }
227 }
228}
229
230impl fmt::Display for LockMultiplier {
231 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
232 match self {
233 LockMultiplier::None => write!(f, "none"),
234 LockMultiplier::OneMonth => write!(f, "one-month"),
235 LockMultiplier::TwoMonths => write!(f, "two-months"),
236 LockMultiplier::ThreeMonths => write!(f, "three-months"),
237 LockMultiplier::SixMonths => write!(f, "six-months"),
238 LockMultiplier::Unknown(value) => write!(f, "unknown({value})"),
239 }
240 }
241}
242
243#[derive(Debug, Clone)]
245pub struct AssetInfo {
246 pub kind: AssetKind,
248 pub token: Address,
250}
251
252#[derive(Debug, Clone)]
254pub struct DepositInfo {
255 pub amount: U256,
257 pub delegated_amount: U256,
259}
260
261#[derive(Debug, Clone)]
263pub struct LockInfo {
264 pub amount: U256,
266 pub multiplier: LockMultiplier,
268 pub expiry_block: u64,
270}
271
272#[derive(Debug, Clone)]
274pub struct DelegationInfo {
275 pub operator: Address,
277 pub shares: U256,
279 pub asset: AssetInfo,
281 pub selection_mode: BlueprintSelectionMode,
283}
284
285#[derive(Debug, Clone)]
287pub struct DelegationRecord {
288 pub info: DelegationInfo,
290 pub blueprint_ids: Vec<u64>,
292}
293
294#[derive(Debug, Clone)]
296pub struct PendingUnstake {
297 pub operator: Address,
299 pub asset: AssetInfo,
301 pub shares: U256,
303 pub requested_round: u64,
305 pub selection_mode: BlueprintSelectionMode,
307 pub slash_factor_snapshot: U256,
309}
310
311#[derive(Debug, Clone)]
313pub struct PendingWithdrawal {
314 pub asset: AssetInfo,
316 pub amount: U256,
318 pub requested_round: u64,
320}
321
322#[derive(Debug, Clone)]
324pub struct OperatorMetadata {
325 pub public_key: EcdsaPublicKey,
327 pub rpc_endpoint: String,
329 pub restaking: RestakingMetadata,
331}
332
333#[derive(Debug, Clone)]
335pub struct OperatorStatusSnapshot {
336 pub service_id: u64,
338 pub operator: Address,
340 pub status_code: u8,
342 pub last_heartbeat: u64,
344 pub online: bool,
346}
347
348#[derive(Clone, Debug)]
350pub struct TangleEvent {
351 pub block_number: u64,
353 pub block_hash: B256,
355 pub timestamp: u64,
357 pub logs: Vec<Log>,
359}
360
361#[derive(Clone)]
363pub struct TangleClient {
364 provider: Arc<TangleProvider>,
366 tangle_address: Address,
368 restaking_address: Address,
370 status_registry_address: Address,
372 account: Address,
374 pub config: TangleClientConfig,
376 keystore: Arc<Keystore>,
378 latest_block: Arc<Mutex<Option<TangleEvent>>>,
380 block_subscription: Arc<Mutex<Option<u64>>>,
382}
383
384#[allow(clippy::missing_fields_in_debug)] impl fmt::Debug for TangleClient {
386 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
387 f.debug_struct("TangleClient")
388 .field("tangle_address", &self.tangle_address)
389 .field("restaking_address", &self.restaking_address)
390 .field("status_registry_address", &self.status_registry_address)
391 .field("account", &self.account)
392 .finish()
393 }
394}
395
396impl TangleClient {
397 pub async fn new(config: TangleClientConfig) -> Result<Self> {
405 let keystore = Keystore::new(config.keystore_config())?;
406 Self::with_keystore(config, keystore).await
407 }
408
409 pub async fn with_keystore(config: TangleClientConfig, keystore: Keystore) -> Result<Self> {
418 let rpc_url = config.http_rpc_endpoint.as_str();
419
420 let provider = ProviderBuilder::new()
422 .connect(rpc_url)
423 .await
424 .map_err(|e| Error::Config(e.to_string()))?;
425
426 let dyn_provider = DynProvider::new(provider);
427
428 let ecdsa_key = keystore
430 .first_local::<K256Ecdsa>()
431 .map_err(Error::Keystore)?;
432
433 let pubkey_bytes = ecdsa_key.0.to_encoded_point(false);
436 let account = ecdsa_public_key_to_address(pubkey_bytes.as_bytes())?;
437
438 Ok(Self {
439 provider: Arc::new(dyn_provider),
440 tangle_address: config.settings.tangle_contract,
441 restaking_address: config.settings.restaking_contract,
442 status_registry_address: config.settings.status_registry_contract,
443 account,
444 config,
445 keystore: Arc::new(keystore),
446 latest_block: Arc::new(Mutex::new(None)),
447 block_subscription: Arc::new(Mutex::new(None)),
448 })
449 }
450
451 pub fn tangle_contract(&self) -> ITangleInstance<Arc<TangleProvider>> {
453 ITangleInstance::new(self.tangle_address, Arc::clone(&self.provider))
454 }
455
456 pub fn restaking_contract(&self) -> IMultiAssetDelegationInstance<Arc<TangleProvider>> {
458 IMultiAssetDelegation::new(self.restaking_address, Arc::clone(&self.provider))
459 }
460
461 pub fn status_registry_contract(&self) -> IOperatorStatusRegistryInstance<Arc<TangleProvider>> {
463 IOperatorStatusRegistryInstance::new(
464 self.status_registry_address,
465 Arc::clone(&self.provider),
466 )
467 }
468
469 #[must_use]
471 pub fn account(&self) -> Address {
472 self.account
473 }
474
475 #[must_use]
477 pub fn keystore(&self) -> &Arc<Keystore> {
478 &self.keystore
479 }
480
481 #[must_use]
483 pub fn provider(&self) -> &Arc<TangleProvider> {
484 &self.provider
485 }
486
487 #[must_use]
489 pub fn tangle_address(&self) -> Address {
490 self.tangle_address
491 }
492
493 pub fn ecdsa_signing_key(&self) -> Result<blueprint_crypto::k256::K256SigningKey> {
498 let public = self
499 .keystore
500 .first_local::<K256Ecdsa>()
501 .map_err(Error::Keystore)?;
502 self.keystore
503 .get_secret::<K256Ecdsa>(&public)
504 .map_err(Error::Keystore)
505 }
506
507 pub fn wallet(&self) -> Result<alloy_network::EthereumWallet> {
512 let signing_key = self.ecdsa_signing_key()?;
513 let local_signer = signing_key
514 .alloy_key()
515 .map_err(|e| Error::Keystore(blueprint_keystore::Error::Other(e.to_string())))?;
516 Ok(alloy_network::EthereumWallet::from(local_signer))
517 }
518
519 pub async fn block_number(&self) -> Result<u64> {
521 self.provider
522 .get_block_number()
523 .await
524 .map_err(Error::Transport)
525 }
526
527 pub async fn get_block(&self, number: BlockNumberOrTag) -> Result<Option<Block>> {
529 self.provider
530 .get_block_by_number(number)
531 .await
532 .map_err(Error::Transport)
533 }
534
535 pub async fn get_logs(&self, filter: &Filter) -> Result<Vec<Log>> {
537 self.provider
538 .get_logs(filter)
539 .await
540 .map_err(Error::Transport)
541 }
542
543 pub async fn next_event(&self) -> Option<TangleEvent> {
549 loop {
550 let current_block = self.block_number().await.ok()?;
551
552 let mut last_block = self.block_subscription.lock().await;
553 let from_block = last_block.map(|b| b + 1).unwrap_or(0);
554
555 if from_block > current_block {
556 drop(last_block);
557 tokio::time::sleep(Duration::from_secs(1)).await;
558 continue;
559 }
560
561 let block = self
563 .get_block(BlockNumberOrTag::Number(current_block))
564 .await
565 .ok()??;
566
567 let filter = Filter::new()
569 .address(self.tangle_address)
570 .from_block(from_block)
571 .to_block(current_block);
572
573 let logs = self.get_logs(&filter).await.ok()?;
574
575 *last_block = Some(current_block);
576
577 let event = TangleEvent {
578 block_number: current_block,
579 block_hash: block.header.hash,
580 timestamp: block.header.timestamp,
581 logs,
582 };
583
584 *self.latest_block.lock().await = Some(event.clone());
586
587 return Some(event);
588 }
589 }
590
591 pub async fn latest_event(&self) -> Option<TangleEvent> {
593 let latest = self.latest_block.lock().await;
594 match &*latest {
595 Some(event) => Some(event.clone()),
596 None => {
597 drop(latest);
598 self.next_event().await
599 }
600 }
601 }
602
603 pub async fn now(&self) -> Option<B256> {
605 Some(self.latest_event().await?.block_hash)
606 }
607
608 pub async fn get_blueprint(&self, blueprint_id: u64) -> Result<ITangleTypes::Blueprint> {
614 let contract = self.tangle_contract();
615 let result = contract
616 .getBlueprint(blueprint_id)
617 .call()
618 .await
619 .map_err(|e| Error::Contract(e.to_string()))?;
620 Ok(result)
621 }
622
623 pub async fn get_raw_blueprint_definition(&self, blueprint_id: u64) -> Result<Vec<u8>> {
625 let mut data = Vec::with_capacity(4 + 32);
626 let method_hash = keccak256("getBlueprintDefinition(uint64)".as_bytes());
627 data.extend_from_slice(&method_hash[..4]);
628 let mut arg = [0u8; 32];
629 arg[24..].copy_from_slice(&blueprint_id.to_be_bytes());
630 data.extend_from_slice(&arg);
631
632 let mut request = TransactionRequest::default();
633 request.to = Some(TxKind::Call(self.tangle_address));
634 request.input = TransactionInput::new(Bytes::from(data));
635
636 let response = self
637 .provider
638 .call(request)
639 .await
640 .map_err(Error::Transport)?;
641
642 Ok(response.to_vec())
643 }
644
645 pub async fn get_blueprint_config(
647 &self,
648 blueprint_id: u64,
649 ) -> Result<ITangleTypes::BlueprintConfig> {
650 let contract = self.tangle_contract();
651 let result = contract
652 .getBlueprintConfig(blueprint_id)
653 .call()
654 .await
655 .map_err(|e| Error::Contract(e.to_string()))?;
656 Ok(result)
657 }
658
659 pub async fn get_blueprint_definition(
661 &self,
662 blueprint_id: u64,
663 ) -> Result<ITangleTypes::BlueprintDefinition> {
664 let contract = self.tangle_contract();
665 let result = contract
666 .getBlueprintDefinition(blueprint_id)
667 .call()
668 .await
669 .map_err(|e| Error::Contract(e.to_string()))?;
670 Ok(result)
671 }
672
673 pub async fn create_blueprint(
675 &self,
676 encoded_definition: Vec<u8>,
677 ) -> Result<(TransactionResult, u64)> {
678 let definition = ITangleTypes::BlueprintDefinition::abi_decode(encoded_definition.as_ref())
679 .map_err(|err| {
680 Error::Contract(format!("failed to decode blueprint definition: {err}"))
681 })?;
682
683 let wallet = self.wallet()?;
684 let provider = ProviderBuilder::new()
685 .wallet(wallet)
686 .connect(self.config.http_rpc_endpoint.as_str())
687 .await
688 .map_err(Error::Transport)?;
689 let contract = ITangle::new(self.tangle_address, &provider);
690 let pending_tx = contract
691 .createBlueprint(definition)
692 .send()
693 .await
694 .map_err(|e| Error::Contract(e.to_string()))?;
695 let receipt = pending_tx.get_receipt().await?;
696 let blueprint_id = self.extract_blueprint_id(&receipt)?;
697
698 Ok((transaction_result_from_receipt(&receipt), blueprint_id))
699 }
700
701 pub async fn is_operator_registered(
703 &self,
704 blueprint_id: u64,
705 operator: Address,
706 ) -> Result<bool> {
707 let contract = self.tangle_contract();
708 contract
709 .isOperatorRegistered(blueprint_id, operator)
710 .call()
711 .await
712 .map_err(|e| Error::Contract(e.to_string()))
713 }
714
715 pub async fn get_service(&self, service_id: u64) -> Result<ITangleTypes::Service> {
721 let contract = self.tangle_contract();
722 let result = contract
723 .getService(service_id)
724 .call()
725 .await
726 .map_err(|e| Error::Contract(e.to_string()))?;
727 Ok(result)
728 }
729
730 pub async fn get_service_operators(&self, service_id: u64) -> Result<Vec<Address>> {
732 let contract = self.tangle_contract();
733 contract
734 .getServiceOperators(service_id)
735 .call()
736 .await
737 .map_err(|e| Error::Contract(e.to_string()))
738 }
739
740 pub async fn is_service_operator(&self, service_id: u64, operator: Address) -> Result<bool> {
742 let contract = self.tangle_contract();
743 contract
744 .isServiceOperator(service_id, operator)
745 .call()
746 .await
747 .map_err(|e| Error::Contract(e.to_string()))
748 }
749
750 pub async fn get_service_operator(
754 &self,
755 service_id: u64,
756 operator: Address,
757 ) -> Result<ITangleTypes::ServiceOperator> {
758 let contract = self.tangle_contract();
759 let result = contract
760 .getServiceOperator(service_id, operator)
761 .call()
762 .await
763 .map_err(|e| Error::Contract(e.to_string()))?;
764 Ok(result)
765 }
766
767 pub async fn get_service_total_exposure(&self, service_id: u64) -> Result<U256> {
771 let mut total = U256::ZERO;
772 for operator in self.get_service_operators(service_id).await? {
773 let op_info = self.get_service_operator(service_id, operator).await?;
774 if op_info.active {
775 total = total.saturating_add(U256::from(op_info.exposureBps));
776 }
777 }
778 Ok(total)
779 }
780
781 pub async fn get_service_operator_weights(
786 &self,
787 service_id: u64,
788 ) -> Result<BTreeMap<Address, u16>> {
789 let operators = self.get_service_operators(service_id).await?;
790 let mut weights = BTreeMap::new();
791
792 for operator in operators {
793 let op_info = self.get_service_operator(service_id, operator).await?;
794 if op_info.active {
795 weights.insert(operator, op_info.exposureBps);
796 }
797 }
798
799 Ok(weights)
800 }
801
802 pub async fn register_operator(
804 &self,
805 blueprint_id: u64,
806 rpc_endpoint: impl Into<String>,
807 registration_inputs: Option<Bytes>,
808 ) -> Result<TransactionResult> {
809 let wallet = self.wallet()?;
810 let provider = ProviderBuilder::new()
811 .wallet(wallet)
812 .connect(self.config.http_rpc_endpoint.as_str())
813 .await
814 .map_err(Error::Transport)?;
815 let contract = ITangle::new(self.tangle_address, &provider);
816
817 let signing_key = self.ecdsa_signing_key()?;
818 let verifying = signing_key.verifying_key();
819 let encoded_point = verifying.0.to_encoded_point(false);
822 let ecdsa_bytes = Bytes::copy_from_slice(encoded_point.as_bytes());
823 let rpc_endpoint = rpc_endpoint.into();
824
825 let receipt = if let Some(inputs) = registration_inputs {
826 contract
827 .registerOperator_0(
828 blueprint_id,
829 ecdsa_bytes.clone(),
830 rpc_endpoint.clone(),
831 inputs,
832 )
833 .send()
834 .await
835 .map_err(|e| Error::Contract(e.to_string()))?
836 .get_receipt()
837 .await?
838 } else {
839 contract
840 .registerOperator_1(blueprint_id, ecdsa_bytes.clone(), rpc_endpoint.clone())
841 .send()
842 .await
843 .map_err(|e| Error::Contract(e.to_string()))?
844 .get_receipt()
845 .await?
846 };
847
848 Ok(transaction_result_from_receipt(&receipt))
849 }
850
851 pub async fn unregister_operator(&self, blueprint_id: u64) -> Result<TransactionResult> {
853 let wallet = self.wallet()?;
854 let provider = ProviderBuilder::new()
855 .wallet(wallet)
856 .connect(self.config.http_rpc_endpoint.as_str())
857 .await
858 .map_err(Error::Transport)?;
859 let contract = ITangle::new(self.tangle_address, &provider);
860
861 let receipt = contract
862 .unregisterOperator(blueprint_id)
863 .send()
864 .await
865 .map_err(|e| Error::Contract(e.to_string()))?
866 .get_receipt()
867 .await?;
868
869 Ok(transaction_result_from_receipt(&receipt))
870 }
871
872 pub async fn blueprint_count(&self) -> Result<u64> {
874 let contract = self.tangle_contract();
875 contract
876 .blueprintCount()
877 .call()
878 .await
879 .map_err(|e| Error::Contract(e.to_string()))
880 }
881
882 pub async fn service_count(&self) -> Result<u64> {
884 let contract = self.tangle_contract();
885 contract
886 .serviceCount()
887 .call()
888 .await
889 .map_err(|e| Error::Contract(e.to_string()))
890 }
891
892 pub async fn get_service_request(
894 &self,
895 request_id: u64,
896 ) -> Result<ITangleTypes::ServiceRequest> {
897 let contract = self.tangle_contract();
898 contract
899 .getServiceRequest(request_id)
900 .call()
901 .await
902 .map_err(|e| Error::Contract(e.to_string()))
903 }
904
905 pub async fn service_request_count(&self) -> Result<u64> {
907 let mut data = Vec::with_capacity(4);
908 let selector = keccak256("serviceRequestCount()".as_bytes());
909 data.extend_from_slice(&selector[..4]);
910
911 let mut request = TransactionRequest::default();
912 request.to = Some(TxKind::Call(self.tangle_address));
913 request.input = TransactionInput::new(Bytes::from(data));
914
915 let response = self
916 .provider
917 .call(request)
918 .await
919 .map_err(Error::Transport)?;
920
921 if response.len() < 32 {
922 return Err(Error::Contract(
923 "serviceRequestCount returned malformed data".into(),
924 ));
925 }
926
927 let raw = response.as_ref();
928 let mut buf = [0u8; 8];
929 buf.copy_from_slice(&raw[24..32]);
930 Ok(u64::from_be_bytes(buf))
931 }
932
933 pub async fn get_job_call(
935 &self,
936 service_id: u64,
937 call_id: u64,
938 ) -> Result<ITangleTypes::JobCall> {
939 let contract = self.tangle_contract();
940 contract
941 .getJobCall(service_id, call_id)
942 .call()
943 .await
944 .map_err(|e| Error::Contract(e.to_string()))
945 }
946
947 pub async fn get_operator_metadata(
949 &self,
950 blueprint_id: u64,
951 operator: Address,
952 ) -> Result<OperatorMetadata> {
953 let contract = self.tangle_contract();
954 let prefs = contract
955 .getOperatorPreferences(blueprint_id, operator)
956 .call()
957 .await
958 .map_err(|e| Error::Contract(format!("getOperatorPreferences failed: {e}")))?;
959 let restaking_meta = self
960 .restaking_contract()
961 .getOperatorMetadata(operator)
962 .call()
963 .await
964 .map_err(|e| Error::Contract(format!("getOperatorMetadata failed: {e}")))?;
965 let public_key = normalize_public_key(&prefs.ecdsaPublicKey.0)?;
966 Ok(OperatorMetadata {
967 public_key,
968 rpc_endpoint: prefs.rpcAddress.to_string(),
969 restaking: RestakingMetadata {
970 stake: restaking_meta.stake,
971 delegation_count: restaking_meta.delegationCount,
972 status: RestakingStatus::from(restaking_meta.status),
973 leaving_round: restaking_meta.leavingRound,
974 },
975 })
976 }
977
978 #[allow(clippy::too_many_arguments)]
980 pub async fn request_service(
981 &self,
982 params: ServiceRequestParams,
983 ) -> Result<(TransactionResult, u64)> {
984 let wallet = self.wallet()?;
985 let provider = ProviderBuilder::new()
986 .wallet(wallet)
987 .connect(self.config.http_rpc_endpoint.as_str())
988 .await
989 .map_err(Error::Transport)?;
990 let contract = ITangle::new(self.tangle_address, &provider);
991
992 let ServiceRequestParams {
993 blueprint_id,
994 operators,
995 operator_exposures,
996 permitted_callers,
997 config,
998 ttl,
999 payment_token,
1000 payment_amount,
1001 security_requirements,
1002 } = params;
1003 let confidentiality = 0u8;
1004
1005 let is_native_payment = payment_token == Address::ZERO && payment_amount > U256::ZERO;
1006
1007 if payment_token != Address::ZERO && payment_amount > U256::ZERO {
1009 self.erc20_approve(payment_token, self.tangle_address, payment_amount)
1010 .await?;
1011 }
1012
1013 let request_id_hint = if !security_requirements.is_empty() {
1014 let mut call = contract.requestServiceWithSecurity(
1015 blueprint_id,
1016 operators.clone(),
1017 security_requirements.clone(),
1018 config.clone(),
1019 permitted_callers.clone(),
1020 ttl,
1021 payment_token,
1022 payment_amount,
1023 confidentiality,
1024 );
1025 call = call.from(self.account());
1026 if is_native_payment {
1027 call = call.value(payment_amount);
1028 }
1029 call.call().await.ok()
1030 } else if let Some(ref exposures) = operator_exposures {
1031 let mut call = contract.requestServiceWithExposure(
1032 blueprint_id,
1033 operators.clone(),
1034 exposures.clone(),
1035 config.clone(),
1036 permitted_callers.clone(),
1037 ttl,
1038 payment_token,
1039 payment_amount,
1040 confidentiality,
1041 );
1042 call = call.from(self.account());
1043 if is_native_payment {
1044 call = call.value(payment_amount);
1045 }
1046 call.call().await.ok()
1047 } else {
1048 let mut call = contract.requestService(
1049 blueprint_id,
1050 operators.clone(),
1051 config.clone(),
1052 permitted_callers.clone(),
1053 ttl,
1054 payment_token,
1055 payment_amount,
1056 confidentiality,
1057 );
1058 call = call.from(self.account());
1059 if is_native_payment {
1060 call = call.value(payment_amount);
1061 }
1062 call.call().await.ok()
1063 };
1064 let pre_count = self.service_request_count().await.ok();
1065
1066 let pending_tx = if !security_requirements.is_empty() {
1067 let mut call = contract.requestServiceWithSecurity(
1068 blueprint_id,
1069 operators.clone(),
1070 security_requirements.clone(),
1071 config.clone(),
1072 permitted_callers.clone(),
1073 ttl,
1074 payment_token,
1075 payment_amount,
1076 confidentiality,
1077 );
1078 if is_native_payment {
1079 call = call.value(payment_amount);
1080 }
1081 call.send().await
1082 } else if let Some(exposures) = operator_exposures {
1083 let mut call = contract.requestServiceWithExposure(
1084 blueprint_id,
1085 operators.clone(),
1086 exposures,
1087 config.clone(),
1088 permitted_callers.clone(),
1089 ttl,
1090 payment_token,
1091 payment_amount,
1092 confidentiality,
1093 );
1094 if is_native_payment {
1095 call = call.value(payment_amount);
1096 }
1097 call.send().await
1098 } else {
1099 let mut call = contract.requestService(
1100 blueprint_id,
1101 operators.clone(),
1102 config.clone(),
1103 permitted_callers.clone(),
1104 ttl,
1105 payment_token,
1106 payment_amount,
1107 confidentiality,
1108 );
1109 if is_native_payment {
1110 call = call.value(payment_amount);
1111 }
1112 call.send().await
1113 }
1114 .map_err(|e| Error::Contract(e.to_string()))?;
1115
1116 let receipt = pending_tx.get_receipt().await?;
1117 if !receipt.status() {
1118 return Err(Error::Contract(
1119 "requestService transaction reverted".into(),
1120 ));
1121 }
1122
1123 let request_id = match self.extract_request_id(&receipt, blueprint_id).await {
1124 Ok(id) => id,
1125 Err(err) => {
1126 if let Some(id) = request_id_hint {
1127 return Ok((transaction_result_from_receipt(&receipt), id));
1128 }
1129 if let Some(count) = pre_count {
1130 return Ok((transaction_result_from_receipt(&receipt), count));
1131 }
1132 return Err(err);
1133 }
1134 };
1135
1136 Ok((transaction_result_from_receipt(&receipt), request_id))
1137 }
1138
1139 pub async fn join_service(
1141 &self,
1142 service_id: u64,
1143 exposure_bps: u16,
1144 ) -> Result<TransactionResult> {
1145 let wallet = self.wallet()?;
1146 let provider = ProviderBuilder::new()
1147 .wallet(wallet)
1148 .connect(self.config.http_rpc_endpoint.as_str())
1149 .await
1150 .map_err(Error::Transport)?;
1151 let contract = ITangle::new(self.tangle_address, &provider);
1152
1153 let receipt = contract
1154 .joinService(service_id, exposure_bps)
1155 .send()
1156 .await
1157 .map_err(|e| Error::Contract(e.to_string()))?
1158 .get_receipt()
1159 .await?;
1160
1161 Ok(transaction_result_from_receipt(&receipt))
1162 }
1163
1164 pub async fn join_service_with_commitments(
1169 &self,
1170 service_id: u64,
1171 exposure_bps: u16,
1172 commitments: Vec<ITangleTypes::AssetSecurityCommitment>,
1173 ) -> Result<TransactionResult> {
1174 let wallet = self.wallet()?;
1175 let provider = ProviderBuilder::new()
1176 .wallet(wallet)
1177 .connect(self.config.http_rpc_endpoint.as_str())
1178 .await
1179 .map_err(Error::Transport)?;
1180 let contract = ITangle::new(self.tangle_address, &provider);
1181
1182 let receipt = contract
1183 .joinServiceWithCommitments(service_id, exposure_bps, commitments)
1184 .send()
1185 .await
1186 .map_err(|e| Error::Contract(e.to_string()))?
1187 .get_receipt()
1188 .await?;
1189
1190 Ok(transaction_result_from_receipt(&receipt))
1191 }
1192
1193 pub async fn leave_service(&self, service_id: u64) -> Result<TransactionResult> {
1201 let wallet = self.wallet()?;
1202 let provider = ProviderBuilder::new()
1203 .wallet(wallet)
1204 .connect(self.config.http_rpc_endpoint.as_str())
1205 .await
1206 .map_err(Error::Transport)?;
1207 let contract = ITangle::new(self.tangle_address, &provider);
1208
1209 let receipt = contract
1210 .leaveService(service_id)
1211 .send()
1212 .await
1213 .map_err(|e| Error::Contract(e.to_string()))?
1214 .get_receipt()
1215 .await?;
1216
1217 Ok(transaction_result_from_receipt(&receipt))
1218 }
1219
1220 pub async fn schedule_exit(&self, service_id: u64) -> Result<TransactionResult> {
1228 let wallet = self.wallet()?;
1229 let provider = ProviderBuilder::new()
1230 .wallet(wallet)
1231 .connect(self.config.http_rpc_endpoint.as_str())
1232 .await
1233 .map_err(Error::Transport)?;
1234 let contract = ITangle::new(self.tangle_address, &provider);
1235
1236 let receipt = contract
1237 .scheduleExit(service_id)
1238 .send()
1239 .await
1240 .map_err(|e| Error::Contract(e.to_string()))?
1241 .get_receipt()
1242 .await?;
1243
1244 Ok(transaction_result_from_receipt(&receipt))
1245 }
1246
1247 pub async fn execute_exit(&self, service_id: u64) -> Result<TransactionResult> {
1252 let wallet = self.wallet()?;
1253 let provider = ProviderBuilder::new()
1254 .wallet(wallet)
1255 .connect(self.config.http_rpc_endpoint.as_str())
1256 .await
1257 .map_err(Error::Transport)?;
1258 let contract = ITangle::new(self.tangle_address, &provider);
1259
1260 let receipt = contract
1261 .executeExit(service_id)
1262 .send()
1263 .await
1264 .map_err(|e| Error::Contract(e.to_string()))?
1265 .get_receipt()
1266 .await?;
1267
1268 Ok(transaction_result_from_receipt(&receipt))
1269 }
1270
1271 pub async fn cancel_exit(&self, service_id: u64) -> Result<TransactionResult> {
1276 let wallet = self.wallet()?;
1277 let provider = ProviderBuilder::new()
1278 .wallet(wallet)
1279 .connect(self.config.http_rpc_endpoint.as_str())
1280 .await
1281 .map_err(Error::Transport)?;
1282 let contract = ITangle::new(self.tangle_address, &provider);
1283
1284 let receipt = contract
1285 .cancelExit(service_id)
1286 .send()
1287 .await
1288 .map_err(|e| Error::Contract(e.to_string()))?
1289 .get_receipt()
1290 .await?;
1291
1292 Ok(transaction_result_from_receipt(&receipt))
1293 }
1294
1295 pub async fn approve_service(
1297 &self,
1298 request_id: u64,
1299 restaking_percent: u8,
1300 ) -> Result<TransactionResult> {
1301 let wallet = self.wallet()?;
1302 let provider = ProviderBuilder::new()
1303 .wallet(wallet)
1304 .connect(self.config.http_rpc_endpoint.as_str())
1305 .await
1306 .map_err(Error::Transport)?;
1307 let contract = ITangle::new(self.tangle_address, &provider);
1308
1309 let receipt = contract
1310 .approveService(request_id, restaking_percent)
1311 .send()
1312 .await
1313 .map_err(|e| Error::Contract(e.to_string()))?
1314 .get_receipt()
1315 .await?;
1316
1317 Ok(transaction_result_from_receipt(&receipt))
1318 }
1319
1320 pub async fn approve_service_with_commitments(
1322 &self,
1323 request_id: u64,
1324 commitments: Vec<ITangleTypes::AssetSecurityCommitment>,
1325 ) -> Result<TransactionResult> {
1326 let wallet = self.wallet()?;
1327 let provider = ProviderBuilder::new()
1328 .wallet(wallet)
1329 .connect(self.config.http_rpc_endpoint.as_str())
1330 .await
1331 .map_err(Error::Transport)?;
1332 let contract = ITangle::new(self.tangle_address, &provider);
1333
1334 let receipt = contract
1335 .approveServiceWithCommitments(request_id, commitments)
1336 .send()
1337 .await
1338 .map_err(|e| Error::Contract(e.to_string()))?
1339 .get_receipt()
1340 .await?;
1341
1342 Ok(transaction_result_from_receipt(&receipt))
1343 }
1344
1345 pub async fn reject_service(&self, request_id: u64) -> Result<TransactionResult> {
1347 let wallet = self.wallet()?;
1348 let provider = ProviderBuilder::new()
1349 .wallet(wallet)
1350 .connect(self.config.http_rpc_endpoint.as_str())
1351 .await
1352 .map_err(Error::Transport)?;
1353 let contract = ITangle::new(self.tangle_address, &provider);
1354
1355 let receipt = contract
1356 .rejectService(request_id)
1357 .send()
1358 .await
1359 .map_err(|e| Error::Contract(e.to_string()))?
1360 .get_receipt()
1361 .await?;
1362
1363 Ok(transaction_result_from_receipt(&receipt))
1364 }
1365
1366 pub async fn is_operator(&self, operator: Address) -> Result<bool> {
1372 let contract = self.restaking_contract();
1373 contract
1374 .isOperator(operator)
1375 .call()
1376 .await
1377 .map_err(|e| Error::Contract(e.to_string()))
1378 }
1379
1380 pub async fn is_operator_active(&self, operator: Address) -> Result<bool> {
1382 let contract = self.restaking_contract();
1383 contract
1384 .isOperatorActive(operator)
1385 .call()
1386 .await
1387 .map_err(|e| Error::Contract(e.to_string()))
1388 }
1389
1390 pub async fn get_operator_stake(&self, operator: Address) -> Result<U256> {
1392 let contract = self.restaking_contract();
1393 contract
1394 .getOperatorStake(operator)
1395 .call()
1396 .await
1397 .map_err(|e| Error::Contract(e.to_string()))
1398 }
1399
1400 pub async fn min_operator_stake(&self) -> Result<U256> {
1402 let contract = self.restaking_contract();
1403 contract
1404 .minOperatorStake()
1405 .call()
1406 .await
1407 .map_err(|e| Error::Contract(e.to_string()))
1408 }
1409
1410 pub async fn operator_status(
1412 &self,
1413 service_id: u64,
1414 operator: Address,
1415 ) -> Result<OperatorStatusSnapshot> {
1416 if self.status_registry_address.is_zero() {
1417 return Err(Error::MissingStatusRegistry);
1418 }
1419 let contract = self.status_registry_contract();
1420
1421 let last_heartbeat = contract
1422 .getLastHeartbeat(service_id, operator)
1423 .call()
1424 .await
1425 .map_err(|e| Error::Contract(e.to_string()))?;
1426 let status_code = contract
1427 .getOperatorStatus(service_id, operator)
1428 .call()
1429 .await
1430 .map_err(|e| Error::Contract(e.to_string()))?;
1431 let online = contract
1432 .isOnline(service_id, operator)
1433 .call()
1434 .await
1435 .map_err(|e| Error::Contract(e.to_string()))?;
1436
1437 let last_heartbeat = u64::try_from(last_heartbeat).unwrap_or(u64::MAX);
1438
1439 Ok(OperatorStatusSnapshot {
1440 service_id,
1441 operator,
1442 status_code,
1443 last_heartbeat,
1444 online,
1445 })
1446 }
1447
1448 pub async fn get_restaking_metadata(&self, operator: Address) -> Result<RestakingMetadata> {
1450 let restaking_meta = self
1451 .restaking_contract()
1452 .getOperatorMetadata(operator)
1453 .call()
1454 .await
1455 .map_err(|e| Error::Contract(format!("getOperatorMetadata failed: {e}")))?;
1456 Ok(RestakingMetadata {
1457 stake: restaking_meta.stake,
1458 delegation_count: restaking_meta.delegationCount,
1459 status: RestakingStatus::from(restaking_meta.status),
1460 leaving_round: restaking_meta.leavingRound,
1461 })
1462 }
1463
1464 pub async fn get_operator_self_stake(&self, operator: Address) -> Result<U256> {
1466 let contract = self.restaking_contract();
1467 contract
1468 .getOperatorSelfStake(operator)
1469 .call()
1470 .await
1471 .map_err(|e| Error::Contract(e.to_string()))
1472 }
1473
1474 pub async fn get_operator_delegated_stake(&self, operator: Address) -> Result<U256> {
1476 let contract = self.restaking_contract();
1477 contract
1478 .getOperatorDelegatedStake(operator)
1479 .call()
1480 .await
1481 .map_err(|e| Error::Contract(e.to_string()))
1482 }
1483
1484 pub async fn get_operator_delegators(&self, operator: Address) -> Result<Vec<Address>> {
1486 let contract = self.restaking_contract();
1487 contract
1488 .getOperatorDelegators(operator)
1489 .call()
1490 .await
1491 .map_err(|e| Error::Contract(e.to_string()))
1492 }
1493
1494 pub async fn get_operator_delegator_count(&self, operator: Address) -> Result<u64> {
1496 let contract = self.restaking_contract();
1497 let count = contract
1498 .getOperatorDelegatorCount(operator)
1499 .call()
1500 .await
1501 .map_err(|e| Error::Contract(e.to_string()))?;
1502 Ok(u64::try_from(count).unwrap_or(u64::MAX))
1503 }
1504
1505 pub async fn restaking_round(&self) -> Result<u64> {
1507 let contract = self.restaking_contract();
1508 contract
1509 .currentRound()
1510 .call()
1511 .await
1512 .map_err(|e| Error::Contract(e.to_string()))
1513 }
1514
1515 pub async fn operator_commission_bps(&self) -> Result<u16> {
1517 let contract = self.restaking_contract();
1518 contract
1519 .operatorCommissionBps()
1520 .call()
1521 .await
1522 .map_err(|e| Error::Contract(e.to_string()))
1523 }
1524
1525 pub async fn get_delegation_mode(&self, operator: Address) -> Result<DelegationMode> {
1536 let contract = self.restaking_contract();
1537 let mode = contract
1538 .getDelegationMode(operator)
1539 .call()
1540 .await
1541 .map_err(|e| Error::Contract(e.to_string()))?;
1542 Ok(DelegationMode::from(mode))
1543 }
1544
1545 pub async fn is_delegator_whitelisted(
1547 &self,
1548 operator: Address,
1549 delegator: Address,
1550 ) -> Result<bool> {
1551 let contract = self.restaking_contract();
1552 contract
1553 .isWhitelisted(operator, delegator)
1554 .call()
1555 .await
1556 .map_err(|e| Error::Contract(e.to_string()))
1557 }
1558
1559 pub async fn can_delegate(&self, operator: Address, delegator: Address) -> Result<bool> {
1563 let contract = self.restaking_contract();
1564 contract
1565 .canDelegate(operator, delegator)
1566 .call()
1567 .await
1568 .map_err(|e| Error::Contract(e.to_string()))
1569 }
1570
1571 pub async fn set_delegation_mode(&self, mode: DelegationMode) -> Result<TransactionResult> {
1579 let wallet = self.wallet()?;
1580 let provider = ProviderBuilder::new()
1581 .wallet(wallet)
1582 .connect(self.config.http_rpc_endpoint.as_str())
1583 .await
1584 .map_err(Error::Transport)?;
1585 let contract = IMultiAssetDelegation::new(self.restaking_address, provider);
1586
1587 let mode_value: u8 = match mode {
1588 DelegationMode::Disabled => 0,
1589 DelegationMode::Whitelist => 1,
1590 DelegationMode::Open => 2,
1591 DelegationMode::Unknown(v) => v,
1592 };
1593
1594 let receipt = contract
1595 .setDelegationMode(mode_value)
1596 .send()
1597 .await
1598 .map_err(|e| Error::Contract(e.to_string()))?
1599 .get_receipt()
1600 .await
1601 .map_err(|e| Error::Contract(e.to_string()))?;
1602
1603 Ok(transaction_result_from_receipt(&receipt))
1604 }
1605
1606 pub async fn set_delegation_whitelist(
1615 &self,
1616 delegators: Vec<Address>,
1617 approved: bool,
1618 ) -> Result<TransactionResult> {
1619 let wallet = self.wallet()?;
1620 let provider = ProviderBuilder::new()
1621 .wallet(wallet)
1622 .connect(self.config.http_rpc_endpoint.as_str())
1623 .await
1624 .map_err(Error::Transport)?;
1625 let contract = IMultiAssetDelegation::new(self.restaking_address, provider);
1626
1627 let receipt = contract
1628 .setDelegationWhitelist(delegators, approved)
1629 .send()
1630 .await
1631 .map_err(|e| Error::Contract(e.to_string()))?
1632 .get_receipt()
1633 .await
1634 .map_err(|e| Error::Contract(e.to_string()))?;
1635
1636 Ok(transaction_result_from_receipt(&receipt))
1637 }
1638
1639 pub async fn erc20_allowance(
1641 &self,
1642 token: Address,
1643 owner: Address,
1644 spender: Address,
1645 ) -> Result<U256> {
1646 let contract = IERC20::new(token, Arc::clone(&self.provider));
1647 contract
1648 .allowance(owner, spender)
1649 .call()
1650 .await
1651 .map_err(|e| Error::Contract(e.to_string()))
1652 }
1653
1654 pub async fn erc20_balance(&self, token: Address, owner: Address) -> Result<U256> {
1656 let contract = IERC20::new(token, Arc::clone(&self.provider));
1657 contract
1658 .balanceOf(owner)
1659 .call()
1660 .await
1661 .map_err(|e| Error::Contract(e.to_string()))
1662 }
1663
1664 pub async fn erc20_approve(
1666 &self,
1667 token: Address,
1668 spender: Address,
1669 amount: U256,
1670 ) -> Result<TransactionResult> {
1671 let wallet = self.wallet()?;
1672 let provider = ProviderBuilder::new()
1673 .wallet(wallet)
1674 .connect(self.config.http_rpc_endpoint.as_str())
1675 .await
1676 .map_err(Error::Transport)?;
1677 let contract = IERC20::new(token, &provider);
1678
1679 let receipt = contract
1680 .approve(spender, amount)
1681 .send()
1682 .await
1683 .map_err(|e| Error::Contract(e.to_string()))?
1684 .get_receipt()
1685 .await?;
1686
1687 Ok(transaction_result_from_receipt(&receipt))
1688 }
1689
1690 pub async fn get_deposit_info(
1692 &self,
1693 delegator: Address,
1694 token: Address,
1695 ) -> Result<DepositInfo> {
1696 let contract = self.restaking_contract();
1697 let deposit = contract
1698 .getDeposit(delegator, token)
1699 .call()
1700 .await
1701 .map_err(|e| Error::Contract(e.to_string()))?;
1702 Ok(DepositInfo {
1703 amount: deposit.amount,
1704 delegated_amount: deposit.delegatedAmount,
1705 })
1706 }
1707
1708 pub async fn get_locks(&self, delegator: Address, token: Address) -> Result<Vec<LockInfo>> {
1710 let contract = self.restaking_contract();
1711 let locks = contract
1712 .getLocks(delegator, token)
1713 .call()
1714 .await
1715 .map_err(|e| Error::Contract(e.to_string()))?;
1716 Ok(locks
1717 .into_iter()
1718 .map(|lock| LockInfo {
1719 amount: lock.amount,
1720 multiplier: LockMultiplier::from(lock.multiplier),
1721 expiry_block: lock.expiryBlock,
1722 })
1723 .collect())
1724 }
1725
1726 pub async fn get_delegations(&self, delegator: Address) -> Result<Vec<DelegationInfo>> {
1728 let contract = self.restaking_contract();
1729 let delegations = contract
1730 .getDelegations(delegator)
1731 .call()
1732 .await
1733 .map_err(|e| Error::Contract(e.to_string()))?;
1734 Ok(delegations
1735 .into_iter()
1736 .map(|delegation| DelegationInfo {
1737 operator: delegation.operator,
1738 shares: delegation.shares,
1739 asset: asset_info_from_types(delegation.asset),
1740 selection_mode: BlueprintSelectionMode::from(delegation.selectionMode),
1741 })
1742 .collect())
1743 }
1744
1745 pub async fn get_delegations_with_blueprints(
1747 &self,
1748 delegator: Address,
1749 ) -> Result<Vec<DelegationRecord>> {
1750 let delegations = self.get_delegations(delegator).await?;
1751 let mut records = Vec::with_capacity(delegations.len());
1752 for (idx, info) in delegations.into_iter().enumerate() {
1753 let blueprint_ids = if matches!(info.selection_mode, BlueprintSelectionMode::Fixed) {
1754 self.get_delegation_blueprints(delegator, idx as u64)
1755 .await?
1756 } else {
1757 Vec::new()
1758 };
1759 records.push(DelegationRecord {
1760 info,
1761 blueprint_ids,
1762 });
1763 }
1764 Ok(records)
1765 }
1766
1767 pub async fn get_delegation_blueprints(
1769 &self,
1770 delegator: Address,
1771 index: u64,
1772 ) -> Result<Vec<u64>> {
1773 let contract = self.restaking_contract();
1774 let ids = contract
1775 .getDelegationBlueprints(delegator, U256::from(index))
1776 .call()
1777 .await
1778 .map_err(|e| Error::Contract(e.to_string()))?;
1779 Ok(ids)
1780 }
1781
1782 pub async fn get_pending_unstakes(&self, delegator: Address) -> Result<Vec<PendingUnstake>> {
1784 let contract = self.restaking_contract();
1785 let unstakes = contract
1786 .getPendingUnstakes(delegator)
1787 .call()
1788 .await
1789 .map_err(|e| Error::Contract(e.to_string()))?;
1790 Ok(unstakes
1791 .into_iter()
1792 .map(|request| PendingUnstake {
1793 operator: request.operator,
1794 asset: asset_info_from_types(request.asset),
1795 shares: request.shares,
1796 requested_round: request.requestedRound,
1797 selection_mode: BlueprintSelectionMode::from(request.selectionMode),
1798 slash_factor_snapshot: request.slashFactorSnapshot,
1799 })
1800 .collect())
1801 }
1802
1803 pub async fn get_pending_withdrawals(
1805 &self,
1806 delegator: Address,
1807 ) -> Result<Vec<PendingWithdrawal>> {
1808 let contract = self.restaking_contract();
1809 let withdrawals = contract
1810 .getPendingWithdrawals(delegator)
1811 .call()
1812 .await
1813 .map_err(|e| Error::Contract(e.to_string()))?;
1814 Ok(withdrawals
1815 .into_iter()
1816 .map(|request| PendingWithdrawal {
1817 asset: asset_info_from_types(request.asset),
1818 amount: request.amount,
1819 requested_round: request.requestedRound,
1820 })
1821 .collect())
1822 }
1823
1824 pub async fn deposit_and_delegate_with_options(
1826 &self,
1827 operator: Address,
1828 token: Address,
1829 amount: U256,
1830 selection_mode: BlueprintSelectionMode,
1831 blueprint_ids: Vec<u64>,
1832 ) -> Result<TransactionResult> {
1833 let wallet = self.wallet()?;
1834 let provider = ProviderBuilder::new()
1835 .wallet(wallet)
1836 .connect(self.config.http_rpc_endpoint.as_str())
1837 .await
1838 .map_err(Error::Transport)?;
1839 let contract = IMultiAssetDelegation::new(self.restaking_address, &provider);
1840
1841 let mut call = contract.depositAndDelegateWithOptions(
1842 operator,
1843 token,
1844 amount,
1845 selection_mode_to_u8(selection_mode),
1846 blueprint_ids,
1847 );
1848 if token == Address::ZERO {
1849 call = call.value(amount);
1850 }
1851
1852 let receipt = call
1853 .send()
1854 .await
1855 .map_err(|e| Error::Contract(e.to_string()))?
1856 .get_receipt()
1857 .await?;
1858
1859 Ok(transaction_result_from_receipt(&receipt))
1860 }
1861
1862 pub async fn delegate_with_options(
1864 &self,
1865 operator: Address,
1866 token: Address,
1867 amount: U256,
1868 selection_mode: BlueprintSelectionMode,
1869 blueprint_ids: Vec<u64>,
1870 ) -> Result<TransactionResult> {
1871 let wallet = self.wallet()?;
1872 let provider = ProviderBuilder::new()
1873 .wallet(wallet)
1874 .connect(self.config.http_rpc_endpoint.as_str())
1875 .await
1876 .map_err(Error::Transport)?;
1877 let contract = IMultiAssetDelegation::new(self.restaking_address, &provider);
1878
1879 let receipt = contract
1880 .delegateWithOptions(
1881 operator,
1882 token,
1883 amount,
1884 selection_mode_to_u8(selection_mode),
1885 blueprint_ids,
1886 )
1887 .send()
1888 .await
1889 .map_err(|e| Error::Contract(e.to_string()))?
1890 .get_receipt()
1891 .await?;
1892
1893 Ok(transaction_result_from_receipt(&receipt))
1894 }
1895
1896 pub async fn schedule_delegator_unstake(
1898 &self,
1899 operator: Address,
1900 token: Address,
1901 amount: U256,
1902 ) -> Result<TransactionResult> {
1903 let wallet = self.wallet()?;
1904 let provider = ProviderBuilder::new()
1905 .wallet(wallet)
1906 .connect(self.config.http_rpc_endpoint.as_str())
1907 .await
1908 .map_err(Error::Transport)?;
1909 let contract = IMultiAssetDelegation::new(self.restaking_address, &provider);
1910
1911 let receipt = contract
1912 .scheduleDelegatorUnstake(operator, token, amount)
1913 .send()
1914 .await
1915 .map_err(|e| Error::Contract(e.to_string()))?
1916 .get_receipt()
1917 .await?;
1918
1919 Ok(transaction_result_from_receipt(&receipt))
1920 }
1921
1922 pub async fn execute_delegator_unstake(&self) -> Result<TransactionResult> {
1924 let wallet = self.wallet()?;
1925 let provider = ProviderBuilder::new()
1926 .wallet(wallet)
1927 .connect(self.config.http_rpc_endpoint.as_str())
1928 .await
1929 .map_err(Error::Transport)?;
1930 let contract = IMultiAssetDelegation::new(self.restaking_address, &provider);
1931
1932 let receipt = contract
1933 .executeDelegatorUnstake()
1934 .send()
1935 .await
1936 .map_err(|e| Error::Contract(e.to_string()))?
1937 .get_receipt()
1938 .await?;
1939
1940 Ok(transaction_result_from_receipt(&receipt))
1941 }
1942
1943 pub async fn execute_delegator_unstake_and_withdraw(
1945 &self,
1946 operator: Address,
1947 token: Address,
1948 shares: U256,
1949 requested_round: u64,
1950 receiver: Address,
1951 ) -> Result<TransactionResult> {
1952 let wallet = self.wallet()?;
1953 let provider = ProviderBuilder::new()
1954 .wallet(wallet)
1955 .connect(self.config.http_rpc_endpoint.as_str())
1956 .await
1957 .map_err(Error::Transport)?;
1958 let contract = IMultiAssetDelegation::new(self.restaking_address, &provider);
1959
1960 let receipt = contract
1961 .executeDelegatorUnstakeAndWithdraw(operator, token, shares, requested_round, receiver)
1962 .send()
1963 .await
1964 .map_err(|e| Error::Contract(e.to_string()))?
1965 .get_receipt()
1966 .await?;
1967
1968 Ok(transaction_result_from_receipt(&receipt))
1969 }
1970
1971 pub async fn schedule_withdraw(
1973 &self,
1974 token: Address,
1975 amount: U256,
1976 ) -> Result<TransactionResult> {
1977 let wallet = self.wallet()?;
1978 let provider = ProviderBuilder::new()
1979 .wallet(wallet)
1980 .connect(self.config.http_rpc_endpoint.as_str())
1981 .await
1982 .map_err(Error::Transport)?;
1983 let contract = IMultiAssetDelegation::new(self.restaking_address, &provider);
1984
1985 let receipt = contract
1986 .scheduleWithdraw(token, amount)
1987 .send()
1988 .await
1989 .map_err(|e| Error::Contract(e.to_string()))?
1990 .get_receipt()
1991 .await?;
1992
1993 Ok(transaction_result_from_receipt(&receipt))
1994 }
1995
1996 pub async fn execute_withdraw(&self) -> Result<TransactionResult> {
1998 let wallet = self.wallet()?;
1999 let provider = ProviderBuilder::new()
2000 .wallet(wallet)
2001 .connect(self.config.http_rpc_endpoint.as_str())
2002 .await
2003 .map_err(Error::Transport)?;
2004 let contract = IMultiAssetDelegation::new(self.restaking_address, &provider);
2005
2006 let receipt = contract
2007 .executeWithdraw()
2008 .send()
2009 .await
2010 .map_err(|e| Error::Contract(e.to_string()))?
2011 .get_receipt()
2012 .await?;
2013
2014 Ok(transaction_result_from_receipt(&receipt))
2015 }
2016
2017 pub async fn schedule_operator_unstake(&self, amount: U256) -> Result<TransactionResult> {
2019 let wallet = self.wallet()?;
2020 let provider = ProviderBuilder::new()
2021 .wallet(wallet)
2022 .connect(self.config.http_rpc_endpoint.as_str())
2023 .await
2024 .map_err(Error::Transport)?;
2025 let contract = IMultiAssetDelegation::new(self.restaking_address, &provider);
2026
2027 let receipt = contract
2028 .scheduleOperatorUnstake(amount)
2029 .send()
2030 .await
2031 .map_err(|e| Error::Contract(e.to_string()))?
2032 .get_receipt()
2033 .await?;
2034
2035 Ok(transaction_result_from_receipt(&receipt))
2036 }
2037
2038 pub async fn execute_operator_unstake(&self) -> Result<TransactionResult> {
2040 let wallet = self.wallet()?;
2041 let provider = ProviderBuilder::new()
2042 .wallet(wallet)
2043 .connect(self.config.http_rpc_endpoint.as_str())
2044 .await
2045 .map_err(Error::Transport)?;
2046 let contract = IMultiAssetDelegation::new(self.restaking_address, &provider);
2047
2048 let receipt = contract
2049 .executeOperatorUnstake()
2050 .send()
2051 .await
2052 .map_err(|e| Error::Contract(e.to_string()))?
2053 .get_receipt()
2054 .await?;
2055
2056 Ok(transaction_result_from_receipt(&receipt))
2057 }
2058
2059 pub async fn start_leaving(&self) -> Result<TransactionResult> {
2061 let wallet = self.wallet()?;
2062 let provider = ProviderBuilder::new()
2063 .wallet(wallet)
2064 .connect(self.config.http_rpc_endpoint.as_str())
2065 .await
2066 .map_err(Error::Transport)?;
2067 let contract = IMultiAssetDelegation::new(self.restaking_address, &provider);
2068
2069 let receipt = contract
2070 .startLeaving()
2071 .send()
2072 .await
2073 .map_err(|e| Error::Contract(e.to_string()))?
2074 .get_receipt()
2075 .await?;
2076
2077 Ok(transaction_result_from_receipt(&receipt))
2078 }
2079
2080 pub async fn complete_leaving(&self) -> Result<TransactionResult> {
2082 let wallet = self.wallet()?;
2083 let provider = ProviderBuilder::new()
2084 .wallet(wallet)
2085 .connect(self.config.http_rpc_endpoint.as_str())
2086 .await
2087 .map_err(Error::Transport)?;
2088 let contract = IMultiAssetDelegation::new(self.restaking_address, &provider);
2089
2090 let receipt = contract
2091 .completeLeaving()
2092 .send()
2093 .await
2094 .map_err(|e| Error::Contract(e.to_string()))?
2095 .get_receipt()
2096 .await?;
2097
2098 Ok(transaction_result_from_receipt(&receipt))
2099 }
2100
2101 pub async fn operator_bond_token(&self) -> Result<Address> {
2110 let contract = self.restaking_contract();
2111 contract
2112 .operatorBondToken()
2113 .call()
2114 .await
2115 .map_err(|e| Error::Contract(e.to_string()))
2116 }
2117
2118 pub async fn register_operator_restaking(
2123 &self,
2124 stake_amount: U256,
2125 ) -> Result<TransactionResult> {
2126 let bond_token = self.operator_bond_token().await?;
2127
2128 if bond_token != Address::ZERO {
2130 self.erc20_approve(bond_token, self.restaking_address, stake_amount)
2131 .await?;
2132 }
2133
2134 let wallet = self.wallet()?;
2135 let provider = ProviderBuilder::new()
2136 .wallet(wallet)
2137 .connect(self.config.http_rpc_endpoint.as_str())
2138 .await
2139 .map_err(Error::Transport)?;
2140 let contract = IMultiAssetDelegation::new(self.restaking_address, &provider);
2141
2142 let receipt = if bond_token == Address::ZERO {
2143 contract
2145 .registerOperator()
2146 .value(stake_amount)
2147 .send()
2148 .await
2149 .map_err(|e| Error::Contract(e.to_string()))?
2150 .get_receipt()
2151 .await?
2152 } else {
2153 contract
2155 .registerOperatorWithAsset(bond_token, stake_amount)
2156 .send()
2157 .await
2158 .map_err(|e| Error::Contract(e.to_string()))?
2159 .get_receipt()
2160 .await?
2161 };
2162
2163 Ok(transaction_result_from_receipt(&receipt))
2164 }
2165
2166 pub async fn increase_stake(&self, amount: U256) -> Result<TransactionResult> {
2171 let bond_token = self.operator_bond_token().await?;
2172
2173 if bond_token != Address::ZERO {
2175 self.erc20_approve(bond_token, self.restaking_address, amount)
2176 .await?;
2177 }
2178
2179 let wallet = self.wallet()?;
2180 let provider = ProviderBuilder::new()
2181 .wallet(wallet)
2182 .connect(self.config.http_rpc_endpoint.as_str())
2183 .await
2184 .map_err(Error::Transport)?;
2185 let contract = IMultiAssetDelegation::new(self.restaking_address, &provider);
2186
2187 let receipt = if bond_token == Address::ZERO {
2188 contract
2190 .increaseStake()
2191 .value(amount)
2192 .send()
2193 .await
2194 .map_err(|e| Error::Contract(e.to_string()))?
2195 .get_receipt()
2196 .await?
2197 } else {
2198 contract
2200 .increaseStakeWithAsset(bond_token, amount)
2201 .send()
2202 .await
2203 .map_err(|e| Error::Contract(e.to_string()))?
2204 .get_receipt()
2205 .await?
2206 };
2207
2208 Ok(transaction_result_from_receipt(&receipt))
2209 }
2210
2211 pub async fn deposit_native(&self, amount: U256) -> Result<TransactionResult> {
2215 let wallet = self.wallet()?;
2216 let provider = ProviderBuilder::new()
2217 .wallet(wallet)
2218 .connect(self.config.http_rpc_endpoint.as_str())
2219 .await
2220 .map_err(Error::Transport)?;
2221 let contract = IMultiAssetDelegation::new(self.restaking_address, &provider);
2222
2223 let receipt = contract
2224 .deposit()
2225 .value(amount)
2226 .send()
2227 .await
2228 .map_err(|e| Error::Contract(e.to_string()))?
2229 .get_receipt()
2230 .await?;
2231
2232 Ok(transaction_result_from_receipt(&receipt))
2233 }
2234
2235 pub async fn deposit_erc20(&self, token: Address, amount: U256) -> Result<TransactionResult> {
2239 let wallet = self.wallet()?;
2240 let provider = ProviderBuilder::new()
2241 .wallet(wallet)
2242 .connect(self.config.http_rpc_endpoint.as_str())
2243 .await
2244 .map_err(Error::Transport)?;
2245 let contract = IMultiAssetDelegation::new(self.restaking_address, &provider);
2246
2247 let receipt = contract
2248 .depositERC20(token, amount)
2249 .send()
2250 .await
2251 .map_err(|e| Error::Contract(e.to_string()))?
2252 .get_receipt()
2253 .await?;
2254
2255 Ok(transaction_result_from_receipt(&receipt))
2256 }
2257
2258 pub async fn get_blueprint_manager(&self, service_id: u64) -> Result<Option<Address>> {
2264 let service = self.get_service(service_id).await?;
2265 let blueprint = self.get_blueprint(service.blueprintId).await?;
2266 if blueprint.manager == Address::ZERO {
2267 Ok(None)
2268 } else {
2269 Ok(Some(blueprint.manager))
2270 }
2271 }
2272
2273 pub async fn requires_aggregation(&self, service_id: u64, job_index: u8) -> Result<bool> {
2278 let manager = match self.get_blueprint_manager(service_id).await? {
2279 Some(m) => m,
2280 None => return Ok(false), };
2282
2283 let bsm = IBlueprintServiceManager::new(manager, Arc::clone(&self.provider));
2284 match bsm.requiresAggregation(service_id, job_index).call().await {
2285 Ok(required) => Ok(required),
2286 Err(_) => Ok(false), }
2288 }
2289
2290 pub async fn get_aggregation_threshold(
2296 &self,
2297 service_id: u64,
2298 job_index: u8,
2299 ) -> Result<(u16, u8)> {
2300 let manager = match self.get_blueprint_manager(service_id).await? {
2301 Some(m) => m,
2302 None => return Ok((6700, 0)), };
2304
2305 let bsm = IBlueprintServiceManager::new(manager, Arc::clone(&self.provider));
2306 match bsm
2307 .getAggregationThreshold(service_id, job_index)
2308 .call()
2309 .await
2310 {
2311 Ok(result) => Ok((result.thresholdBps, result.thresholdType)),
2312 Err(_) => Ok((6700, 0)), }
2314 }
2315
2316 pub async fn get_aggregation_config(
2320 &self,
2321 service_id: u64,
2322 job_index: u8,
2323 ) -> Result<AggregationConfig> {
2324 let requires_aggregation = self.requires_aggregation(service_id, job_index).await?;
2325 let (threshold_bps, threshold_type) = self
2326 .get_aggregation_threshold(service_id, job_index)
2327 .await?;
2328
2329 Ok(AggregationConfig {
2330 required: requires_aggregation,
2331 threshold_bps,
2332 threshold_type: if threshold_type == 0 {
2333 ThresholdType::CountBased
2334 } else {
2335 ThresholdType::StakeWeighted
2336 },
2337 })
2338 }
2339
2340 pub async fn submit_job(
2346 &self,
2347 service_id: u64,
2348 job_index: u8,
2349 inputs: Bytes,
2350 ) -> Result<JobSubmissionResult> {
2351 self.submit_job_with_value(service_id, job_index, inputs, U256::ZERO)
2352 .await
2353 }
2354
2355 pub async fn submit_job_with_value(
2360 &self,
2361 service_id: u64,
2362 job_index: u8,
2363 inputs: Bytes,
2364 value: U256,
2365 ) -> Result<JobSubmissionResult> {
2366 use crate::contracts::ITangle::submitJobCall;
2367 use alloy_sol_types::SolCall;
2368
2369 let wallet = self.wallet()?;
2370 let provider = ProviderBuilder::new()
2371 .wallet(wallet)
2372 .connect(self.config.http_rpc_endpoint.as_str())
2373 .await
2374 .map_err(Error::Transport)?;
2375
2376 let call = submitJobCall {
2377 serviceId: service_id,
2378 jobIndex: job_index,
2379 inputs,
2380 };
2381 let calldata = call.abi_encode();
2382
2383 let mut tx_request = TransactionRequest::default()
2384 .to(self.tangle_address)
2385 .input(calldata.into());
2386 if value > U256::ZERO {
2387 tx_request = tx_request.value(value);
2388 }
2389
2390 let pending_tx = provider
2391 .send_transaction(tx_request)
2392 .await
2393 .map_err(Error::Transport)?;
2394
2395 let receipt = pending_tx
2396 .get_receipt()
2397 .await
2398 .map_err(Error::PendingTransaction)?;
2399
2400 self.parse_job_submitted(&receipt)
2401 }
2402
2403 pub async fn submit_job_from_quote(
2408 &self,
2409 service_id: u64,
2410 job_index: u8,
2411 inputs: Bytes,
2412 quotes: Vec<ITangleTypes::SignedJobQuote>,
2413 ) -> Result<JobSubmissionResult> {
2414 use crate::contracts::ITangle::submitJobFromQuoteCall;
2415 use alloy_sol_types::SolCall;
2416
2417 let total_value: U256 = quotes.iter().map(|q| q.details.price).sum();
2419
2420 let wallet = self.wallet()?;
2421 let provider = ProviderBuilder::new()
2422 .wallet(wallet)
2423 .connect(self.config.http_rpc_endpoint.as_str())
2424 .await
2425 .map_err(Error::Transport)?;
2426
2427 let call = submitJobFromQuoteCall {
2428 serviceId: service_id,
2429 jobIndex: job_index,
2430 inputs,
2431 quotes,
2432 };
2433 let calldata = call.abi_encode();
2434
2435 let mut tx_request = TransactionRequest::default()
2436 .to(self.tangle_address)
2437 .input(calldata.into());
2438 if total_value > U256::ZERO {
2439 tx_request = tx_request.value(total_value);
2440 }
2441
2442 let pending_tx = provider
2443 .send_transaction(tx_request)
2444 .await
2445 .map_err(Error::Transport)?;
2446
2447 let receipt = pending_tx
2448 .get_receipt()
2449 .await
2450 .map_err(Error::PendingTransaction)?;
2451
2452 self.parse_job_submitted(&receipt)
2453 }
2454
2455 fn parse_job_submitted(&self, receipt: &TransactionReceipt) -> Result<JobSubmissionResult> {
2457 let tx = TransactionResult {
2458 tx_hash: receipt.transaction_hash,
2459 block_number: receipt.block_number,
2460 gas_used: receipt.gas_used,
2461 success: receipt.status(),
2462 };
2463
2464 let job_submitted_sig = keccak256("JobSubmitted(uint64,uint64,uint8,address,bytes)");
2465 let call_id = receipt
2466 .logs()
2467 .iter()
2468 .find_map(|log| {
2469 let topics = log.topics();
2470 if log.address() != self.tangle_address || topics.len() < 3 {
2471 return None;
2472 }
2473 if topics[0].0 != job_submitted_sig {
2474 return None;
2475 }
2476 let mut buf = [0u8; 32];
2477 buf.copy_from_slice(topics[2].as_slice());
2478 Some(U256::from_be_bytes(buf).to::<u64>())
2479 })
2480 .ok_or_else(|| {
2481 let status = receipt.status();
2482 let log_count = receipt.logs().len();
2483 let topics: Vec<String> = receipt
2484 .logs()
2485 .iter()
2486 .map(|log| {
2487 log.topics()
2488 .iter()
2489 .map(|topic| format!("{topic:#x}"))
2490 .collect::<Vec<_>>()
2491 .join(",")
2492 })
2493 .collect();
2494 Error::Contract(format!(
2495 "submitJob receipt missing JobSubmitted event (status={status:?}, logs={log_count}, topics={topics:?})"
2496 ))
2497 })?;
2498
2499 Ok(JobSubmissionResult { tx, call_id })
2500 }
2501
2502 pub async fn submit_result(
2514 &self,
2515 service_id: u64,
2516 call_id: u64,
2517 output: Bytes,
2518 ) -> Result<TransactionResult> {
2519 use crate::contracts::ITangle::submitResultCall;
2520 use alloy_sol_types::SolCall;
2521
2522 let wallet = self.wallet()?;
2523 let provider = ProviderBuilder::new()
2524 .wallet(wallet)
2525 .connect(self.config.http_rpc_endpoint.as_str())
2526 .await
2527 .map_err(Error::Transport)?;
2528
2529 let call = submitResultCall {
2530 serviceId: service_id,
2531 callId: call_id,
2532 result: output,
2533 };
2534 let calldata = call.abi_encode();
2535
2536 let tx_request = TransactionRequest::default()
2537 .to(self.tangle_address)
2538 .input(calldata.into());
2539
2540 let pending_tx = provider
2541 .send_transaction(tx_request)
2542 .await
2543 .map_err(Error::Transport)?;
2544
2545 let receipt = pending_tx
2546 .get_receipt()
2547 .await
2548 .map_err(Error::PendingTransaction)?;
2549
2550 Ok(TransactionResult {
2551 tx_hash: receipt.transaction_hash,
2552 block_number: receipt.block_number,
2553 gas_used: receipt.gas_used,
2554 success: receipt.status(),
2555 })
2556 }
2557
2558 pub async fn submit_aggregated_result(
2573 &self,
2574 service_id: u64,
2575 call_id: u64,
2576 output: Bytes,
2577 signer_bitmap: U256,
2578 aggregated_signature: [U256; 2],
2579 aggregated_pubkey: [U256; 4],
2580 ) -> Result<TransactionResult> {
2581 use crate::contracts::ITangle::submitAggregatedResultCall;
2582 use alloy_sol_types::SolCall;
2583
2584 let wallet = self.wallet()?;
2585 let provider = ProviderBuilder::new()
2586 .wallet(wallet)
2587 .connect(self.config.http_rpc_endpoint.as_str())
2588 .await
2589 .map_err(Error::Transport)?;
2590
2591 let call = submitAggregatedResultCall {
2592 serviceId: service_id,
2593 callId: call_id,
2594 output,
2595 signerBitmap: signer_bitmap,
2596 aggregatedSignature: aggregated_signature,
2597 aggregatedPubkey: aggregated_pubkey,
2598 };
2599 let calldata = call.abi_encode();
2600
2601 let tx_request = TransactionRequest::default()
2602 .to(self.tangle_address)
2603 .input(calldata.into());
2604
2605 let pending_tx = provider
2606 .send_transaction(tx_request)
2607 .await
2608 .map_err(Error::Transport)?;
2609
2610 let receipt = pending_tx
2611 .get_receipt()
2612 .await
2613 .map_err(Error::PendingTransaction)?;
2614
2615 Ok(TransactionResult {
2616 tx_hash: receipt.transaction_hash,
2617 block_number: receipt.block_number,
2618 gas_used: receipt.gas_used,
2619 success: receipt.status(),
2620 })
2621 }
2622
2623 async fn extract_request_id(
2624 &self,
2625 receipt: &TransactionReceipt,
2626 blueprint_id: u64,
2627 ) -> Result<u64> {
2628 if let Some(event) = receipt.decoded_log::<ITangle::ServiceRequested>() {
2629 return Ok(event.data.requestId);
2630 }
2631 if let Some(event) = receipt.decoded_log::<ITangle::ServiceRequestedWithSecurity>() {
2632 return Ok(event.data.requestId);
2633 }
2634
2635 let requested_sig = keccak256("ServiceRequested(uint64,uint64,address)".as_bytes());
2636 let requested_with_security_sig = keccak256(
2637 "ServiceRequestedWithSecurity(uint64,uint64,address,address[],((uint8,address),uint16,uint16)[])"
2638 .as_bytes(),
2639 );
2640
2641 for log in receipt.logs() {
2642 let topics = log.topics();
2643 if topics.is_empty() {
2644 continue;
2645 }
2646 let sig = topics[0].0;
2647 if sig != requested_sig && sig != requested_with_security_sig {
2648 continue;
2649 }
2650 if topics.len() < 2 {
2651 continue;
2652 }
2653
2654 let mut buf = [0u8; 32];
2655 buf.copy_from_slice(topics[1].as_slice());
2656 let id = U256::from_be_bytes(buf).to::<u64>();
2657 return Ok(id);
2658 }
2659
2660 if let Some(block_number) = receipt.block_number {
2661 let filter = Filter::new()
2662 .select(block_number)
2663 .address(self.tangle_address)
2664 .event_signature(vec![requested_sig, requested_with_security_sig]);
2665 if let Ok(logs) = self.get_logs(&filter).await {
2666 for log in logs {
2667 let topics = log.topics();
2668 if topics.len() < 2 {
2669 continue;
2670 }
2671 let mut buf = [0u8; 32];
2672 buf.copy_from_slice(topics[1].as_slice());
2673 let id = U256::from_be_bytes(buf).to::<u64>();
2674 return Ok(id);
2675 }
2676 }
2677 }
2678
2679 let count = self.service_request_count().await?;
2680 if count == 0 {
2681 return Err(Error::Contract(
2682 "requestService receipt missing ServiceRequested event".into(),
2683 ));
2684 }
2685
2686 let account = self.account();
2687 let start = count.saturating_sub(5);
2688 for candidate in (start..count).rev() {
2689 if let Ok(request) = self.get_service_request(candidate).await
2690 && request.blueprintId == blueprint_id
2691 && request.requester == account
2692 {
2693 return Ok(candidate);
2694 }
2695 }
2696
2697 Ok(count - 1)
2698 }
2699
2700 fn extract_blueprint_id(&self, receipt: &TransactionReceipt) -> Result<u64> {
2701 for log in receipt.logs() {
2702 if let Ok(event) = log.log_decode::<ITangle::BlueprintCreated>() {
2703 return Ok(event.inner.blueprintId);
2704 }
2705 }
2706
2707 Err(Error::Contract(
2708 "createBlueprint receipt missing BlueprintCreated event".into(),
2709 ))
2710 }
2711}
2712
2713#[derive(Debug, Clone)]
2715pub struct TransactionResult {
2716 pub tx_hash: B256,
2718 pub block_number: Option<u64>,
2720 pub gas_used: u64,
2722 pub success: bool,
2724}
2725
2726#[derive(Debug, Clone)]
2728pub struct JobSubmissionResult {
2729 pub tx: TransactionResult,
2731 pub call_id: u64,
2733}
2734
2735#[derive(Debug, Clone, Copy, PartialEq, Eq)]
2737pub enum ThresholdType {
2738 CountBased,
2740 StakeWeighted,
2742}
2743
2744#[derive(Debug, Clone)]
2746pub struct AggregationConfig {
2747 pub required: bool,
2749 pub threshold_bps: u16,
2751 pub threshold_type: ThresholdType,
2753}
2754
2755fn ecdsa_public_key_to_address(pubkey: &[u8]) -> Result<Address> {
2757 use alloy_primitives::keccak256;
2758
2759 let uncompressed = if pubkey.len() == 33 {
2761 use k256::EncodedPoint;
2763 use k256::elliptic_curve::sec1::FromEncodedPoint;
2764
2765 let point = EncodedPoint::from_bytes(pubkey)
2766 .map_err(|e| Error::InvalidAddress(format!("Invalid compressed key: {e}")))?;
2767
2768 let pubkey: k256::PublicKey = Option::from(k256::PublicKey::from_encoded_point(&point))
2769 .ok_or_else(|| Error::InvalidAddress("Failed to decompress public key".into()))?;
2770
2771 pubkey.to_encoded_point(false).as_bytes().to_vec()
2772 } else if pubkey.len() == 65 {
2773 pubkey.to_vec()
2774 } else if pubkey.len() == 64 {
2775 let mut full = vec![0x04];
2777 full.extend_from_slice(pubkey);
2778 full
2779 } else {
2780 return Err(Error::InvalidAddress(format!(
2781 "Invalid public key length: {}",
2782 pubkey.len()
2783 )));
2784 };
2785
2786 let hash = keccak256(&uncompressed[1..]);
2788
2789 Ok(Address::from_slice(&hash[12..]))
2791}
2792
2793fn normalize_public_key(raw: &[u8]) -> Result<EcdsaPublicKey> {
2794 match raw.len() {
2795 65 => {
2796 let mut key = [0u8; 65];
2797 key.copy_from_slice(raw);
2798 Ok(key)
2799 }
2800 64 => {
2801 let mut key = [0u8; 65];
2802 key[0] = 0x04;
2803 key[1..].copy_from_slice(raw);
2804 Ok(key)
2805 }
2806 33 => {
2807 use k256::EncodedPoint;
2808 use k256::elliptic_curve::sec1::FromEncodedPoint;
2809
2810 let point = EncodedPoint::from_bytes(raw)
2811 .map_err(|e| Error::InvalidAddress(format!("Invalid compressed key: {e}")))?;
2812 let public_key: k256::PublicKey =
2813 Option::from(k256::PublicKey::from_encoded_point(&point)).ok_or_else(|| {
2814 Error::InvalidAddress("Failed to decompress public key".into())
2815 })?;
2816 let encoded = public_key.to_encoded_point(false);
2817 let bytes = encoded.as_bytes();
2818 let mut key = [0u8; 65];
2819 key.copy_from_slice(bytes);
2820 Ok(key)
2821 }
2822 0 => Err(Error::Other(
2823 "Operator has not published an ECDSA public key".into(),
2824 )),
2825 len => Err(Error::InvalidAddress(format!(
2826 "Unexpected operator key length: {len}"
2827 ))),
2828 }
2829}
2830
2831fn asset_info_from_types(asset: IMultiAssetDelegationTypes::Asset) -> AssetInfo {
2832 AssetInfo {
2833 kind: AssetKind::from(asset.kind),
2834 token: asset.token,
2835 }
2836}
2837
2838fn selection_mode_to_u8(mode: BlueprintSelectionMode) -> u8 {
2839 match mode {
2840 BlueprintSelectionMode::All => 0,
2841 BlueprintSelectionMode::Fixed => 1,
2842 BlueprintSelectionMode::Unknown(value) => value,
2843 }
2844}
2845
2846fn transaction_result_from_receipt(receipt: &TransactionReceipt) -> TransactionResult {
2847 TransactionResult {
2848 tx_hash: receipt.transaction_hash,
2849 block_number: receipt.block_number,
2850 gas_used: receipt.gas_used,
2851 success: receipt.status(),
2852 }
2853}
2854
2855impl BlueprintServicesClient for TangleClient {
2860 type PublicApplicationIdentity = EcdsaPublicKey;
2861 type PublicAccountIdentity = Address;
2862 type Id = u64;
2863 type Error = Error;
2864
2865 async fn get_operators(
2867 &self,
2868 ) -> core::result::Result<
2869 OperatorSet<Self::PublicAccountIdentity, Self::PublicApplicationIdentity>,
2870 Self::Error,
2871 > {
2872 let service_id = self
2873 .config
2874 .settings
2875 .service_id
2876 .ok_or_else(|| Error::Other("No service ID configured".into()))?;
2877
2878 let operators = self.get_service_operators(service_id).await?;
2880
2881 let mut map = BTreeMap::new();
2882
2883 for operator in operators {
2884 let metadata = self
2885 .get_operator_metadata(self.config.settings.blueprint_id, operator)
2886 .await?;
2887 map.insert(operator, metadata.public_key);
2888 }
2889
2890 Ok(map)
2891 }
2892
2893 async fn operator_id(
2895 &self,
2896 ) -> core::result::Result<Self::PublicApplicationIdentity, Self::Error> {
2897 let key = self
2898 .keystore
2899 .first_local::<K256Ecdsa>()
2900 .map_err(Error::Keystore)?;
2901
2902 let encoded = key.0.to_encoded_point(false);
2904 let bytes = encoded.as_bytes();
2905
2906 let mut uncompressed = [0u8; 65];
2907 uncompressed.copy_from_slice(bytes);
2908
2909 Ok(uncompressed)
2910 }
2911
2912 async fn blueprint_id(&self) -> core::result::Result<Self::Id, Self::Error> {
2914 Ok(self.config.settings.blueprint_id)
2915 }
2916}