Skip to main content

acta_maker_sdk/chain/
events.rs

1use super::ChainError;
2use crate::types::{OptionTerms, Strike, TakerSide};
3
4/// The one event every open emits, whoever writes and holds.
5#[derive(Debug, Clone, PartialEq, Eq)]
6pub struct PositionOpenedEvent {
7    pub position_pda: [u8; 32],
8    pub market_pda: [u8; 32],
9    pub order_id: [u8; 32],
10    pub writer: [u8; 32],
11    pub holder: [u8; 32],
12    /// `Position.flags`: bit 0 a vault writes, bit 1 a vault holds, bit 2 the taker bought.
13    pub flags: u8,
14    pub side: TakerSide,
15    pub terms: OptionTerms,
16    pub quantity: u64,
17    pub price: u64,
18    pub gross_premium: u64,
19    pub net_premium: u64,
20    pub collateral: u64,
21    pub valid_until: u64,
22    pub created_at: u64,
23}
24
25#[derive(Debug, Clone, PartialEq, Eq)]
26pub struct SettleSpreadEvent {
27    pub settler: [u8; 32],
28    pub writer: [u8; 32],
29    pub holder: [u8; 32],
30    pub position_pda: [u8; 32],
31    pub market_pda: [u8; 32],
32    pub final_status: u8,
33    pub is_otm: bool,
34    pub holder_payoff: u64,
35    pub writer_return: u64,
36}
37
38/// The position events this module decodes; other kinds are skipped.
39#[derive(Debug, Clone, PartialEq, Eq)]
40pub enum PositionEvent {
41    Opened(PositionOpenedEvent),
42    SpreadSettled(SettleSpreadEvent),
43}
44
45const HEADER: &[u8; 5] = b"ACTA\x02";
46const POSITION_OPENED: u8 = 11;
47const SETTLE_SPREAD: u8 = 79;
48const FLAG_TAKER_BUY: u8 = 1 << 2;
49
50/// Decode an ACTA version-2 envelope. Other event kinds return None.
51pub fn decode_position_event(data: &[u8]) -> Result<Option<PositionEvent>, ChainError> {
52    if data.len() < 6 || &data[..5] != HEADER {
53        return Err(ChainError::InvalidAccountData);
54    }
55    let bytes = &data[6..];
56    let array = |offset: usize| -> Result<[u8; 32], ChainError> {
57        bytes
58            .get(offset..offset + 32)
59            .and_then(|s| s.try_into().ok())
60            .ok_or(ChainError::InvalidAccountData)
61    };
62    let word = |offset: usize| -> Result<u64, ChainError> {
63        let value = bytes
64            .get(offset..offset + 8)
65            .and_then(|s| s.try_into().ok())
66            .ok_or(ChainError::InvalidAccountData)?;
67        Ok(u64::from_le_bytes(value))
68    };
69    Ok(Some(match data[5] {
70        POSITION_OPENED => {
71            if bytes.len() != 234 {
72                return Err(ChainError::InvalidAccountData);
73            }
74            let flags = bytes[160];
75            let product = crate::ChainPositionType::try_from(bytes[161])
76                .map_err(|error| ChainError::InvalidPositionType(error.0))?;
77            let strike = Strike::new(word(162)?);
78            let upper = product
79                .is_spread()
80                .then(|| word(170))
81                .transpose()?
82                .map(Strike::new);
83            PositionEvent::Opened(PositionOpenedEvent {
84                position_pda: array(0)?,
85                market_pda: array(32)?,
86                order_id: array(64)?,
87                writer: array(96)?,
88                holder: array(128)?,
89                flags,
90                side: if flags & FLAG_TAKER_BUY != 0 {
91                    TakerSide::Buy
92                } else {
93                    TakerSide::Sell
94                },
95                terms: OptionTerms::from_chain(crate::ChainTerms::from_parts(
96                    product, strike, upper,
97                )?),
98                quantity: word(178)?,
99                price: word(186)?,
100                gross_premium: word(194)?,
101                net_premium: word(202)?,
102                collateral: word(210)?,
103                valid_until: word(218)?,
104                created_at: word(226)?,
105            })
106        }
107        SETTLE_SPREAD => {
108            if bytes.len() != 178 || bytes[160] != 4 || bytes[161] > 1 {
109                return Err(ChainError::InvalidAccountData);
110            }
111            PositionEvent::SpreadSettled(SettleSpreadEvent {
112                settler: array(0)?,
113                writer: array(32)?,
114                holder: array(64)?,
115                position_pda: array(96)?,
116                market_pda: array(128)?,
117                final_status: bytes[160],
118                is_otm: bytes[161] == 1,
119                holder_payoff: word(162)?,
120                writer_return: word(170)?,
121            })
122        }
123        _ => return Ok(None),
124    }))
125}