solana_trader_client_rust/provider/
utils.rs

1use std::{collections::HashMap, str::FromStr};
2
3use anyhow::Result;
4use base64::{engine::general_purpose, Engine};
5use serde_json::{json, Value};
6use solana_sdk::{
7    address_lookup_table::AddressLookupTableAccount,
8    instruction::{AccountMeta, Instruction},
9    pubkey::Pubkey,
10    transaction::Transaction,
11};
12use solana_trader_proto::api::{self, Project, TransactionMessage, TransactionMessageV2};
13
14pub trait IntoTransactionMessage {
15    fn into_transaction_message(self) -> TransactionMessage;
16}
17
18impl IntoTransactionMessage for TransactionMessage {
19    fn into_transaction_message(self) -> TransactionMessage {
20        self
21    }
22}
23
24impl IntoTransactionMessage for TransactionMessageV2 {
25    fn into_transaction_message(self) -> TransactionMessage {
26        TransactionMessage {
27            content: self.content,
28            is_cleanup: false,
29        }
30    }
31}
32
33pub fn convert_address_lookup_table(
34    address_lookup_table: &HashMap<String, api::PublicKeys>,
35) -> Result<Vec<AddressLookupTableAccount>> {
36    let mut result = Vec::new();
37
38    for (key, accounts) in address_lookup_table {
39        let lookup_table_address = Pubkey::from_str(key)?;
40        let addresses: Vec<Pubkey> = accounts
41            .pks
42            .iter()
43            .map(|pk| Pubkey::from_str(pk))
44            .collect::<Result<_, _>>()?;
45
46        let lookup_table = AddressLookupTableAccount {
47            key: lookup_table_address,
48            addresses,
49        };
50
51        result.push(lookup_table);
52    }
53
54    Ok(result)
55}
56
57pub fn convert_jupiter_instructions(
58    instructions: &[api::InstructionJupiter],
59) -> Result<Vec<Instruction>> {
60    let mut solana_instructions = Vec::new();
61
62    for inst in instructions {
63        let program_id = Pubkey::from_str(&inst.program_id)?;
64
65        let accounts: Vec<AccountMeta> = inst
66            .accounts
67            .iter()
68            .map(|acc| {
69                let pubkey = Pubkey::from_str(&acc.program_id)?;
70                Ok(AccountMeta {
71                    pubkey,
72                    is_signer: acc.is_signer,
73                    is_writable: acc.is_writable,
74                })
75            })
76            .collect::<Result<Vec<_>>>()?;
77
78        solana_instructions.push(Instruction {
79            program_id,
80            accounts,
81            data: inst.data.clone(),
82        });
83    }
84
85    Ok(solana_instructions)
86}
87
88pub fn convert_raydium_instructions(
89    instructions: &[api::InstructionRaydium],
90) -> Result<Vec<Instruction>> {
91    let mut solana_instructions = Vec::new();
92
93    for inst in instructions {
94        let program_id = Pubkey::from_str(&inst.program_id)?;
95
96        let accounts: Vec<AccountMeta> = inst
97            .accounts
98            .iter()
99            .map(|acc| {
100                let pubkey = Pubkey::from_str(&acc.program_id)?;
101                Ok(AccountMeta {
102                    pubkey,
103                    is_signer: acc.is_signer,
104                    is_writable: acc.is_writable,
105                })
106            })
107            .collect::<Result<Vec<_>>>()?;
108
109        solana_instructions.push(Instruction {
110            program_id,
111            accounts,
112            data: inst.data.clone(),
113        });
114    }
115
116    Ok(solana_instructions)
117}
118
119pub fn create_transaction_message(
120    instructions: Vec<Instruction>,
121    recent_blockhash: &str,
122) -> Result<api::TransactionMessage> {
123    let mut transaction = Transaction::new_with_payer(&instructions, None);
124    transaction.message.recent_blockhash = recent_blockhash.parse()?;
125
126    let serialized = bincode::serialize(&transaction)?;
127    let content = general_purpose::STANDARD.encode(serialized);
128
129    Ok(api::TransactionMessage {
130        content,
131        is_cleanup: false,
132    })
133}
134
135pub fn convert_string_enums(value: &mut Value) {
136    match value {
137        Value::Object(map) => {
138            for (key, val) in map {
139                match (key.as_str(), &val) {
140                    ("project", Value::String(s)) => {
141                        if let Some(project_enum) = Project::from_str_name(s) {
142                            *val = json!(project_enum as i32);
143                        }
144                    }
145
146                    ("infinity", Value::String(s)) => {
147                        let mapped = match s.as_str() {
148                            "INF_NOT" => 0,
149                            "INF" => 1,
150                            "INF_NEG" => 2,
151                            _ => return,
152                        };
153                        *val = json!(mapped);
154                    }
155
156                    _ => convert_string_enums(val),
157                }
158            }
159        }
160        Value::Array(arr) => arr.iter_mut().for_each(convert_string_enums),
161        _ => {}
162    }
163}
164
165#[cfg(test)]
166mod tests {
167    use super::*;
168
169    #[test]
170    fn test_conversions() {
171        let mut value = json!({
172            "project": "P_JUPITER",
173            "tradeFeeRate": "1000",
174            "nested": {
175                "project": "P_RAYDIUM",
176                "priceImpactPercent": {
177                    "infinity": "INF_NOT"
178                }
179            },
180            "array": [
181                {"project": "P_OPENBOOK"}
182            ]
183        });
184
185        convert_string_enums(&mut value);
186
187        assert_eq!(value["project"], 2);
188        assert_eq!(value["nested"]["project"], 3);
189        assert_eq!(value["nested"]["priceImpactPercent"]["infinity"], 0);
190        assert_eq!(value["array"][0]["project"], 5);
191    }
192}