Skip to main content

acta_maker_sdk/chain/vault/
settle.rs

1use 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
15/// Settles a wallet maker's own position, including a vault writer.
16pub 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}