acta_maker_sdk/chain/
events.rs1use super::ChainError;
2use crate::types::{OptionTerms, Strike, TakerSide};
3
4#[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 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#[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
50pub 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}