Skip to main content

dig_events_protocol/
event.rs

1//! The wallet/chain event taxonomy — the heart of the contract (SPEC §5).
2//!
3//! The engine EMITS [`WalletEvent`]s; apps SUBSCRIBE to a FILTERED view of them (by [`EventKind`]).
4//! Subscription is live and best-effort; a subscriber that falls behind uses a
5//! [`Cursor`](crate::Cursor) to catch up from the engine's persisted delta, then resumes live. This
6//! is the "event-driven, poll only on a gap" contract. This module owns ONLY the wire shape — the
7//! bus/store/subscription machinery lives in the engine.
8
9use enumset::EnumSet;
10use serde::{Deserialize, Serialize};
11
12use crate::kind::EventKind;
13use crate::sync::SyncLifecycle;
14use crate::value::{Amount, AssetId, WalletId};
15
16/// The event the engine emits and apps consume.
17///
18/// Tagged by `type` in snake_case on the wire (`{"type":"funds_received",…}`), so a machine consumer
19/// branches on a stable discriminant.
20#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
21#[serde(tag = "type", rename_all = "snake_case")]
22pub enum WalletEvent {
23    /// Inbound value landed for a wallet.
24    FundsReceived {
25        /// The wallet that received value.
26        wallet_id: WalletId,
27        /// The asset received; `None` = native XCH.
28        asset: Option<AssetId>,
29        /// The amount received.
30        amount: Amount,
31        /// The receiving coin id (hex).
32        coin_id: String,
33        /// The confirmation height.
34        confirmed_height: u32,
35    },
36    /// Outbound value confirmed for a wallet.
37    FundsSent {
38        /// The wallet that sent value.
39        wallet_id: WalletId,
40        /// The asset sent; `None` = native XCH.
41        asset: Option<AssetId>,
42        /// The amount sent.
43        amount: Amount,
44        /// The transaction id (hex).
45        tx_id: String,
46        /// The confirmation height.
47        confirmed_height: u32,
48    },
49    /// A tracked coin changed state.
50    CoinStateChanged {
51        /// The affected coin id (hex).
52        coin_id: String,
53        /// Whether the coin is now spent.
54        spent: bool,
55        /// The height it was created at, if known.
56        created_height: Option<u32>,
57        /// The height it was spent at, if spent.
58        spent_height: Option<u32>,
59    },
60    /// A submitted transaction confirmed on-chain.
61    Confirmation {
62        /// The transaction id (hex).
63        tx_id: String,
64        /// The confirmation height.
65        height: u32,
66    },
67    /// A submitted transaction failed (rejected or never confirmed).
68    TransactionFailed {
69        /// The transaction id (hex).
70        tx_id: String,
71        /// A human-readable failure reason.
72        error: String,
73    },
74    /// A new chain tip was observed.
75    NewTip {
76        /// The tip height.
77        height: u32,
78        /// The tip header hash (hex).
79        header_hash: String,
80    },
81    /// Sync progress advanced for a wallet.
82    SyncProgress {
83        /// The wallet whose sync advanced.
84        wallet_id: WalletId,
85        /// The current lifecycle state.
86        state: SyncLifecycle,
87        /// The processed height.
88        peak_height: u32,
89        /// The tip height being synced toward.
90        target_height: u32,
91    },
92    /// CAT metadata for an asset became available.
93    CatInfo {
94        /// The CAT asset id.
95        asset_id: AssetId,
96        /// The resolved ticker/name.
97        name: Option<String>,
98    },
99    /// DID metadata became available.
100    DidInfo {
101        /// The DID launcher id (hex).
102        launcher_id: String,
103    },
104    /// NFT data became available.
105    NftData {
106        /// The NFT launcher id (hex).
107        launcher_id: String,
108    },
109    /// A new HD receive address became active.
110    Derivation {
111        /// The wallet the address belongs to.
112        wallet_id: WalletId,
113        /// The newly-active derivation index.
114        index: u32,
115    },
116}
117
118impl WalletEvent {
119    /// The [`EventKind`] discriminant used for subscription filtering.
120    pub fn kind(&self) -> EventKind {
121        match self {
122            Self::FundsReceived { .. } => EventKind::FundsReceived,
123            Self::FundsSent { .. } => EventKind::FundsSent,
124            Self::CoinStateChanged { .. } => EventKind::CoinStateChanged,
125            Self::Confirmation { .. } => EventKind::Confirmation,
126            Self::TransactionFailed { .. } => EventKind::TransactionFailed,
127            Self::NewTip { .. } => EventKind::NewTip,
128            Self::SyncProgress { .. } => EventKind::SyncProgress,
129            Self::CatInfo { .. } => EventKind::CatInfo,
130            Self::DidInfo { .. } => EventKind::DidInfo,
131            Self::NftData { .. } => EventKind::NftData,
132            Self::Derivation { .. } => EventKind::Derivation,
133        }
134    }
135
136    /// Whether this event passes a subscription filter (an `EnumSet` of kinds).
137    pub fn matches(&self, filter: EnumSet<EventKind>) -> bool {
138        filter.contains(self.kind())
139    }
140}