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::asset::AssetId;
8use ark_core::build_anchor_tx;
9use ark_core::history;
10use ark_core::history::generate_incoming_vtxo_transaction_history;
11use ark_core::history::generate_outgoing_vtxo_transaction_history;
12use ark_core::history::sort_transactions_by_created_at;
13use ark_core::history::OutgoingTransaction;
14use ark_core::server;
15use ark_core::server::GetVtxosRequest;
16use ark_core::server::SubscriptionResponse;
17use ark_core::server::VirtualTxOutPoint;
18use ark_core::ArkAddress;
19use ark_core::ExplorerUtxo;
20use ark_core::UtxoCoinSelection;
21use ark_core::Vtxo;
22use ark_core::VtxoList;
23use ark_core::DEFAULT_DERIVATION_PATH;
24use ark_grpc::VtxoChainResponse;
25use bitcoin::bip32::DerivationPath;
26use bitcoin::bip32::Xpriv;
27use bitcoin::key::Keypair;
28use bitcoin::key::Secp256k1;
29use bitcoin::secp256k1::All;
30use bitcoin::Address;
31use bitcoin::Amount;
32use bitcoin::OutPoint;
33use bitcoin::ScriptBuf;
34use bitcoin::Transaction;
35use bitcoin::Txid;
36use bitcoin::XOnlyPublicKey;
37use futures::Future;
38use futures::Stream;
39use std::collections::HashMap;
40use std::collections::HashSet;
41use std::str::FromStr;
42use std::sync::Arc;
43use std::time::Duration;
44
45pub mod error;
46pub mod key_provider;
47pub mod swap_storage;
48pub mod vtxo_watcher;
49pub mod wallet;
50
51mod asset;
52mod batch;
53mod boltz;
54mod coin_select;
55mod fee_estimation;
56mod send_vtxo;
57mod unilateral_exit;
58mod utils;
59
60pub use asset::IssueAssetResult;
61pub use boltz::ChainSwapAmount;
62pub use boltz::ChainSwapData;
63pub use boltz::ChainSwapDirection;
64pub use boltz::ChainSwapResult;
65pub use boltz::PendingVhtlcSpendTx;
66pub use boltz::PendingVhtlcSpendType;
67pub use boltz::ReverseSwapData;
68pub use boltz::SubmarineSwapData;
69pub use boltz::SwapAmount;
70pub use boltz::SwapStatus;
71pub use boltz::SwapStatusInfo;
72pub use boltz::SwapType;
73pub use boltz::TimeoutBlockHeights;
74pub use error::Error;
75pub use key_provider::Bip32KeyProvider;
76pub use key_provider::KeyProvider;
77pub use key_provider::StaticKeyProvider;
78pub use lightning_invoice;
79pub use swap_storage::InMemorySwapStorage;
80#[cfg(feature = "sqlite")]
81pub use swap_storage::SqliteSwapStorage;
82pub use swap_storage::SwapStorage;
83
84pub const DEFAULT_GAP_LIMIT: u32 = 20;
89
90#[derive(Clone)]
288pub struct OfflineClient<B, W, S, K> {
289 network_client: ark_grpc::Client,
291 pub name: String,
292 key_provider: Arc<K>,
293 blockchain: Arc<B>,
294 secp: Secp256k1<All>,
295 wallet: Arc<W>,
296 swap_storage: Arc<S>,
297 boltz_url: String,
298 timeout: Duration,
299 delegator_pk: Option<XOnlyPublicKey>,
300 historical_delegator_pks: Vec<XOnlyPublicKey>,
301}
302
303pub struct Client<B, W, S, K> {
307 inner: OfflineClient<B, W, S, K>,
308 pub server_info: server::Info,
309 fee_estimator: ark_fees::Estimator,
310}
311
312#[derive(Clone, Copy, Debug)]
313pub struct TxStatus {
314 pub confirmed_at: Option<i64>,
315}
316
317#[derive(Clone, Copy, Debug)]
318pub struct SpendStatus {
319 pub spend_txid: Option<Txid>,
320}
321
322pub struct AddressVtxos {
323 pub unspent: Vec<VirtualTxOutPoint>,
324 pub spent: Vec<VirtualTxOutPoint>,
325}
326
327#[derive(Clone, Debug, Default)]
328pub struct OffChainBalance {
329 pre_confirmed: Amount,
330 confirmed: Amount,
331 recoverable: Amount,
332 asset_balances: HashMap<AssetId, u64>,
333}
334
335impl OffChainBalance {
336 pub fn pre_confirmed(&self) -> Amount {
337 self.pre_confirmed
338 }
339
340 pub fn confirmed(&self) -> Amount {
341 self.confirmed
342 }
343
344 pub fn recoverable(&self) -> Amount {
346 self.recoverable
347 }
348
349 pub fn total(&self) -> Amount {
350 self.pre_confirmed + self.confirmed + self.recoverable
351 }
352
353 pub fn asset_balances(&self) -> &HashMap<AssetId, u64> {
355 &self.asset_balances
356 }
357}
358
359pub trait Blockchain {
360 fn find_outpoints(
361 &self,
362 address: &Address,
363 ) -> impl Future<Output = Result<Vec<ExplorerUtxo>, Error>> + Send;
364
365 fn find_tx(
366 &self,
367 txid: &Txid,
368 ) -> impl Future<Output = Result<Option<Transaction>, Error>> + Send;
369
370 fn get_tx_status(&self, txid: &Txid) -> impl Future<Output = Result<TxStatus, Error>> + Send;
371
372 fn get_output_status(
373 &self,
374 txid: &Txid,
375 vout: u32,
376 ) -> impl Future<Output = Result<SpendStatus, Error>> + Send;
377
378 fn broadcast(&self, tx: &Transaction) -> impl Future<Output = Result<(), Error>> + Send;
379
380 fn get_fee_rate(&self) -> impl Future<Output = Result<f64, Error>> + Send;
381
382 fn broadcast_package(
383 &self,
384 txs: &[&Transaction],
385 ) -> impl Future<Output = Result<(), Error>> + Send;
386}
387
388impl<B, W, S, K> OfflineClient<B, W, S, K>
389where
390 B: Blockchain,
391 W: BoardingWallet + OnchainWallet,
392 S: SwapStorage + 'static,
393 K: KeyProvider,
394{
395 #[allow(clippy::too_many_arguments)]
409 pub fn new(
410 name: String,
411 key_provider: Arc<K>,
412 blockchain: Arc<B>,
413 wallet: Arc<W>,
414 ark_server_url: String,
415 swap_storage: Arc<S>,
416 boltz_url: String,
417 timeout: Duration,
418 delegator_pk: Option<XOnlyPublicKey>,
419 historical_delegator_pks: Vec<XOnlyPublicKey>,
420 ) -> Self {
421 let secp = Secp256k1::new();
422
423 let network_client = ark_grpc::Client::new(ark_server_url);
424
425 let mut seen = HashSet::new();
428 let mut historical_delegator_pks: Vec<_> = historical_delegator_pks
429 .into_iter()
430 .filter(|pk| seen.insert(*pk))
431 .collect();
432
433 if let Some(pk) = delegator_pk {
434 historical_delegator_pks.retain(|k| *k != pk);
435 historical_delegator_pks.insert(0, pk);
436 }
437
438 Self {
439 network_client,
440 name,
441 key_provider,
442 blockchain,
443 secp,
444 wallet,
445 swap_storage,
446 boltz_url,
447 timeout,
448 delegator_pk,
449 historical_delegator_pks,
450 }
451 }
452
453 #[allow(clippy::too_many_arguments)]
468 pub fn new_with_keypair(
469 name: String,
470 kp: Keypair,
471 blockchain: Arc<B>,
472 wallet: Arc<W>,
473 ark_server_url: String,
474 swap_storage: Arc<S>,
475 boltz_url: String,
476 timeout: Duration,
477 delegator_pk: Option<XOnlyPublicKey>,
478 historical_delegator_pks: Vec<XOnlyPublicKey>,
479 ) -> OfflineClient<B, W, S, StaticKeyProvider> {
480 let key_provider = Arc::new(StaticKeyProvider::new(kp));
481
482 OfflineClient::new(
483 name,
484 key_provider,
485 blockchain,
486 wallet,
487 ark_server_url,
488 swap_storage,
489 boltz_url,
490 timeout,
491 delegator_pk,
492 historical_delegator_pks,
493 )
494 }
495
496 #[allow(clippy::too_many_arguments)]
509 pub fn new_with_bip32(
510 name: String,
511 xpriv: Xpriv,
512 path: Option<DerivationPath>,
513 blockchain: Arc<B>,
514 wallet: Arc<W>,
515 ark_server_url: String,
516 swap_storage: Arc<S>,
517 boltz_url: String,
518 timeout: Duration,
519 delegator_pk: Option<XOnlyPublicKey>,
520 historical_delegator_pks: Vec<XOnlyPublicKey>,
521 ) -> OfflineClient<B, W, S, Bip32KeyProvider> {
522 let path = path.unwrap_or(
523 DerivationPath::from_str(DEFAULT_DERIVATION_PATH).expect("valid derivation path"),
524 );
525 let key_provider = Arc::new(Bip32KeyProvider::new(xpriv, path));
526
527 OfflineClient::new(
528 name,
529 key_provider,
530 blockchain,
531 wallet,
532 ark_server_url,
533 swap_storage,
534 boltz_url,
535 timeout,
536 delegator_pk,
537 historical_delegator_pks,
538 )
539 }
540
541 pub fn delegator_pk(&self) -> Option<XOnlyPublicKey> {
543 self.delegator_pk
544 }
545
546 pub async fn connect(mut self) -> Result<Client<B, W, S, K>, Error> {
552 timeout_op(self.timeout, self.network_client.connect())
553 .await
554 .context("Failed to connect to Ark server")??;
555
556 self.finish_connect().await
557 }
558
559 pub async fn connect_with_retries(
567 mut self,
568 max_retries: usize,
569 ) -> Result<Client<B, W, S, K>, Error> {
570 let mut n_retries = 0;
571 while n_retries < max_retries {
572 let res = timeout_op(self.timeout, self.network_client.connect())
573 .await
574 .context("Failed to connect to Ark server")?;
575
576 match res {
577 Ok(()) => break,
578 Err(error) => {
579 tracing::warn!(?error, "Failed to connect to Ark server, retrying");
580
581 sleep(Duration::from_secs(2)).await;
582
583 n_retries += 1;
584
585 continue;
586 }
587 };
588 }
589
590 self.finish_connect().await
591 }
592
593 async fn finish_connect(mut self) -> Result<Client<B, W, S, K>, Error> {
594 let server_info = timeout_op(self.timeout, self.network_client.get_info())
595 .await
596 .context("Failed to get Ark server info")??;
597
598 tracing::debug!(
599 name = self.name,
600 ark_server_url = ?self.network_client,
601 "Connected to Ark server"
602 );
603
604 let fee_estimator_config = server_info
605 .fees
606 .clone()
607 .map(|fees| ark_fees::Config {
608 intent_offchain_input_program: fees.intent_fee.offchain_input.unwrap_or_default(),
609 intent_onchain_input_program: fees.intent_fee.onchain_input.unwrap_or_default(),
610 intent_offchain_output_program: fees.intent_fee.offchain_output.unwrap_or_default(),
611 intent_onchain_output_program: fees.intent_fee.onchain_output.unwrap_or_default(),
612 })
613 .unwrap_or_default();
614
615 let fee_estimator =
616 ark_fees::Estimator::new(fee_estimator_config).map_err(Error::ark_server)?;
617
618 let client = Client {
619 inner: self,
620 server_info,
621 fee_estimator,
622 };
623
624 if let Err(error) = client.discover_keys(DEFAULT_GAP_LIMIT).await {
625 tracing::warn!(?error, "Failed during key discovery");
626 };
627
628 Ok(client)
629 }
630}
631
632impl<B, W, S, K> Client<B, W, S, K>
633where
634 B: Blockchain,
635 W: BoardingWallet + OnchainWallet,
636 S: SwapStorage + 'static,
637 K: KeyProvider,
638{
639 pub fn delegator_pk(&self) -> Option<XOnlyPublicKey> {
641 self.inner.delegator_pk()
642 }
643
644 pub fn get_offchain_address(&self) -> Result<(ArkAddress, Vtxo), Error> {
652 let server_info = &self.server_info;
653
654 let server_signer = server_info.signer_pk.into();
655 let owner = self
656 .next_keypair(KeypairIndex::LastUnused)?
657 .public_key()
658 .into();
659
660 let vtxo = self.make_vtxo(server_signer, owner)?;
661
662 let ark_address = vtxo.to_ark_address();
663
664 Ok((ark_address, vtxo))
665 }
666
667 pub fn get_offchain_addresses(&self) -> Result<Vec<(ArkAddress, Vtxo)>, Error> {
674 let server_info = &self.server_info;
675 let server_signer = server_info.signer_pk.into();
676
677 let pks = self.inner.key_provider.get_cached_pks()?;
678
679 let mut results = Vec::new();
680
681 for owner_pk in &pks {
682 let default_vtxo = Vtxo::new_default(
684 self.secp(),
685 server_signer,
686 *owner_pk,
687 server_info.unilateral_exit_delay,
688 server_info.network,
689 )?;
690 results.push((default_vtxo.to_ark_address(), default_vtxo));
691
692 let mut seen = HashSet::new();
694 for dpk in &self.inner.historical_delegator_pks {
695 if !seen.insert(dpk) {
696 continue;
697 }
698 let delegate_vtxo = Vtxo::new_with_delegator(
699 self.secp(),
700 server_signer,
701 *owner_pk,
702 *dpk,
703 server_info.unilateral_exit_delay,
704 server_info.network,
705 )?;
706 results.push((delegate_vtxo.to_ark_address(), delegate_vtxo));
707 }
708 }
709
710 Ok(results)
711 }
712
713 fn make_vtxo(
716 &self,
717 server_signer: XOnlyPublicKey,
718 owner: XOnlyPublicKey,
719 ) -> Result<Vtxo, Error> {
720 let server_info = &self.server_info;
721 match self.inner.delegator_pk {
722 Some(delegator) => Vtxo::new_with_delegator(
723 self.secp(),
724 server_signer,
725 owner,
726 delegator,
727 server_info.unilateral_exit_delay,
728 server_info.network,
729 )
730 .map_err(Into::into),
731 None => Vtxo::new_default(
732 self.secp(),
733 server_signer,
734 owner,
735 server_info.unilateral_exit_delay,
736 server_info.network,
737 )
738 .map_err(Into::into),
739 }
740 }
741
742 pub async fn discover_keys(&self, gap_limit: u32) -> Result<u32, Error> {
753 if !self.inner.key_provider.supports_discovery() {
754 tracing::debug!("Key provider does not support discovery, skipping");
755 return Ok(0);
756 }
757
758 let server_info = &self.server_info;
759 let server_signer: XOnlyPublicKey = server_info.signer_pk.into();
760
761 let mut start_index = 0u32;
762 let mut discovered_count = 0u32;
763
764 tracing::info!(gap_limit, "Starting key discovery");
765
766 loop {
767 let mut batch: Vec<(u32, Keypair, Vec<ArkAddress>)> =
769 Vec::with_capacity(gap_limit as usize);
770
771 for i in 0..gap_limit {
772 let index = start_index
773 .checked_add(i)
774 .ok_or_else(|| Error::ad_hoc("Key discovery index overflow"))?;
775
776 let kp = match self.inner.key_provider.derive_at_discovery_index(index)? {
777 Some(kp) => kp,
778 None => break,
779 };
780
781 let owner_pk = kp.x_only_public_key().0;
782
783 let mut addresses =
784 Vec::with_capacity(1 + self.inner.historical_delegator_pks.len());
785
786 let default_vtxo = Vtxo::new_default(
788 self.secp(),
789 server_signer,
790 owner_pk,
791 server_info.unilateral_exit_delay,
792 server_info.network,
793 )?;
794 addresses.push(default_vtxo.to_ark_address());
795
796 for dpk in &self.inner.historical_delegator_pks {
798 let delegate_vtxo = Vtxo::new_with_delegator(
799 self.secp(),
800 server_signer,
801 owner_pk,
802 *dpk,
803 server_info.unilateral_exit_delay,
804 server_info.network,
805 )?;
806 addresses.push(delegate_vtxo.to_ark_address());
807 }
808
809 batch.push((index, kp, addresses));
810 }
811
812 if batch.is_empty() {
813 break;
814 }
815
816 let addresses = batch.iter().flat_map(|(_, _, addrs)| addrs.iter().copied());
818
819 let vtxo_list = self.list_vtxos_for_addresses(addresses).await?;
820
821 let used_scripts: HashSet<&ScriptBuf> = vtxo_list.all().map(|v| &v.script).collect();
823
824 let mut found_any = false;
826 for (index, kp, addrs) in batch {
827 let used_addr = addrs.iter().find(|addr| {
828 let script = addr.to_p2tr_script_pubkey();
829 used_scripts.contains(&script)
830 });
831 if let Some(addr) = used_addr {
832 tracing::debug!(index, addr = %addr, "Found used address");
833 self.inner
834 .key_provider
835 .cache_discovered_keypair(index, kp)?;
836 discovered_count += 1;
837 found_any = true;
838 }
839 }
840
841 if !found_any {
843 break;
844 }
845
846 start_index = start_index
847 .checked_add(gap_limit)
848 .ok_or_else(|| Error::ad_hoc("Key discovery index overflow"))?;
849 }
850
851 tracing::info!(discovered_count, "Key discovery completed");
852
853 Ok(discovered_count)
854 }
855
856 pub fn get_boarding_address(&self) -> Result<Address, Error> {
858 let server_info = &self.server_info;
859
860 let boarding_output = self.inner.wallet.new_boarding_output(
861 server_info.signer_pk.into(),
862 server_info.boarding_exit_delay,
863 server_info.network,
864 )?;
865
866 Ok(boarding_output.address().clone())
867 }
868
869 pub fn get_onchain_address(&self) -> Result<Address, Error> {
870 self.inner.wallet.get_onchain_address()
871 }
872
873 pub fn get_boarding_addresses(&self) -> Result<Vec<Address>, Error> {
874 let address = self.get_boarding_address()?;
875
876 Ok(vec![address])
877 }
878
879 pub async fn get_virtual_tx_outpoints(
880 &self,
881 addresses: impl Iterator<Item = ArkAddress>,
882 ) -> Result<Vec<VirtualTxOutPoint>, Error> {
883 let request = GetVtxosRequest::new_for_addresses(addresses);
884 self.fetch_all_vtxos(request).await
885 }
886
887 pub async fn list_vtxos(&self) -> Result<(VtxoList, HashMap<ScriptBuf, Vtxo>), Error> {
888 let ark_addresses = self.get_offchain_addresses()?;
889
890 let script_pubkey_to_vtxo_map = ark_addresses
891 .iter()
892 .map(|(a, v)| (a.to_p2tr_script_pubkey(), v.clone()))
893 .collect();
894
895 let addresses = ark_addresses.iter().map(|(a, _)| a).copied();
896
897 let vtxo_list = self.list_vtxos_for_addresses(addresses).await?;
898
899 Ok((vtxo_list, script_pubkey_to_vtxo_map))
900 }
901
902 pub async fn list_vtxos_for_addresses(
903 &self,
904 addresses: impl Iterator<Item = ArkAddress>,
905 ) -> Result<VtxoList, Error> {
906 let virtual_tx_outpoints = self
907 .get_virtual_tx_outpoints(addresses)
908 .await
909 .context("failed to get VTXOs for addresses")?;
910
911 let vtxo_list = VtxoList::new(self.server_info.dust, virtual_tx_outpoints);
912
913 Ok(vtxo_list)
914 }
915
916 pub async fn list_vtxos_for_outpoints(
917 &self,
918 outpoints: Vec<OutPoint>,
919 ) -> Result<(VtxoList, HashMap<ScriptBuf, Vtxo>), Error> {
920 let ark_addresses = self.get_offchain_addresses()?;
921
922 let script_pubkey_to_vtxo_map = ark_addresses
923 .iter()
924 .map(|(a, v)| (a.to_p2tr_script_pubkey(), v.clone()))
925 .collect::<HashMap<_, _>>();
926
927 let request = GetVtxosRequest::new_for_outpoints(&outpoints);
928 let virtual_tx_outpoints = self.fetch_all_vtxos(request).await?;
929
930 let virtual_tx_outpoints = virtual_tx_outpoints
932 .into_iter()
933 .filter(|v| match script_pubkey_to_vtxo_map.get(&v.script) {
934 Some(_) => true,
935 None => {
936 tracing::debug!(outpoint = %v.outpoint, "Missing spend info for VTXO");
937
938 false
939 }
940 })
941 .collect();
942
943 let vtxo_list = VtxoList::new(self.server_info.dust, virtual_tx_outpoints);
944
945 Ok((vtxo_list, script_pubkey_to_vtxo_map))
946 }
947
948 pub async fn get_vtxo_chain(
949 &self,
950 out_point: OutPoint,
951 size: i32,
952 index: i32,
953 ) -> Result<Option<VtxoChainResponse>, Error> {
954 let vtxo_chain = timeout_op(
955 self.inner.timeout,
956 self.network_client()
957 .get_vtxo_chain(Some(out_point), Some((size, index))),
958 )
959 .await
960 .context("Failed to fetch VTXO chain")??;
961
962 Ok(Some(vtxo_chain))
963 }
964
965 pub async fn offchain_balance(&self) -> Result<OffChainBalance, Error> {
966 let (vtxo_list, _) = self.list_vtxos().await.context("failed to list VTXOs")?;
967
968 let pre_confirmed = vtxo_list
969 .pre_confirmed()
970 .fold(Amount::ZERO, |acc, x| acc + x.amount);
971
972 let confirmed = vtxo_list
973 .confirmed()
974 .fold(Amount::ZERO, |acc, x| acc + x.amount);
975
976 let recoverable = vtxo_list
977 .recoverable()
978 .fold(Amount::ZERO, |acc, x| acc + x.amount);
979
980 let mut asset_balances: HashMap<AssetId, u64> = HashMap::new();
982 for vtxo in vtxo_list.spendable_offchain() {
983 for asset in &vtxo.assets {
984 let total = asset_balances
985 .get(&asset.asset_id)
986 .copied()
987 .unwrap_or(0)
988 .checked_add(asset.amount)
989 .ok_or_else(|| Error::ad_hoc("asset balance overflow"))?;
990 asset_balances.insert(asset.asset_id, total);
991 }
992 }
993
994 Ok(OffChainBalance {
995 pre_confirmed,
996 confirmed,
997 recoverable,
998 asset_balances,
999 })
1000 }
1001
1002 pub async fn get_asset(&self, asset_id: AssetId) -> Result<server::AssetInfo, Error> {
1004 timeout_op(
1005 self.inner.timeout,
1006 self.network_client().get_asset(asset_id),
1007 )
1008 .await
1009 .context("Failed to get asset info")?
1010 .map_err(Error::ark_server)
1011 }
1012
1013 pub async fn transaction_history(&self) -> Result<Vec<history::Transaction>, Error> {
1014 let mut boarding_transactions = Vec::new();
1015 let mut boarding_commitment_transactions = Vec::new();
1016
1017 let boarding_addresses = self.get_boarding_addresses()?;
1018 for boarding_address in boarding_addresses.iter() {
1019 let outpoints = timeout_op(
1020 self.inner.timeout,
1021 self.blockchain().find_outpoints(boarding_address),
1022 )
1023 .await
1024 .context("Failed to find outpoints")??;
1025
1026 for ExplorerUtxo {
1027 outpoint,
1028 amount,
1029 confirmation_blocktime,
1030 ..
1031 } in outpoints.iter()
1032 {
1033 let confirmed_at = confirmation_blocktime.map(|t| t as i64);
1034
1035 boarding_transactions.push(history::Transaction::Boarding {
1036 txid: outpoint.txid,
1037 amount: *amount,
1038 confirmed_at,
1039 });
1040
1041 let status = timeout_op(
1042 self.inner.timeout,
1043 self.blockchain()
1044 .get_output_status(&outpoint.txid, outpoint.vout),
1045 )
1046 .await
1047 .context("Failed to get Tx output status")??;
1048
1049 if let Some(spend_txid) = status.spend_txid {
1050 boarding_commitment_transactions.push(spend_txid);
1051 }
1052 }
1053 }
1054
1055 let (vtxo_list, _) = self.list_vtxos().await?;
1056
1057 let spent_outpoints = vtxo_list.spent().cloned().collect::<Vec<_>>();
1058 let unspent_outpoints = vtxo_list.all_unspent().cloned().collect::<Vec<_>>();
1059
1060 let incoming_transactions = generate_incoming_vtxo_transaction_history(
1061 &spent_outpoints,
1062 &unspent_outpoints,
1063 &boarding_commitment_transactions,
1064 )?;
1065
1066 let outgoing_txs =
1067 generate_outgoing_vtxo_transaction_history(&spent_outpoints, &unspent_outpoints)?;
1068
1069 let mut outgoing_transactions = vec![];
1070 for tx in outgoing_txs {
1071 let tx = match tx {
1072 OutgoingTransaction::Complete(tx) => tx,
1073 OutgoingTransaction::Incomplete(incomplete_tx) => {
1074 let first_outpoint = incomplete_tx.first_outpoint();
1075
1076 let request = GetVtxosRequest::new_for_outpoints(&[first_outpoint]);
1077 let vtxos = self.fetch_all_vtxos(request).await?;
1078
1079 match vtxos.first() {
1080 Some(virtual_tx_outpoint) => {
1081 match incomplete_tx.finish(virtual_tx_outpoint) {
1082 Ok(tx) => tx,
1083 Err(e) => {
1084 tracing::warn!(
1085 %first_outpoint,
1086 "Could not finish outgoing TX, skipping: {e}"
1087 );
1088 continue;
1089 }
1090 }
1091 }
1092 None => {
1093 tracing::warn!(
1094 %first_outpoint,
1095 "Could not find virtual TX outpoint for outgoing TX, skipping"
1096 );
1097 continue;
1098 }
1099 }
1100 }
1101 OutgoingTransaction::IncompleteOffboard(incomplete_offboard) => {
1102 let status = timeout_op(
1103 self.inner.timeout,
1104 self.blockchain()
1105 .get_tx_status(&incomplete_offboard.commitment_txid()),
1106 )
1107 .await
1108 .context("failed to get commitment TX status")??;
1109
1110 incomplete_offboard.finish(status.confirmed_at)
1111 }
1112 };
1113
1114 outgoing_transactions.push(tx);
1115 }
1116
1117 let mut txs = [
1118 boarding_transactions,
1119 incoming_transactions,
1120 outgoing_transactions,
1121 ]
1122 .concat();
1123
1124 sort_transactions_by_created_at(&mut txs);
1125
1126 Ok(txs)
1127 }
1128
1129 pub fn dust(&self) -> Amount {
1131 self.server_info.dust
1132 }
1133
1134 pub fn network_client(&self) -> ark_grpc::Client {
1135 self.inner.network_client.clone()
1136 }
1137
1138 async fn fetch_all_vtxos(
1140 &self,
1141 request: GetVtxosRequest,
1142 ) -> Result<Vec<VirtualTxOutPoint>, Error> {
1143 if request.reference().is_empty() {
1144 return Ok(Vec::new());
1145 }
1146
1147 let mut all_vtxos = Vec::new();
1148 let mut cursor = 0;
1149 const PAGE_SIZE: i32 = 100;
1150
1151 loop {
1152 let paged_request = request.clone().with_page(PAGE_SIZE, cursor);
1153 let response = timeout_op(
1154 self.inner.timeout,
1155 self.network_client().list_vtxos(paged_request),
1156 )
1157 .await
1158 .context("failed to fetch list of VTXOs")??;
1159
1160 all_vtxos.extend(response.vtxos);
1161
1162 match response.page {
1164 Some(page) if page.next < page.total => {
1165 cursor = page.next;
1166 }
1167 _ => break,
1168 }
1169 }
1170
1171 Ok(all_vtxos)
1172 }
1173
1174 fn next_keypair(&self, keypair_index: KeypairIndex) -> Result<Keypair, Error> {
1175 self.inner.key_provider.get_next_keypair(keypair_index)
1176 }
1177 fn keypair_by_pk(&self, pk: &XOnlyPublicKey) -> Result<Keypair, Error> {
1178 self.inner.key_provider.get_keypair_for_pk(pk)
1179 }
1180
1181 fn derivation_index_for_pk(&self, pk: &XOnlyPublicKey) -> Option<u32> {
1182 self.inner.key_provider.get_derivation_index_for_pk(pk)
1183 }
1184
1185 fn secp(&self) -> &Secp256k1<All> {
1186 &self.inner.secp
1187 }
1188
1189 fn blockchain(&self) -> &B {
1190 &self.inner.blockchain
1191 }
1192
1193 fn swap_storage(&self) -> &S {
1194 &self.inner.swap_storage
1195 }
1196
1197 pub async fn bump_tx(&self, parent: &Transaction) -> Result<Transaction, Error> {
1199 let fee_rate = timeout_op(self.inner.timeout, self.blockchain().get_fee_rate())
1200 .await
1201 .context("Failed to retrieve fee rate")??;
1202
1203 let change_address = self.inner.wallet.get_onchain_address()?;
1204
1205 let select_coins_fn =
1207 |target_amount: Amount| -> Result<UtxoCoinSelection, ark_core::Error> {
1208 self.inner.wallet.select_coins(target_amount).map_err(|e| {
1209 ark_core::Error::ad_hoc(format!("failed to select coins for anchor TX: {e}"))
1210 })
1211 };
1212
1213 let mut psbt = build_anchor_tx(parent, change_address, fee_rate, select_coins_fn)
1215 .map_err(|e| Error::ad_hoc(e.to_string()))?;
1216
1217 self.inner
1219 .wallet
1220 .sign(&mut psbt)
1221 .context("failed to sign bump TX")?;
1222
1223 let tx = psbt.extract_tx().map_err(Error::ad_hoc)?;
1225
1226 Ok(tx)
1227 }
1228
1229 pub async fn subscribe_to_scripts(
1245 &self,
1246 scripts: Vec<ArkAddress>,
1247 subscription_id: Option<String>,
1248 ) -> Result<String, Error> {
1249 self.network_client()
1250 .subscribe_to_scripts(scripts, subscription_id)
1251 .await
1252 .map_err(Into::into)
1253 }
1254
1255 pub async fn unsubscribe_from_scripts(
1265 &self,
1266 scripts: Vec<ArkAddress>,
1267 subscription_id: String,
1268 ) -> Result<(), Error> {
1269 self.network_client()
1270 .unsubscribe_from_scripts(scripts, subscription_id)
1271 .await
1272 .map_err(Into::into)
1273 }
1274
1275 pub async fn get_subscription(
1288 &self,
1289 subscription_id: String,
1290 ) -> Result<impl Stream<Item = Result<SubscriptionResponse, ark_grpc::Error>> + Unpin, Error>
1291 {
1292 self.network_client()
1293 .get_subscription(subscription_id)
1294 .await
1295 .map_err(Into::into)
1296 }
1297}