1use std::{
2 collections::{BTreeMap, HashMap},
3 fmt,
4 path::PathBuf,
5};
6
7use blake3::Hash;
8use chrono::{DateTime, Local};
9use crossbeam_channel::{Receiver, Sender};
10use serde::{de::Visitor, Deserialize, Deserializer, Serialize, Serializer};
12use serde_with::{serde_as, BytesOrString};
13use solana_account_decoder_client_types::UiAccount;
14use solana_clock::{Clock, Epoch};
15use solana_epoch_info::EpochInfo;
16use solana_message::inner_instruction::InnerInstructionsList;
17use solana_pubkey::Pubkey;
18use solana_signature::Signature;
19use solana_transaction::versioned::VersionedTransaction;
20use solana_transaction_context::TransactionReturnData;
21use solana_transaction_error::TransactionError;
22use txtx_addon_kit::types::types::Value;
23use txtx_addon_network_svm_types::subgraph::SubgraphRequest;
24use uuid::Uuid;
25
26pub const DEFAULT_RPC_URL: &str = "https://api.mainnet-beta.solana.com";
27
28#[derive(Debug, Default, Clone, PartialEq, Serialize, Deserialize)]
29pub struct TransactionMetadata {
30 pub signature: Signature,
31 pub logs: Vec<String>,
32 pub inner_instructions: InnerInstructionsList,
33 pub compute_units_consumed: u64,
34 pub return_data: TransactionReturnData,
35}
36
37#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
38#[serde(rename_all = "camelCase")]
39pub enum TransactionConfirmationStatus {
40 Processed,
41 Confirmed,
42 Finalized,
43}
44
45#[derive(Clone, Debug, PartialEq, Eq, Default, Serialize, Deserialize)]
46pub enum BlockProductionMode {
47 #[default]
48 Clock,
49 Transaction,
50 Manual,
51}
52
53#[derive(Debug, Clone)]
54pub struct Collection {
55 pub uuid: Uuid,
56 pub name: String,
57 pub entries: Vec<SubgraphDataEntry>,
58}
59
60#[derive(Debug, Clone)]
61pub struct SubgraphDataEntry {
62 pub uuid: Uuid,
64 pub values: HashMap<String, Value>,
66 pub block_height: u64,
68 pub transaction_hash: Hash,
70}
71
72impl SubgraphDataEntry {
73 pub fn new(values: HashMap<String, Value>, block_height: u64, tx_hash: [u8; 32]) -> Self {
74 Self {
75 uuid: Uuid::new_v4(),
76 values,
77 block_height,
78 transaction_hash: Hash::from_bytes(tx_hash),
79 }
80 }
81}
82
83#[derive(Debug)]
84pub enum SubgraphEvent {
85 EndpointReady,
86 InfoLog(DateTime<Local>, String),
87 ErrorLog(DateTime<Local>, String),
88 WarnLog(DateTime<Local>, String),
89 DebugLog(DateTime<Local>, String),
90 Shutdown,
91}
92
93impl SubgraphEvent {
94 pub fn info<S>(msg: S) -> Self
95 where
96 S: Into<String>,
97 {
98 Self::InfoLog(Local::now(), msg.into())
99 }
100
101 pub fn warn<S>(msg: S) -> Self
102 where
103 S: Into<String>,
104 {
105 Self::WarnLog(Local::now(), msg.into())
106 }
107
108 pub fn error<S>(msg: S) -> Self
109 where
110 S: Into<String>,
111 {
112 Self::ErrorLog(Local::now(), msg.into())
113 }
114
115 pub fn debug<S>(msg: S) -> Self
116 where
117 S: Into<String>,
118 {
119 Self::DebugLog(Local::now(), msg.into())
120 }
121}
122
123#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
125#[serde(rename_all = "camelCase")]
126pub struct ComputeUnitsEstimationResult {
127 pub success: bool,
128 pub compute_units_consumed: u64,
129 pub log_messages: Option<Vec<String>>,
130 pub error_message: Option<String>,
131}
132
133#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
135#[serde(rename_all = "camelCase")]
136pub struct ProfileResult {
137 pub compute_units: ComputeUnitsEstimationResult,
138 pub state: ProfileState,
139}
140
141#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
142#[serde(rename_all = "camelCase")]
143pub struct ProfileState {
144 pub pre_execution: BTreeMap<Pubkey, Option<UiAccount>>,
145 pub post_execution: BTreeMap<Pubkey, Option<UiAccount>>,
146}
147
148impl ProfileState {
149 pub fn new(
150 pre_execution: BTreeMap<Pubkey, Option<UiAccount>>,
151 post_execution: BTreeMap<Pubkey, Option<UiAccount>>,
152 ) -> Self {
153 Self {
154 pre_execution,
155 post_execution,
156 }
157 }
158}
159
160#[derive(Debug, Clone, Deserialize, Serialize)]
161pub enum SchemaDataSourcingEvent {
162 Rountrip(Uuid),
163 ApplyEntry(Uuid, Vec<u8>, u64, [u8; 32]),
164}
165
166#[derive(Debug, Clone)]
167pub enum SubgraphCommand {
168 CreateSubgraph(Uuid, SubgraphRequest, Sender<String>),
169 ObserveSubgraph(Receiver<SchemaDataSourcingEvent>),
170 Shutdown,
171}
172
173#[derive(Debug)]
174pub enum SimnetEvent {
175 Ready,
176 Connected(String),
177 Aborted(String),
178 Shutdown,
179 ClockUpdate(Clock),
180 EpochInfoUpdate(EpochInfo),
181 BlockHashExpired,
182 InfoLog(DateTime<Local>, String),
183 ErrorLog(DateTime<Local>, String),
184 WarnLog(DateTime<Local>, String),
185 DebugLog(DateTime<Local>, String),
186 PluginLoaded(String),
187 TransactionReceived(DateTime<Local>, VersionedTransaction),
188 TransactionProcessed(
189 DateTime<Local>,
190 TransactionMetadata,
191 Option<TransactionError>,
192 ),
193 AccountUpdate(DateTime<Local>, Pubkey),
194 TaggedProfile {
195 result: ProfileResult,
196 tag: String,
197 timestamp: DateTime<Local>,
198 },
199}
200
201impl SimnetEvent {
202 pub fn info<S>(msg: S) -> Self
203 where
204 S: Into<String>,
205 {
206 Self::InfoLog(Local::now(), msg.into())
207 }
208
209 pub fn warn<S>(msg: S) -> Self
210 where
211 S: Into<String>,
212 {
213 Self::WarnLog(Local::now(), msg.into())
214 }
215
216 pub fn error<S>(msg: S) -> Self
217 where
218 S: Into<String>,
219 {
220 Self::ErrorLog(Local::now(), msg.into())
221 }
222
223 pub fn debug<S>(msg: S) -> Self
224 where
225 S: Into<String>,
226 {
227 Self::DebugLog(Local::now(), msg.into())
228 }
229
230 pub fn transaction_processed(meta: TransactionMetadata, err: Option<TransactionError>) -> Self {
231 Self::TransactionProcessed(Local::now(), meta, err)
232 }
233
234 pub fn transaction_received(tx: VersionedTransaction) -> Self {
235 Self::TransactionReceived(Local::now(), tx)
236 }
237
238 pub fn account_update(pubkey: Pubkey) -> Self {
239 Self::AccountUpdate(Local::now(), pubkey)
240 }
241
242 pub fn tagged_profile(result: ProfileResult, tag: String) -> Self {
243 Self::TaggedProfile {
244 result,
245 tag,
246 timestamp: Local::now(),
247 }
248 }
249
250 pub fn account_update_msg(&self) -> String {
251 match self {
252 SimnetEvent::AccountUpdate(_, pubkey) => {
253 format!("Account {} updated.", pubkey)
254 }
255 _ => unreachable!("This function should only be called for AccountUpdate events"),
256 }
257 }
258
259 pub fn epoch_info_update_msg(&self) -> String {
260 match self {
261 SimnetEvent::EpochInfoUpdate(epoch_info) => {
262 format!(
263 "Connection established. Epoch {} / Slot index {} / Slot {}.",
264 epoch_info.epoch, epoch_info.slot_index, epoch_info.absolute_slot
265 )
266 }
267 _ => unreachable!("This function should only be called for EpochInfoUpdate events"),
268 }
269 }
270
271 pub fn plugin_loaded_msg(&self) -> String {
272 match self {
273 SimnetEvent::PluginLoaded(plugin_name) => {
274 format!("Plugin {} successfully loaded.", plugin_name)
275 }
276 _ => unreachable!("This function should only be called for PluginLoaded events"),
277 }
278 }
279
280 pub fn clock_update_msg(&self) -> String {
281 match self {
282 SimnetEvent::ClockUpdate(clock) => {
283 format!("Clock ticking (epoch {}, slot {})", clock.epoch, clock.slot)
284 }
285 _ => unreachable!("This function should only be called for ClockUpdate events"),
286 }
287 }
288}
289
290#[derive(Debug)]
291pub enum TransactionStatusEvent {
292 Success(TransactionConfirmationStatus),
293 SimulationFailure((TransactionError, TransactionMetadata)),
294 ExecutionFailure((TransactionError, TransactionMetadata)),
295 VerificationFailure(String),
296}
297
298#[derive(Debug)]
299pub enum SimnetCommand {
300 SlotForward(Option<Hash>),
301 SlotBackward(Option<Hash>),
302 UpdateClock(ClockCommand),
303 UpdateBlockProductionMode(BlockProductionMode),
304 TransactionReceived(
305 Option<Hash>,
306 VersionedTransaction,
307 Sender<TransactionStatusEvent>,
308 bool,
309 ),
310 Terminate(Option<Hash>),
311}
312
313#[derive(Debug)]
314pub enum ClockCommand {
315 Pause,
316 Resume,
317 Toggle,
318 UpdateSlotInterval(u64),
319}
320
321pub enum ClockEvent {
322 Tick,
323 ExpireBlockHash,
324}
325
326#[derive(Clone, Debug, Default)]
327pub struct SurfpoolConfig {
328 pub simnets: Vec<SimnetConfig>,
329 pub rpc: RpcConfig,
330 pub subgraph: SubgraphConfig,
331 pub plugin_config_path: Vec<PathBuf>,
332}
333
334#[derive(Clone, Debug)]
335pub struct SimnetConfig {
336 pub remote_rpc_url: String,
337 pub slot_time: u64,
338 pub block_production_mode: BlockProductionMode,
339 pub airdrop_addresses: Vec<Pubkey>,
340 pub airdrop_token_amount: u64,
341 pub expiry: Option<u64>,
342}
343
344impl Default for SimnetConfig {
345 fn default() -> Self {
346 Self {
347 remote_rpc_url: DEFAULT_RPC_URL.to_string(),
348 slot_time: 0,
349 block_production_mode: BlockProductionMode::Clock,
350 airdrop_addresses: vec![],
351 airdrop_token_amount: 0,
352 expiry: None,
353 }
354 }
355}
356
357#[derive(Clone, Debug, Default)]
358pub struct SubgraphConfig {}
359
360#[derive(Clone, Debug)]
361pub struct RpcConfig {
362 pub bind_host: String,
363 pub bind_port: u16,
364 pub ws_port: u16,
365}
366
367impl RpcConfig {
368 pub fn get_socket_address(&self) -> String {
369 format!("{}:{}", self.bind_host, self.bind_port)
370 }
371 pub fn get_ws_address(&self) -> String {
372 format!("{}:{}", self.bind_host, self.ws_port)
373 }
374}
375
376#[derive(Debug, Clone, Deserialize, Serialize)]
377pub struct SubgraphPluginConfig {
378 pub uuid: Uuid,
379 pub ipc_token: String,
380 pub subgraph_request: SubgraphRequest,
381}
382
383impl Default for RpcConfig {
384 fn default() -> Self {
385 Self {
386 bind_host: "127.0.0.1".to_string(),
387 bind_port: 8899,
388 ws_port: 8900,
389 }
390 }
391}
392
393#[derive(Serialize, Deserialize, Clone, Debug)]
394pub struct SvmSimnetInitializationRequest {
395 pub domain: String,
396 pub block_production_mode: BlockProductionMode,
397 pub datasource_rpc_url: String,
398}
399
400#[derive(Serialize, Deserialize, Clone, Debug)]
401pub enum SvmSimnetCommand {
402 Init(SvmSimnetInitializationRequest),
403}
404
405#[derive(Serialize, Deserialize)]
406pub struct CreateNetworkRequest {
407 pub workspace_id: Uuid,
408 pub name: String,
409 pub description: Option<String>,
410 pub datasource_rpc_url: String,
411 pub block_production_mode: BlockProductionMode,
412}
413
414impl CreateNetworkRequest {
415 pub fn new(
416 workspace_id: Uuid,
417 name: String,
418 description: Option<String>,
419 datasource_rpc_url: String,
420 block_production_mode: BlockProductionMode,
421 ) -> Self {
422 Self {
423 workspace_id,
424 name,
425 description,
426 datasource_rpc_url,
427 block_production_mode,
428 }
429 }
430}
431
432#[derive(Serialize, Deserialize)]
433pub struct CreateNetworkResponse {
434 pub rpc_url: String,
435}
436
437
438#[derive(Serialize, Deserialize)]
439pub struct DeleteNetworkRequest {
440 pub workspace_id: Uuid,
441 pub network_id: Uuid,
442}
443
444impl DeleteNetworkRequest {
445 pub fn new(
446 workspace_id: Uuid,
447 network_id: Uuid,
448 ) -> Self {
449 Self {
450 workspace_id,
451 network_id,
452 }
453 }
454}
455
456#[derive(Serialize, Deserialize)]
457pub struct DeleteNetworkResponse;
458
459
460#[serde_as]
461#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
462#[serde(rename_all = "camelCase")]
463pub struct AccountUpdate {
464 pub lamports: Option<u64>,
466 #[serde_as(as = "Option<BytesOrString>")]
468 pub data: Option<Vec<u8>>,
469 pub owner: Option<String>,
471 pub executable: Option<bool>,
473 pub rent_epoch: Option<Epoch>,
475}
476
477#[derive(Debug, Clone)]
478pub enum SetSomeAccount {
479 Account(String),
480 NoAccount,
481}
482
483impl<'de> Deserialize<'de> for SetSomeAccount {
484 fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
485 where
486 D: Deserializer<'de>,
487 {
488 struct SetSomeAccountVisitor;
489
490 impl<'de> Visitor<'de> for SetSomeAccountVisitor {
491 type Value = SetSomeAccount;
492
493 fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
494 formatter.write_str("a Pubkey String or the String 'null'")
495 }
496
497 fn visit_some<D_>(self, deserializer: D_) -> std::result::Result<Self::Value, D_::Error>
498 where
499 D_: Deserializer<'de>,
500 {
501 Deserialize::deserialize(deserializer).map(|v: String| match v.as_str() {
502 "null" => SetSomeAccount::NoAccount,
503 _ => SetSomeAccount::Account(v.to_string()),
504 })
505 }
506 }
507
508 deserializer.deserialize_option(SetSomeAccountVisitor)
509 }
510}
511
512impl Serialize for SetSomeAccount {
513 fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
514 where
515 S: Serializer,
516 {
517 match self {
518 SetSomeAccount::Account(val) => serializer.serialize_str(val),
519 SetSomeAccount::NoAccount => serializer.serialize_str("null"),
520 }
521 }
522}
523
524#[serde_as]
525#[derive(Debug, Clone, Default, Serialize, Deserialize)]
526#[serde(rename_all = "camelCase")]
527pub struct TokenAccountUpdate {
528 pub amount: Option<u64>,
530 pub delegate: Option<SetSomeAccount>,
532 pub state: Option<String>,
534 pub delegated_amount: Option<u64>,
536 pub close_authority: Option<SetSomeAccount>,
538}
539
540#[derive(Debug, Clone, serde::Deserialize, serde::Serialize)]
542pub struct SupplyUpdate {
543 pub total: Option<u64>,
544 pub circulating: Option<u64>,
545 pub non_circulating: Option<u64>,
546 pub non_circulating_accounts: Option<Vec<String>>,
547}