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}