pub(crate) mod discovery;
pub(crate) mod errors;
pub(crate) mod lineage;
pub(crate) mod observer;
pub(crate) mod store;
use alloc::boxed::Box;
use alloc::collections::BTreeSet;
use alloc::sync::Arc;
use alloc::vec::Vec;
use async_trait::async_trait;
pub use errors::PswapLineageError;
use lineage::PswapLineageFilter;
pub use lineage::{PswapLineageRecord, PswapLineageState};
use miden_protocol::Felt;
use miden_protocol::account::AccountId;
use miden_protocol::note::Note;
use miden_standards::note::PswapNote;
use miden_tx::auth::TransactionAuthenticator;
pub use observer::PswapChainObserver;
use crate::store::{NoteFilter, Store};
use crate::sync::{NoteTagRecord, NoteTagSource};
use crate::transaction::{
TransactionObserver,
TransactionRequest,
TransactionRequestBuilder,
TransactionResult,
notes_from_output,
};
use crate::{Client, ClientError};
pub struct PswapTransactionObserver {
store: Arc<dyn Store>,
}
impl PswapTransactionObserver {
pub fn new(store: Arc<dyn Store>) -> Self {
Self { store }
}
}
#[async_trait(?Send)]
impl TransactionObserver for PswapTransactionObserver {
fn name(&self) -> &'static str {
"PswapTransactionObserver"
}
async fn apply(&self, tx_result: &TransactionResult) -> Result<(), ClientError> {
let output_notes = tx_result.executed_transaction().output_notes();
for note in notes_from_output(output_notes) {
let Ok(pswap) = PswapNote::try_from(note) else {
continue;
};
if pswap.parent_depth() != 0 {
continue;
}
let record = PswapLineageRecord::new_depth_zero(note.id(), &pswap);
store::put_lineage(&self.store, &record).await?;
self.store
.add_note_tag(NoteTagRecord {
tag: PswapNote::create_tag(
pswap.note_type(),
pswap.offered_asset(),
pswap.storage().requested_asset(),
),
source: NoteTagSource::Subscription(record.original_note_id.as_word()),
})
.await?;
}
Ok(())
}
}
impl<AUTH: TransactionAuthenticator + Sync + 'static> Client<AUTH> {
pub async fn pswap_lineages(&self) -> Result<Vec<PswapLineageRecord>, ClientError> {
store::list_lineages(&self.store, PswapLineageFilter::All)
.await
.map_err(Into::into)
}
pub async fn pswap_lineages_for(
&self,
creator: AccountId,
) -> Result<Vec<PswapLineageRecord>, ClientError> {
store::list_lineages(&self.store, PswapLineageFilter::ByCreator(creator))
.await
.map_err(Into::into)
}
pub async fn pswap_active_lineages(&self) -> Result<Vec<PswapLineageRecord>, ClientError> {
store::list_lineages(&self.store, PswapLineageFilter::Active)
.await
.map_err(Into::into)
}
pub async fn pswap_lineage(
&self,
order_id: Felt,
) -> Result<Option<PswapLineageRecord>, ClientError> {
store::get_lineage(&self.store, order_id).await.map_err(Into::into)
}
pub async fn build_pswap_cancel_by_order(
&self,
order_id: Felt,
) -> Result<TransactionRequest, ClientError> {
let lineage = store::get_lineage(&self.store, order_id)
.await?
.ok_or(PswapLineageError::NotFound(order_id))?;
if lineage.state != PswapLineageState::Active {
return Err(PswapLineageError::NotActive(lineage.state).into());
}
let creator = lineage.creator_account_id();
let local_accounts: BTreeSet<_> = self.store.get_account_ids().await?.into_iter().collect();
if !local_accounts.contains(&creator) {
return Err(PswapLineageError::CreatorNotLocal(creator).into());
}
let tip_note: Note = if lineage.current_depth == 0 {
Note::from(store::get_original_pswap(&self.store, lineage.original_note_id).await?)
} else {
let record = self
.store
.get_input_notes(NoteFilter::Unique(lineage.current_tip_note_id))
.await?
.into_iter()
.next()
.ok_or(PswapLineageError::TipMissing)?;
record.try_into().map_err(ClientError::NoteRecordConversionError)?
};
TransactionRequestBuilder::new()
.build_pswap_cancel(tip_note, lineage.creator_account_id())
.map_err(ClientError::TransactionRequestError)
}
}