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};
13use std::time::{SystemTime, UNIX_EPOCH};
14use chrono::{Utc, DateTime};
15use prost_wkt_types::Timestamp;
16pub trait IntoTransactionMessage {
17    fn into_transaction_message(self) -> TransactionMessage;
18}
19
20impl IntoTransactionMessage for TransactionMessage {
21    fn into_transaction_message(self) -> TransactionMessage {
22        self
23    }
24}
25
26impl IntoTransactionMessage for TransactionMessageV2 {
27    fn into_transaction_message(self) -> TransactionMessage {
28        TransactionMessage {
29            content: self.content,
30            is_cleanup: false,
31        }
32    }
33}
34
35pub fn convert_address_lookup_table(
36    address_lookup_table: &HashMap<String, api::PublicKeys>,
37) -> Result<Vec<AddressLookupTableAccount>> {
38    let mut result = Vec::new();
39
40    for (key, accounts) in address_lookup_table {
41        let lookup_table_address = Pubkey::from_str(key)?;
42        let addresses: Vec<Pubkey> = accounts
43            .pks
44            .iter()
45            .map(|pk| Pubkey::from_str(pk))
46            .collect::<Result<_, _>>()?;
47
48        let lookup_table = AddressLookupTableAccount {
49            key: lookup_table_address,
50            addresses,
51        };
52
53        result.push(lookup_table);
54    }
55
56    Ok(result)
57}
58
59pub fn convert_jupiter_instructions(
60    instructions: &[api::InstructionJupiter],
61) -> Result<Vec<Instruction>> {
62    let mut solana_instructions = Vec::new();
63
64    for inst in instructions {
65        let program_id = Pubkey::from_str(&inst.program_id)?;
66
67        let accounts: Vec<AccountMeta> = inst
68            .accounts
69            .iter()
70            .map(|acc| {
71                let pubkey = Pubkey::from_str(&acc.program_id)?;
72                Ok(AccountMeta {
73                    pubkey,
74                    is_signer: acc.is_signer,
75                    is_writable: acc.is_writable,
76                })
77            })
78            .collect::<Result<Vec<_>>>()?;
79
80        solana_instructions.push(Instruction {
81            program_id,
82            accounts,
83            data: inst.data.clone(),
84        });
85    }
86
87    Ok(solana_instructions)
88}
89
90pub fn convert_raydium_instructions(
91    instructions: &[api::InstructionRaydium],
92) -> Result<Vec<Instruction>> {
93    let mut solana_instructions = Vec::new();
94
95    for inst in instructions {
96        let program_id = Pubkey::from_str(&inst.program_id)?;
97
98        let accounts: Vec<AccountMeta> = inst
99            .accounts
100            .iter()
101            .map(|acc| {
102                let pubkey = Pubkey::from_str(&acc.program_id)?;
103                Ok(AccountMeta {
104                    pubkey,
105                    is_signer: acc.is_signer,
106                    is_writable: acc.is_writable,
107                })
108            })
109            .collect::<Result<Vec<_>>>()?;
110
111        solana_instructions.push(Instruction {
112            program_id,
113            accounts,
114            data: inst.data.clone(),
115        });
116    }
117
118    Ok(solana_instructions)
119}
120
121pub fn create_transaction_message(
122    instructions: Vec<Instruction>,
123    recent_blockhash: &str,
124) -> Result<api::TransactionMessage> {
125    let mut transaction = Transaction::new_with_payer(&instructions, None);
126    transaction.message.recent_blockhash = recent_blockhash.parse()?;
127
128    let serialized = bincode::serialize(&transaction)?;
129    let content = general_purpose::STANDARD.encode(serialized);
130
131    Ok(api::TransactionMessage {
132        content,
133        is_cleanup: false,
134    })
135}
136
137pub fn convert_string_enums(value: &mut Value) {
138    match value {
139        Value::Object(map) => {
140            for (key, val) in map {
141                match (key.as_str(), &val) {
142                    ("project", Value::String(s)) => {
143                        if let Some(project_enum) = Project::from_str_name(s) {
144                            *val = json!(project_enum as i32);
145                        }
146                    }
147
148                    ("infinity", Value::String(s)) => {
149                        let mapped = match s.as_str() {
150                            "INF_NOT" => 0,
151                            "INF" => 1,
152                            "INF_NEG" => 2,
153                            _ => return,
154                        };
155                        *val = json!(mapped);
156                    }
157
158                    _ => convert_string_enums(val),
159                }
160            }
161        }
162        Value::Array(arr) => arr.iter_mut().for_each(convert_string_enums),
163        _ => {}
164    }
165}
166
167#[cfg(test)]
168mod tests {
169    use super::*;
170
171    #[test]
172    fn test_conversions() {
173        let mut value = json!({
174            "project": "P_JUPITER",
175            "tradeFeeRate": "1000",
176            "nested": {
177                "project": "P_RAYDIUM",
178                "priceImpactPercent": {
179                    "infinity": "INF_NOT"
180                }
181            },
182            "array": [
183                {"project": "P_OPENBOOK"}
184            ]
185        });
186
187        convert_string_enums(&mut value);
188
189        assert_eq!(value["project"], 2);
190        assert_eq!(value["nested"]["project"], 3);
191        assert_eq!(value["nested"]["priceImpactPercent"]["infinity"], 0);
192        assert_eq!(value["array"][0]["project"], 5);
193    }
194}
195
196/// Creates and returns a Protocol Buffer Timestamp object based on the current time.
197/// This function captures the current time and converts it to a Protocol Buffer
198pub fn timestamp() -> Option<Timestamp> {
199    SystemTime::now().duration_since(UNIX_EPOCH).ok().map(|duration| {
200        Timestamp {
201            seconds: duration.as_secs() as i64,
202            nanos: duration.subsec_nanos() as i32,
203        }
204    })
205}
206
207/// Returns the current time as an RFC 3339 formatted string suitable for
208/// Protocol Buffer Timestamp JSON serialization.
209pub fn timestamp_rfc3339() -> String {
210    let now: DateTime<Utc> = Utc::now();
211    format!("{}.{}Z", 
212        now.format("%Y-%m-%dT%H:%M:%S"),
213        format!("{:06}", now.timestamp_subsec_micros()).chars().take(3).collect::<String>() + "000"
214    )
215}