1use async_trait::async_trait;
2use solana_client::nonblocking::rpc_client::RpcClient;
3use solana_sdk::pubkey::Pubkey;
4use std::collections::HashSet;
5
6use crate::{
7 config::{Config, TransactionPluginType},
8 error::KoraError,
9 transaction::VersionedTransactionResolved,
10};
11
12mod plugin_gas_swap;
13
14use plugin_gas_swap::GasSwapPlugin;
15
16#[derive(Debug, Clone, Copy)]
17pub enum PluginExecutionContext {
18 SignTransaction,
19 SignAndSendTransaction,
20 SignBundle,
21 SignAndSendBundle,
22}
23
24impl PluginExecutionContext {
25 pub(super) fn method_name(self) -> &'static str {
26 match self {
27 Self::SignTransaction => "signTransaction",
28 Self::SignAndSendTransaction => "signAndSendTransaction",
29 Self::SignBundle => "signBundle",
30 Self::SignAndSendBundle => "signAndSendBundle",
31 }
32 }
33}
34
35#[async_trait]
36trait TransactionPlugin: Send + Sync {
37 async fn validate(
38 &self,
39 transaction: &mut VersionedTransactionResolved,
40 _config: &Config,
41 _rpc_client: &RpcClient,
42 fee_payer: &Pubkey,
43 context: PluginExecutionContext,
44 ) -> Result<(), KoraError>;
45
46 fn validate_config(&self, _config: &Config) -> (Vec<String>, Vec<String>) {
49 (vec![], vec![])
50 }
51}
52
53pub struct TransactionPluginRunner {
54 plugins: Vec<Box<dyn TransactionPlugin>>,
55}
56
57impl TransactionPluginRunner {
58 pub fn from_config(config: &Config) -> Self {
59 let mut enabled = HashSet::new();
60 let mut plugins: Vec<Box<dyn TransactionPlugin>> = Vec::new();
61
62 for plugin in &config.kora.plugins.enabled {
73 if !enabled.insert(plugin.clone()) {
74 continue;
75 }
76
77 match plugin {
78 TransactionPluginType::GasSwap => {
79 plugins.push(Box::new(GasSwapPlugin));
80 }
81 }
82 }
83
84 Self { plugins }
85 }
86
87 pub fn validate_config(config: &Config) -> (Vec<String>, Vec<String>) {
88 let mut errors = Vec::new();
89 let mut warnings = Vec::new();
90 let mut seen = HashSet::new();
91
92 for plugin_type in &config.kora.plugins.enabled {
93 if !seen.insert(plugin_type.clone()) {
94 continue;
95 }
96 let plugin: Box<dyn TransactionPlugin> = match plugin_type {
97 TransactionPluginType::GasSwap => Box::new(GasSwapPlugin),
98 };
99 let (e, w) = plugin.validate_config(config);
100 errors.extend(e);
101 warnings.extend(w);
102 }
103
104 (errors, warnings)
105 }
106
107 pub async fn run(
108 &self,
109 transaction: &mut VersionedTransactionResolved,
110 config: &Config,
111 rpc_client: &RpcClient,
112 fee_payer: &Pubkey,
113 context: PluginExecutionContext,
114 ) -> Result<(), KoraError> {
115 for plugin in &self.plugins {
116 plugin.validate(transaction, config, rpc_client, fee_payer, context).await?;
117 }
118
119 Ok(())
120 }
121}