Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions rust/ecashapp/src/db.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ use fedimint_core::{
impl_db_lookup, impl_db_record,
util::SafeUrl,
};
use fedimint_eventlog::{EventLogEntry, EventLogId};
use serde::{Deserialize, Serialize};

use crate::multimint::FederationMeta;
Expand Down Expand Up @@ -49,6 +50,7 @@ pub(crate) enum DbKeyPrefix {
PinCodeHash = 0x12,
RequirePinForSpending = 0x13,
ShowMsats = 0x14,
EventLogEntry = 0x15,
}

#[derive(Debug, Clone, Encodable, Decodable, Eq, PartialEq, Hash, Ord, PartialOrd)]
Expand Down Expand Up @@ -365,3 +367,17 @@ impl_db_record!(
value = (),
db_prefix = DbKeyPrefix::ShowMsats,
);

#[derive(Clone, Debug, Encodable, Decodable)]
pub(crate) struct EventLogEntryKey(pub FederationId, pub EventLogId);

#[derive(Clone, Debug, Encodable, Decodable)]
pub(crate) struct EventLogEntryPrefix(pub FederationId);

impl_db_record!(
key = EventLogEntryKey,
value = EventLogEntry,
db_prefix = DbKeyPrefix::EventLogEntry,
);

impl_db_lookup!(key = EventLogEntryKey, query_prefix = EventLogEntryPrefix);
182 changes: 182 additions & 0 deletions rust/ecashapp/src/events.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,182 @@
use fedimint_client::OperationId;
use fedimint_core::module::serde_json;
use fedimint_eventlog::{Event, EventLogEntry};
use fedimint_lnv2_client::events::SendPaymentStatus as LnSendPaymentStatus;
use fedimint_mint_client::event::ReceivePaymentStatus as MintReceivePaymentStatus;
use fedimint_wallet_client::events::SendPaymentStatus as WalletSendPaymentStatus;

/// A parsed payment from the event log with all relevant fields
pub(crate) struct ParsedPayment {
pub operation_id: OperationId,
pub incoming: bool,
pub module: &'static str,
pub amount_msats: u64,
pub fee_msats: Option<u64>,
pub timestamp_ms: i64,
pub success: Option<bool>,
pub oob: Option<String>,
}

/// Either a new payment or an update to an existing one
pub(crate) enum ParsedEvent {
Payment(ParsedPayment),
Update {
operation_id: OperationId,
success: bool,
oob: Option<String>,
},
}

/// Fold an update into a payment list by operation_id, returning the updated payment info
pub(crate) fn apply_update(
payments: &mut [ParsedPayment],
operation_id: &OperationId,
success: bool,
oob: Option<String>,
) {
if let Some(payment) = payments
.iter_mut()
.rfind(|p| p.operation_id == *operation_id)
{
payment.success = Some(success);
if oob.is_some() {
payment.oob = oob;
}
}
}

/// Parse a raw EventLogEntry into a ParsedEvent
pub(crate) fn parse_event_log_entry(entry: &EventLogEntry) -> Option<ParsedEvent> {
// LNv2 send (outgoing, pending)
if let Some(send) = parse::<fedimint_lnv2_client::events::SendPaymentEvent>(entry) {
return Some(ParsedEvent::Payment(ParsedPayment {
operation_id: send.operation_id,
incoming: false,
module: "lnv2",
amount_msats: send.amount.msats,
fee_msats: send.fee.map(|fee| fee.msats),
timestamp_ms: (entry.ts_usecs / 1000) as i64,
success: None,
oob: None,
}));
}

// LNv2 send update (success with preimage, or refunded)
if let Some(update) = parse::<fedimint_lnv2_client::events::SendPaymentUpdateEvent>(entry) {
let (success, oob) = match update.status {
LnSendPaymentStatus::Success(preimage) => (true, Some(hex::encode(preimage))),
LnSendPaymentStatus::Refunded => (false, None),
};
return Some(ParsedEvent::Update {
operation_id: update.operation_id,
success,
oob,
});
}

// LNv2 receive (incoming, immediately successful)
if let Some(receive) = parse::<fedimint_lnv2_client::events::ReceivePaymentEvent>(entry) {
return Some(ParsedEvent::Payment(ParsedPayment {
operation_id: receive.operation_id,
incoming: true,
module: "lnv2",
amount_msats: receive.amount.msats,
fee_msats: None,
timestamp_ms: (entry.ts_usecs / 1000) as i64,
success: Some(true),
oob: None,
}));
}

// Ecash send (outgoing, immediately successful, has oob_notes)
if let Some(send) = parse::<fedimint_mint_client::event::SendPaymentEvent>(entry) {
return Some(ParsedEvent::Payment(ParsedPayment {
operation_id: send.operation_id,
incoming: false,
module: "mint",
amount_msats: send.amount.msats,
fee_msats: None,
timestamp_ms: (entry.ts_usecs / 1000) as i64,
success: Some(true),
oob: Some(send.oob_notes),
}));
}

// Ecash receive (incoming, pending)
if let Some(receive) = parse::<fedimint_mint_client::event::ReceivePaymentEvent>(entry) {
return Some(ParsedEvent::Payment(ParsedPayment {
operation_id: receive.operation_id,
incoming: true,
module: "mint",
amount_msats: receive.amount.msats,
fee_msats: None,
timestamp_ms: (entry.ts_usecs / 1000) as i64,
success: None,
oob: None,
}));
}

// Ecash receive update
if let Some(update) = parse::<fedimint_mint_client::event::ReceivePaymentUpdateEvent>(entry) {
return Some(ParsedEvent::Update {
operation_id: update.operation_id,
success: matches!(update.status, MintReceivePaymentStatus::Success),
oob: None,
});
}

// On-chain send (outgoing, pending)
if let Some(send) = parse::<fedimint_wallet_client::events::SendPaymentEvent>(entry) {
return Some(ParsedEvent::Payment(ParsedPayment {
operation_id: send.operation_id,
incoming: false,
module: "wallet",
amount_msats: send.amount.to_sat() * 1000,
fee_msats: Some(send.fee.to_sat() * 1000),
timestamp_ms: (entry.ts_usecs / 1000) as i64,
success: None,
oob: None,
}));
}

// On-chain send status update (success with txid, or aborted)
if let Some(status) = parse::<fedimint_wallet_client::events::SendPaymentStatusEvent>(entry) {
let (success, oob) = match status.status {
WalletSendPaymentStatus::Success(txid) => (true, Some(txid.to_string())),
WalletSendPaymentStatus::Aborted => (false, None),
};
return Some(ParsedEvent::Update {
operation_id: status.operation_id,
success,
oob,
});
}

// On-chain receive (incoming, immediately successful, has txid)
if let Some(receive) = parse::<fedimint_wallet_client::events::ReceivePaymentEvent>(entry) {
return Some(ParsedEvent::Payment(ParsedPayment {
operation_id: receive.operation_id,
incoming: true,
module: "wallet",
amount_msats: receive.amount.msats,
fee_msats: None,
timestamp_ms: (entry.ts_usecs / 1000) as i64,
success: Some(true),
oob: Some(receive.txid.to_string()),
}));
}

None
}

fn parse<T: Event>(entry: &EventLogEntry) -> Option<T> {
if entry.module.clone().map(|m| m.0) != T::MODULE {
return None;
}

if entry.kind != T::KIND {
return None;
}

serde_json::from_slice::<T>(&entry.payload).ok()
}
1 change: 1 addition & 0 deletions rust/ecashapp/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

mod db;
mod event_bus;
mod events;
mod frb_generated;
mod multimint;
mod nostr;
Expand Down
Loading
Loading