1use crate::error::ErrorContext;
2use crate::key_provider::KeypairIndex;
3use crate::utils::sleep;
4use crate::utils::timeout_op;
5use crate::wallet::BoardingWallet;
6use crate::wallet::OnchainWallet;
7use ark_core::build_anchor_tx;
8use ark_core::history;
9use ark_core::history::generate_incoming_vtxo_transaction_history;
10use ark_core::history::generate_outgoing_vtxo_transaction_history;
11use ark_core::history::sort_transactions_by_created_at;
12use ark_core::history::OutgoingTransaction;
13use ark_core::server;
14use ark_core::server::GetVtxosRequest;
15use ark_core::server::SubscriptionResponse;
16use ark_core::server::VirtualTxOutPoint;
17use ark_core::ArkAddress;
18use ark_core::ExplorerUtxo;
19use ark_core::UtxoCoinSelection;
20use ark_core::Vtxo;
21use ark_core::VtxoList;
22use ark_core::DEFAULT_DERIVATION_PATH;
23use ark_grpc::VtxoChainResponse;
24use bitcoin::bip32::DerivationPath;
25use bitcoin::bip32::Xpriv;
26use bitcoin::key::Keypair;
27use bitcoin::key::Secp256k1;
28use bitcoin::secp256k1::All;
29use bitcoin::Address;
30use bitcoin::Amount;
31use bitcoin::OutPoint;
32use bitcoin::ScriptBuf;
33use bitcoin::Transaction;
34use bitcoin::Txid;
35use bitcoin::XOnlyPublicKey;
36use futures::Future;
37use futures::Stream;
38use std::collections::HashMap;
39use std::collections::HashSet;
40use std::str::FromStr;
41use std::sync::Arc;
42use std::time::Duration;
43
44pub mod error;
45pub mod key_provider;
46pub mod swap_storage;
47pub mod wallet;
48
49mod batch;
50mod boltz;
51mod coin_select;
52mod fee_estimation;
53mod send_vtxo;
54mod unilateral_exit;
55mod utils;
56
57pub use boltz::ReverseSwapData;
58pub use boltz::SubmarineSwapData;
59pub use boltz::SwapAmount;
60pub use boltz::TimeoutBlockHeights;
61pub use error::Error;
62pub use key_provider::Bip32KeyProvider;
63pub use key_provider::KeyProvider;
64pub use key_provider::StaticKeyProvider;
65pub use lightning_invoice;
66pub use swap_storage::InMemorySwapStorage;
67#[cfg(feature = "sqlite")]
68pub use swap_storage::SqliteSwapStorage;
69pub use swap_storage::SwapStorage;
70
71pub const DEFAULT_GAP_LIMIT: u32 = 20;
76
77#[derive(Clone)]
271pub struct OfflineClient<B, W, S, K> {
272 network_client: ark_grpc::Client,
274 pub name: String,
275 key_provider: Arc<K>,
276 blockchain: Arc<B>,
277 secp: Secp256k1<All>,
278 wallet: Arc<W>,
279 swap_storage: Arc<S>,
280 boltz_url: String,
281 timeout: Duration,
282}
283
284pub struct Client<B, W, S, K> {
288 inner: OfflineClient<B, W, S, K>,
289 pub server_info: server::Info,
290 fee_estimator: ark_fees::Estimator,
291}
292
293#[derive(Clone, Copy, Debug)]
294pub struct TxStatus {
295 pub confirmed_at: Option<i64>,
296}
297
298#[derive(Clone, Copy, Debug)]
299pub struct SpendStatus {
300 pub spend_txid: Option<Txid>,
301}
302
303pub struct AddressVtxos {
304 pub unspent: Vec<VirtualTxOutPoint>,
305 pub spent: Vec<VirtualTxOutPoint>,
306}
307
308#[derive(Clone, Copy, Debug, Default)]
309pub struct OffChainBalance {
310 pre_confirmed: Amount,
311 confirmed: Amount,
312 recoverable: Amount,
313}
314
315impl OffChainBalance {
316 pub fn pre_confirmed(&self) -> Amount {
317 self.pre_confirmed
318 }
319
320 pub fn confirmed(&self) -> Amount {
321 self.confirmed
322 }
323
324 pub fn recoverable(&self) -> Amount {
326 self.recoverable
327 }
328
329 pub fn total(&self) -> Amount {
330 self.pre_confirmed + self.confirmed + self.recoverable
331 }
332}
333
334pub trait Blockchain {
335 fn find_outpoints(
336 &self,
337 address: &Address,
338 ) -> impl Future<Output = Result<Vec<ExplorerUtxo>, Error>> + Send;
339
340 fn find_tx(
341 &self,
342 txid: &Txid,
343 ) -> impl Future<Output = Result<Option<Transaction>, Error>> + Send;
344
345 fn get_tx_status(&self, txid: &Txid) -> impl Future<Output = Result<TxStatus, Error>> + Send;
346
347 fn get_output_status(
348 &self,
349 txid: &Txid,
350 vout: u32,
351 ) -> impl Future<Output = Result<SpendStatus, Error>> + Send;
352
353 fn broadcast(&self, tx: &Transaction) -> impl Future<Output = Result<(), Error>> + Send;
354
355 fn get_fee_rate(&self) -> impl Future<Output = Result<f64, Error>> + Send;
356
357 fn broadcast_package(
358 &self,
359 txs: &[&Transaction],
360 ) -> impl Future<Output = Result<(), Error>> + Send;
361}
362
363impl<B, W, S, K> OfflineClient<B, W, S, K>
364where
365 B: Blockchain,
366 W: BoardingWallet + OnchainWallet,
367 S: SwapStorage + 'static,
368 K: KeyProvider,
369{
370 #[allow(clippy::too_many_arguments)]
384 pub fn new(
385 name: String,
386 key_provider: Arc<K>,
387 blockchain: Arc<B>,
388 wallet: Arc<W>,
389 ark_server_url: String,
390 swap_storage: Arc<S>,
391 boltz_url: String,
392 timeout: Duration,
393 ) -> Self {
394 let secp = Secp256k1::new();
395
396 let network_client = ark_grpc::Client::new(ark_server_url);
397
398 Self {
399 network_client,
400 name,
401 key_provider,
402 blockchain,
403 secp,
404 wallet,
405 swap_storage,
406 boltz_url,
407 timeout,
408 }
409 }
410
411 #[allow(clippy::too_many_arguments)]
426 pub fn new_with_keypair(
427 name: String,
428 kp: Keypair,
429 blockchain: Arc<B>,
430 wallet: Arc<W>,
431 ark_server_url: String,
432 swap_storage: Arc<S>,
433 boltz_url: String,
434 timeout: Duration,
435 ) -> OfflineClient<B, W, S, StaticKeyProvider> {
436 let key_provider = Arc::new(StaticKeyProvider::new(kp));
437
438 OfflineClient::new(
439 name,
440 key_provider,
441 blockchain,
442 wallet,
443 ark_server_url,
444 swap_storage,
445 boltz_url,
446 timeout,
447 )
448 }
449
450 #[allow(clippy::too_many_arguments)]
463 pub fn new_with_bip32(
464 name: String,
465 xpriv: Xpriv,
466 path: Option<DerivationPath>,
467 blockchain: Arc<B>,
468 wallet: Arc<W>,
469 ark_server_url: String,
470 swap_storage: Arc<S>,
471 boltz_url: String,
472 timeout: Duration,
473 ) -> OfflineClient<B, W, S, Bip32KeyProvider> {
474 let path = path.unwrap_or(
475 DerivationPath::from_str(DEFAULT_DERIVATION_PATH).expect("valid derivation path"),
476 );
477 let key_provider = Arc::new(Bip32KeyProvider::new(xpriv, path));
478
479 OfflineClient::new(
480 name,
481 key_provider,
482 blockchain,
483 wallet,
484 ark_server_url,
485 swap_storage,
486 boltz_url,
487 timeout,
488 )
489 }
490
491 pub async fn connect(mut self) -> Result<Client<B, W, S, K>, Error> {
497 timeout_op(self.timeout, self.network_client.connect())
498 .await
499 .context("Failed to connect to Ark server")??;
500
501 self.finish_connect().await
502 }
503
504 pub async fn connect_with_retries(
512 mut self,
513 max_retries: usize,
514 ) -> Result<Client<B, W, S, K>, Error> {
515 let mut n_retries = 0;
516 while n_retries < max_retries {
517 let res = timeout_op(self.timeout, self.network_client.connect())
518 .await
519 .context("Failed to connect to Ark server")?;
520
521 match res {
522 Ok(()) => break,
523 Err(error) => {
524 tracing::warn!(?error, "Failed to connect to Ark server, retrying");
525
526 sleep(Duration::from_secs(2)).await;
527
528 n_retries += 1;
529
530 continue;
531 }
532 };
533 }
534
535 self.finish_connect().await
536 }
537
538 async fn finish_connect(mut self) -> Result<Client<B, W, S, K>, Error> {
539 let server_info = timeout_op(self.timeout, self.network_client.get_info())
540 .await
541 .context("Failed to get Ark server info")??;
542
543 tracing::debug!(
544 name = self.name,
545 ark_server_url = ?self.network_client,
546 "Connected to Ark server"
547 );
548
549 let fee_estimator_config = server_info
550 .fees
551 .clone()
552 .map(|fees| ark_fees::Config {
553 intent_offchain_input_program: fees.intent_fee.offchain_input.unwrap_or_default(),
554 intent_onchain_input_program: fees.intent_fee.onchain_input.unwrap_or_default(),
555 intent_offchain_output_program: fees.intent_fee.offchain_output.unwrap_or_default(),
556 intent_onchain_output_program: fees.intent_fee.onchain_output.unwrap_or_default(),
557 })
558 .unwrap_or_default();
559
560 let fee_estimator =
561 ark_fees::Estimator::new(fee_estimator_config).map_err(Error::ark_server)?;
562
563 let client = Client {
564 inner: self,
565 server_info,
566 fee_estimator,
567 };
568
569 if let Err(error) = client.discover_keys(DEFAULT_GAP_LIMIT).await {
570 tracing::warn!(?error, "Failed during key discovery");
571 };
572
573 if let Err(error) = client.continue_pending_offchain_txs().await {
574 tracing::warn!(?error, "Failed to recover pending transactions");
575 };
576
577 Ok(client)
578 }
579}
580
581impl<B, W, S, K> Client<B, W, S, K>
582where
583 B: Blockchain,
584 W: BoardingWallet + OnchainWallet,
585 S: SwapStorage + 'static,
586 K: KeyProvider,
587{
588 pub fn get_offchain_address(&self) -> Result<(ArkAddress, Vtxo), Error> {
593 let server_info = &self.server_info;
594
595 let server_signer = server_info.signer_pk.into();
596 let owner = self
597 .next_keypair(KeypairIndex::LastUnused)?
598 .public_key()
599 .into();
600
601 let vtxo = Vtxo::new_default(
602 self.secp(),
603 server_signer,
604 owner,
605 server_info.unilateral_exit_delay,
606 server_info.network,
607 )?;
608
609 let ark_address = vtxo.to_ark_address();
610
611 Ok((ark_address, vtxo))
612 }
613
614 pub fn get_offchain_addresses(&self) -> Result<Vec<(ArkAddress, Vtxo)>, Error> {
615 let server_info = &self.server_info;
616 let server_signer = server_info.signer_pk.into();
617
618 let pks = self.inner.key_provider.get_cached_pks()?;
619
620 pks.into_iter()
621 .map(|owner_pk| {
622 let vtxo = Vtxo::new_default(
623 self.secp(),
624 server_signer,
625 owner_pk,
626 server_info.unilateral_exit_delay,
627 server_info.network,
628 )?;
629
630 let ark_address = vtxo.to_ark_address();
631
632 Ok((ark_address, vtxo))
633 })
634 .collect::<Result<Vec<_>, _>>()
635 }
636
637 pub async fn discover_keys(&self, gap_limit: u32) -> Result<u32, Error> {
648 if !self.inner.key_provider.supports_discovery() {
649 tracing::debug!("Key provider does not support discovery, skipping");
650 return Ok(0);
651 }
652
653 let server_info = &self.server_info;
654 let server_signer: XOnlyPublicKey = server_info.signer_pk.into();
655
656 let mut start_index = 0u32;
657 let mut discovered_count = 0u32;
658
659 tracing::info!(gap_limit, "Starting key discovery");
660
661 loop {
662 let mut batch: Vec<(u32, Keypair, ArkAddress)> = Vec::with_capacity(gap_limit as usize);
664
665 for i in 0..gap_limit {
666 let index = start_index
667 .checked_add(i)
668 .ok_or_else(|| Error::ad_hoc("Key discovery index overflow"))?;
669
670 let kp = match self.inner.key_provider.derive_at_discovery_index(index)? {
671 Some(kp) => kp,
672 None => break,
673 };
674
675 let vtxo = Vtxo::new_default(
676 self.secp(),
677 server_signer,
678 kp.x_only_public_key().0,
679 server_info.unilateral_exit_delay,
680 server_info.network,
681 )?;
682
683 batch.push((index, kp, vtxo.to_ark_address()));
684 }
685
686 if batch.is_empty() {
687 break;
688 }
689
690 let addresses = batch.iter().map(|(_, _, a)| *a);
692
693 let vtxo_list = self.list_vtxos_for_addresses(addresses).await?;
694
695 let used_scripts: HashSet<&ScriptBuf> = vtxo_list.all().map(|v| &v.script).collect();
697
698 let mut found_any = false;
700 for (index, kp, addr) in batch {
701 let script = addr.to_p2tr_script_pubkey();
702 if used_scripts.contains(&script) {
703 tracing::debug!(index, %addr, "Found used address");
704 self.inner
705 .key_provider
706 .cache_discovered_keypair(index, kp)?;
707 discovered_count += 1;
708 found_any = true;
709 }
710 }
711
712 if !found_any {
714 break;
715 }
716
717 start_index = start_index
718 .checked_add(gap_limit)
719 .ok_or_else(|| Error::ad_hoc("Key discovery index overflow"))?;
720 }
721
722 tracing::info!(discovered_count, "Key discovery completed");
723
724 Ok(discovered_count)
725 }
726
727 pub fn get_boarding_address(&self) -> Result<Address, Error> {
729 let server_info = &self.server_info;
730
731 let boarding_output = self.inner.wallet.new_boarding_output(
732 server_info.signer_pk.into(),
733 server_info.boarding_exit_delay,
734 server_info.network,
735 )?;
736
737 Ok(boarding_output.address().clone())
738 }
739
740 pub fn get_onchain_address(&self) -> Result<Address, Error> {
741 self.inner.wallet.get_onchain_address()
742 }
743
744 pub fn get_boarding_addresses(&self) -> Result<Vec<Address>, Error> {
745 let address = self.get_boarding_address()?;
746
747 Ok(vec![address])
748 }
749
750 pub async fn get_virtual_tx_outpoints(
751 &self,
752 addresses: impl Iterator<Item = ArkAddress>,
753 ) -> Result<Vec<VirtualTxOutPoint>, Error> {
754 let request = GetVtxosRequest::new_for_addresses(addresses);
755 self.fetch_all_vtxos(request).await
756 }
757
758 pub async fn list_vtxos(&self) -> Result<(VtxoList, HashMap<ScriptBuf, Vtxo>), Error> {
759 let ark_addresses = self.get_offchain_addresses()?;
760
761 let script_pubkey_to_vtxo_map = ark_addresses
762 .iter()
763 .map(|(a, v)| (a.to_p2tr_script_pubkey(), v.clone()))
764 .collect();
765
766 let addresses = ark_addresses.iter().map(|(a, _)| a).copied();
767
768 let vtxo_list = self.list_vtxos_for_addresses(addresses).await?;
769
770 Ok((vtxo_list, script_pubkey_to_vtxo_map))
771 }
772
773 pub async fn list_vtxos_for_addresses(
774 &self,
775 addresses: impl Iterator<Item = ArkAddress>,
776 ) -> Result<VtxoList, Error> {
777 let virtual_tx_outpoints = self
778 .get_virtual_tx_outpoints(addresses)
779 .await
780 .context("failed to get VTXOs for addresses")?;
781
782 let vtxo_list = VtxoList::new(self.server_info.dust, virtual_tx_outpoints);
783
784 Ok(vtxo_list)
785 }
786
787 pub async fn get_vtxo_chain(
788 &self,
789 out_point: OutPoint,
790 size: i32,
791 index: i32,
792 ) -> Result<Option<VtxoChainResponse>, Error> {
793 let vtxo_chain = timeout_op(
794 self.inner.timeout,
795 self.network_client()
796 .get_vtxo_chain(Some(out_point), Some((size, index))),
797 )
798 .await
799 .context("Failed to fetch VTXO chain")??;
800
801 Ok(Some(vtxo_chain))
802 }
803
804 pub async fn offchain_balance(&self) -> Result<OffChainBalance, Error> {
805 let (vtxo_list, _) = self.list_vtxos().await.context("failed to list VTXOs")?;
806
807 let pre_confirmed = vtxo_list
808 .pre_confirmed()
809 .fold(Amount::ZERO, |acc, x| acc + x.amount);
810
811 let confirmed = vtxo_list
812 .confirmed()
813 .fold(Amount::ZERO, |acc, x| acc + x.amount);
814
815 let recoverable = vtxo_list
816 .recoverable()
817 .fold(Amount::ZERO, |acc, x| acc + x.amount);
818
819 Ok(OffChainBalance {
820 pre_confirmed,
821 confirmed,
822 recoverable,
823 })
824 }
825
826 pub async fn transaction_history(&self) -> Result<Vec<history::Transaction>, Error> {
827 let mut boarding_transactions = Vec::new();
828 let mut boarding_commitment_transactions = Vec::new();
829
830 let boarding_addresses = self.get_boarding_addresses()?;
831 for boarding_address in boarding_addresses.iter() {
832 let outpoints = timeout_op(
833 self.inner.timeout,
834 self.blockchain().find_outpoints(boarding_address),
835 )
836 .await
837 .context("Failed to find outpoints")??;
838
839 for ExplorerUtxo {
840 outpoint,
841 amount,
842 confirmation_blocktime,
843 ..
844 } in outpoints.iter()
845 {
846 let confirmed_at = confirmation_blocktime.map(|t| t as i64);
847
848 boarding_transactions.push(history::Transaction::Boarding {
849 txid: outpoint.txid,
850 amount: *amount,
851 confirmed_at,
852 });
853
854 let status = timeout_op(
855 self.inner.timeout,
856 self.blockchain()
857 .get_output_status(&outpoint.txid, outpoint.vout),
858 )
859 .await
860 .context("Failed to get Tx output status")??;
861
862 if let Some(spend_txid) = status.spend_txid {
863 boarding_commitment_transactions.push(spend_txid);
864 }
865 }
866 }
867
868 let (vtxo_list, _) = self.list_vtxos().await?;
869
870 let spent_outpoints = vtxo_list.spent().cloned().collect::<Vec<_>>();
871 let unspent_outpoints = vtxo_list.all_unspent().cloned().collect::<Vec<_>>();
872
873 let incoming_transactions = generate_incoming_vtxo_transaction_history(
874 &spent_outpoints,
875 &unspent_outpoints,
876 &boarding_commitment_transactions,
877 )?;
878
879 let outgoing_txs =
880 generate_outgoing_vtxo_transaction_history(&spent_outpoints, &unspent_outpoints)?;
881
882 let mut outgoing_transactions = vec![];
883 for tx in outgoing_txs {
884 let tx = match tx {
885 OutgoingTransaction::Complete(tx) => tx,
886 OutgoingTransaction::Incomplete(incomplete_tx) => {
887 let first_outpoint = incomplete_tx.first_outpoint();
888
889 let request = GetVtxosRequest::new_for_outpoints(&[first_outpoint]);
890 let vtxos = self.fetch_all_vtxos(request).await?;
891
892 match vtxos.first() {
893 Some(virtual_tx_outpoint) => {
894 match incomplete_tx.finish(virtual_tx_outpoint) {
895 Ok(tx) => tx,
896 Err(e) => {
897 tracing::warn!(
898 %first_outpoint,
899 "Could not finish outgoing TX, skipping: {e}"
900 );
901 continue;
902 }
903 }
904 }
905 None => {
906 tracing::warn!(
907 %first_outpoint,
908 "Could not find virtual TX outpoint for outgoing TX, skipping"
909 );
910 continue;
911 }
912 }
913 }
914 OutgoingTransaction::IncompleteOffboard(incomplete_offboard) => {
915 let status = timeout_op(
916 self.inner.timeout,
917 self.blockchain()
918 .get_tx_status(&incomplete_offboard.commitment_txid()),
919 )
920 .await
921 .context("failed to get commitment TX status")??;
922
923 incomplete_offboard.finish(status.confirmed_at)
924 }
925 };
926
927 outgoing_transactions.push(tx);
928 }
929
930 let mut txs = [
931 boarding_transactions,
932 incoming_transactions,
933 outgoing_transactions,
934 ]
935 .concat();
936
937 sort_transactions_by_created_at(&mut txs);
938
939 Ok(txs)
940 }
941
942 pub fn boarding_exit_delay_seconds(&self) -> u64 {
952 match self
953 .server_info
954 .boarding_exit_delay
955 .to_relative_lock_time()
956 .expect("relative locktime")
957 {
958 bitcoin::relative::LockTime::Time(time) => time.value() as u64 * 512,
959 bitcoin::relative::LockTime::Blocks(_) => unreachable!(),
960 }
961 }
962
963 pub fn unilateral_vtxo_exit_delay_seconds(&self) -> u64 {
973 match self
974 .server_info
975 .unilateral_exit_delay
976 .to_relative_lock_time()
977 .expect("relative locktime")
978 {
979 bitcoin::relative::LockTime::Time(time) => time.value() as u64 * 512,
980 bitcoin::relative::LockTime::Blocks(_) => unreachable!(),
981 }
982 }
983
984 pub fn network_client(&self) -> ark_grpc::Client {
985 self.inner.network_client.clone()
986 }
987
988 async fn fetch_all_vtxos(
990 &self,
991 request: GetVtxosRequest,
992 ) -> Result<Vec<VirtualTxOutPoint>, Error> {
993 if request.reference().is_empty() {
994 return Ok(Vec::new());
995 }
996
997 let mut all_vtxos = Vec::new();
998 let mut cursor = 0;
999 const PAGE_SIZE: i32 = 100;
1000
1001 loop {
1002 let paged_request = request.clone().with_page(PAGE_SIZE, cursor);
1003 let response = timeout_op(
1004 self.inner.timeout,
1005 self.network_client().list_vtxos(paged_request),
1006 )
1007 .await
1008 .context("failed to fetch list of VTXOs")??;
1009
1010 all_vtxos.extend(response.vtxos);
1011
1012 match response.page {
1014 Some(page) if page.next < page.total => {
1015 cursor = page.next;
1016 }
1017 _ => break,
1018 }
1019 }
1020
1021 Ok(all_vtxos)
1022 }
1023
1024 fn next_keypair(&self, keypair_index: KeypairIndex) -> Result<Keypair, Error> {
1025 self.inner.key_provider.get_next_keypair(keypair_index)
1026 }
1027 fn keypair_by_pk(&self, pk: &XOnlyPublicKey) -> Result<Keypair, Error> {
1028 self.inner.key_provider.get_keypair_for_pk(pk)
1029 }
1030
1031 fn secp(&self) -> &Secp256k1<All> {
1032 &self.inner.secp
1033 }
1034
1035 fn blockchain(&self) -> &B {
1036 &self.inner.blockchain
1037 }
1038
1039 fn swap_storage(&self) -> &S {
1040 &self.inner.swap_storage
1041 }
1042
1043 pub async fn bump_tx(&self, parent: &Transaction) -> Result<Transaction, Error> {
1045 let fee_rate = timeout_op(self.inner.timeout, self.blockchain().get_fee_rate())
1046 .await
1047 .context("Failed to retrieve fee rate")??;
1048
1049 let change_address = self.inner.wallet.get_onchain_address()?;
1050
1051 let select_coins_fn =
1053 |target_amount: Amount| -> Result<UtxoCoinSelection, ark_core::Error> {
1054 self.inner.wallet.select_coins(target_amount).map_err(|e| {
1055 ark_core::Error::ad_hoc(format!("failed to select coins for anchor TX: {e}"))
1056 })
1057 };
1058
1059 let mut psbt = build_anchor_tx(parent, change_address, fee_rate, select_coins_fn)
1061 .map_err(|e| Error::ad_hoc(e.to_string()))?;
1062
1063 self.inner
1065 .wallet
1066 .sign(&mut psbt)
1067 .context("failed to sign bump TX")?;
1068
1069 let tx = psbt.extract_tx().map_err(Error::ad_hoc)?;
1071
1072 Ok(tx)
1073 }
1074
1075 pub async fn subscribe_to_scripts(
1091 &self,
1092 scripts: Vec<ArkAddress>,
1093 subscription_id: Option<String>,
1094 ) -> Result<String, Error> {
1095 self.network_client()
1096 .subscribe_to_scripts(scripts, subscription_id)
1097 .await
1098 .map_err(Into::into)
1099 }
1100
1101 pub async fn unsubscribe_from_scripts(
1111 &self,
1112 scripts: Vec<ArkAddress>,
1113 subscription_id: String,
1114 ) -> Result<(), Error> {
1115 self.network_client()
1116 .unsubscribe_from_scripts(scripts, subscription_id)
1117 .await
1118 .map_err(Into::into)
1119 }
1120
1121 pub async fn get_subscription(
1134 &self,
1135 subscription_id: String,
1136 ) -> Result<impl Stream<Item = Result<SubscriptionResponse, ark_grpc::Error>> + Unpin, Error>
1137 {
1138 self.network_client()
1139 .get_subscription(subscription_id)
1140 .await
1141 .map_err(Into::into)
1142 }
1143}