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