acta_maker_sdk/chain/vault/
settle.rs1use super::pda;
2use crate::chain::ix::{
3 ASSOCIATED_TOKEN_PROGRAM_ID, SYSTEM_PROGRAM_ID, TOKEN_2022_PROGRAM_ID, TOKEN_PROGRAM_ID,
4 derive_associated_token_address as ata,
5};
6use crate::{
7 PositionType,
8 chain::{ChainError, MarketInfo, PositionInfo, PositionStatus},
9};
10use solana_sdk::{
11 instruction::{AccountMeta, Instruction},
12 pubkey::Pubkey,
13};
14
15pub fn build_settle_position_ix(
18 program_id: Pubkey,
19 maker_owner: Pubkey,
20 position: &PositionInfo,
21 market: &MarketInfo,
22) -> Result<Instruction, ChainError> {
23 let buy = position.side.is_taker_buy();
24 require_maker_side(maker_owner, position)?;
25 if position.terms.is_spread() {
26 return crate::chain::spreads::build_cash_settle_ix(
27 program_id,
28 maker_owner,
29 position,
30 market,
31 );
32 }
33 if position.flags & !7 != 0 || position.flags2 != 0 || position.flags3 != 0 {
34 return Err(ChainError::InvalidAccountData);
35 }
36 let market_pda = crate::quote::market_pda(
37 &program_id,
38 &market.underlying_mint,
39 &market.quote_mint,
40 market.expiry_ts,
41 market.is_put,
42 );
43 if market_pda != position.market_pda {
44 return Err(ChainError::Identity("position market"));
45 }
46 if !market.is_finalized
47 || market.is_put != (position.terms.position_type() == PositionType::CashSecuredPut)
48 {
49 return Err(ChainError::InvalidSettlementState);
50 }
51 for program in [
52 market.underlying_token_program_id,
53 market.quote_token_program_id,
54 ] {
55 if program != TOKEN_PROGRAM_ID && program != TOKEN_2022_PROGRAM_ID {
56 return Err(ChainError::InvalidAccountData);
57 }
58 }
59 let otm = is_otm(position, market);
60 let open = position.status == PositionStatus::Open;
61 if position.status != PositionStatus::Funded && !(otm || buy) {
63 return Err(ChainError::InvalidSettlementState);
64 }
65 let Destinations {
66 collateral,
67 settlement,
68 writer,
69 holder,
70 } = destinations(position, market);
71 let position_pda = pda::position(&program_id, position.order_id.as_bytes()).0;
72 let mut accounts = vec![
73 AccountMeta::new(maker_owner, true),
74 AccountMeta::new(position_pda, false),
75 AccountMeta::new(market_pda, false),
76 AccountMeta::new(ata(&position_pda, &collateral.0, &collateral.1), false),
77 if open {
78 AccountMeta::new_readonly(SYSTEM_PROGRAM_ID, false)
79 } else {
80 AccountMeta::new(ata(&position_pda, &settlement.0, &settlement.1), false)
81 },
82 AccountMeta::new(ata(&position.writer, &writer.0, &writer.1), false),
83 if open {
84 AccountMeta::new_readonly(ASSOCIATED_TOKEN_PROGRAM_ID, false)
85 } else {
86 AccountMeta::new(ata(&position.holder, &holder.0, &holder.1), false)
87 },
88 AccountMeta::new_readonly(market.underlying_token_program_id, false),
89 AccountMeta::new_readonly(market.quote_token_program_id, false),
90 ];
91 if position.flags & 1 != 0 {
92 accounts.extend([
93 AccountMeta::new_readonly(market.underlying_mint, false),
94 AccountMeta::new_readonly(market.quote_mint, false),
95 AccountMeta::new(position.writer, false),
96 ]);
97 }
98 Ok(Instruction {
99 program_id,
100 accounts,
101 data: vec![10, 0],
102 })
103}
104
105pub fn settle_position_sequence(
106 program_id: Pubkey,
107 maker_owner: Pubkey,
108 position: &PositionInfo,
109 market: &MarketInfo,
110) -> Result<Vec<Instruction>, ChainError> {
111 if position.terms.is_spread() {
112 require_maker_side(maker_owner, position)?;
113 return crate::chain::spreads::cash_settle_sequence(
114 program_id,
115 maker_owner,
116 position,
117 market,
118 );
119 }
120 let settle = build_settle_position_ix(program_id, maker_owner, position, market)?;
121 let Destinations { writer, holder, .. } = destinations(position, market);
122 let mut instructions = Vec::with_capacity(3);
123 let funded = position.status == PositionStatus::Funded;
125 if position.flags & 1 == 0 && (funded || position.side.is_taker_buy()) {
126 instructions.push(super::lifecycle::create_destination_ata(
127 maker_owner,
128 position.writer,
129 writer.0,
130 writer.1,
131 ));
132 }
133 if funded {
134 instructions.push(super::lifecycle::create_destination_ata(
135 maker_owner,
136 position.holder,
137 holder.0,
138 holder.1,
139 ));
140 }
141 instructions.push(settle);
142 Ok(instructions)
143}
144
145fn require_maker_side(maker_owner: Pubkey, position: &PositionInfo) -> Result<(), ChainError> {
148 let ok = if position.side.is_taker_buy() {
149 position.flags & 1 != 0 || position.writer == maker_owner
150 } else {
151 position.flags & 2 == 0 && position.holder == maker_owner
152 };
153 if ok {
154 Ok(())
155 } else {
156 Err(ChainError::Identity("wallet maker owner"))
157 }
158}
159
160fn is_otm(position: &PositionInfo, market: &MarketInfo) -> bool {
161 if market.is_put {
162 market.settlement_price >= position.terms.strike().value()
163 } else {
164 market.settlement_price <= position.terms.strike().value()
165 }
166}
167
168struct Destinations {
169 collateral: (Pubkey, Pubkey),
170 settlement: (Pubkey, Pubkey),
171 writer: (Pubkey, Pubkey),
173 holder: (Pubkey, Pubkey),
175}
176
177fn destinations(position: &PositionInfo, market: &MarketInfo) -> Destinations {
179 let underlying = (market.underlying_mint, market.underlying_token_program_id);
180 let quote = (market.quote_mint, market.quote_token_program_id);
181 let (collateral, settlement) = if market.is_put {
182 (quote, underlying)
183 } else {
184 (underlying, quote)
185 };
186 let otm = is_otm(position, market);
187 let unfunded_buy = position.side.is_taker_buy() && position.status == PositionStatus::Open;
188 Destinations {
189 collateral,
190 settlement,
191 writer: if otm || unfunded_buy {
192 collateral
193 } else {
194 settlement
195 },
196 holder: if otm { settlement } else { collateral },
197 }
198}
199
200#[cfg(feature = "chain-rpc")]
201impl crate::chain::ChainClient {
202 pub async fn build_settle_position(
203 &self,
204 maker_owner: Pubkey,
205 position_pda: Pubkey,
206 ) -> Result<Instruction, ChainError> {
207 let (position, market) = self.settlement_snapshots(position_pda).await?;
208 build_settle_position_ix(self.program_id(), maker_owner, &position, &market)
209 }
210
211 pub async fn settle_position_sequence(
212 &self,
213 maker_owner: Pubkey,
214 position_pda: Pubkey,
215 ) -> Result<Vec<Instruction>, ChainError> {
216 let (position, market) = self.settlement_snapshots(position_pda).await?;
217 settle_position_sequence(self.program_id(), maker_owner, &position, &market)
218 }
219
220 async fn settlement_snapshots(
221 &self,
222 position_pda: Pubkey,
223 ) -> Result<(PositionInfo, MarketInfo), ChainError> {
224 let position = self.fetch_position_info(&position_pda).await?;
225 if pda::position(&self.program_id(), position.order_id.as_bytes()).0 != position_pda {
226 return Err(ChainError::Identity("position PDA"));
227 }
228 let market = self.fetch_market_info(&position.market_pda).await?;
229 Ok((position, market))
230 }
231}